2014-09-22 55 views
1

我試圖通過使用Moq模擬數據來測試更新函數。我正在使用實體框架6.Mocked DbSet不返回對象

我可以打印出DbSet的計數,這是預期金額。然而,當它試圖選擇一個對象,它拋出一個異常,NullReferenceException: Object reference not set to an instance of an object.

這裏是我的測試類設置的嘲笑DbSetsDbContext

[TestFixture] 
public class ProductControllerTest 
{ 
    private ProductController controller; 
    private IProductRepository productRepo; 
    private IUnitOfWork unitOfWork; 
    private IBrandRepository brandRepo; 
    private ICategoryRepository categoryRepo; 
    private ISegmentRepository segmentRepo; 
    private ITypeRepository typeRepo; 
    private IEnumerable<Product> productList; 

    [SetUp] 
    public void Init() 
    { 
     IEnumerable<Brand> brandList = new List<Brand>{ 
      new Brand{ 
       Id = 1, 
       Name = "Unknown" 
      }, 
      new Brand{ 
       Id = 2, 
       Name = "Clorox" 
      }, 
      new Brand{ 
       Id = 3, 
       Name = "Glad" 
      } 
     }; 
     var brandData = brandList.AsQueryable(); 

     productList = new List<Product>{ 
      new Product{ 
       Id = "0000000001", 
       ParentAsin = "0000000010", 
       Title = "Mocked Product #1", 
       ReleaseDate = DateTime.Now, 
       BrandId = 1, 
       CategoryId = 1, 
       SegmentId = 1, 
       TypeId = 1, 
       Brand = brandList.ElementAt(0) 
      }, 
      new Product{ 
       Id = "0000000002", 
       ParentAsin = "0000000010", 
       Title = "Mocked Product #2", 
       ReleaseDate = DateTime.Now, 
       BrandId = 1, 
       CategoryId = 1, 
       SegmentId = 1, 
       TypeId = 1, 
       Brand = brandList.ElementAt(0) 
      }, 
      new Product{ 
       Id = "0000000003", 
       ParentAsin = "0000000010", 
       Title = "Mocked Product #3", 
       ReleaseDate = DateTime.Now, 
       BrandId = 2, 
       CategoryId = 3, 
       SegmentId = 3, 
       TypeId = 2, 
       Brand = brandList.ElementAt(1) 
      } 
     }; 
     var productData = productList.AsQueryable(); 

     brandList.ElementAt(1).Products.Add(productList.ElementAt<Product>(2)); 

     var mockProductSet = new Mock<DbSet<Product>>(); 
     mockProductSet.As<IQueryable<Product>>().Setup(m => m.Provider).Returns(productData.Provider); 
     mockProductSet.As<IQueryable<Product>>().Setup(m => m.Expression).Returns(productData.Expression); 
     mockProductSet.As<IQueryable<Product>>().Setup(m => m.ElementType).Returns(productData.ElementType); 
     mockProductSet.As<IQueryable<Product>>().Setup(m => m.GetEnumerator()).Returns(productData.GetEnumerator()); 

     var mockBrandSet = new Mock<DbSet<Brand>>(); 
     mockBrandSet.As<IQueryable<Brand>>().Setup(m => m.Provider).Returns(brandData.Provider); 
     mockBrandSet.As<IQueryable<Brand>>().Setup(m => m.Expression).Returns(brandData.Expression); 
     mockBrandSet.As<IQueryable<Brand>>().Setup(m => m.ElementType).Returns(brandData.ElementType); 
     mockBrandSet.As<IQueryable<Brand>>().Setup(m => m.GetEnumerator()).Returns(brandData.GetEnumerator()); 

     var mockContext = new Mock<ApplicationDbContext>() { CallBase = true }; 
     mockContext.Setup(m => m.Set<Product>()).Returns(mockProductSet.Object); 
     mockContext.Setup(m => m.Set<Brand>()).Returns(mockBrandSet.Object); 

     unitOfWork = new UnitOfWork(mockContext.Object); 
     brandRepo = new BrandRepository(mockContext.Object); 
     productRepo = new ProductRepository(mockContext.Object); 
     controller = new ProductController(productRepo, unitOfWork, brandRepo, categoryRepo, segmentRepo, typeRepo); 
    } 

    [Test] 
    public void TestReturnEditedModel() 
    { 
     Product product = productList.ElementAt<Product>(1); 
     product.BrandId = 3; 
     product.CategoryId = 2; 
     product.SegmentId = 2; 
     product.TypeId = 3; 

     controller.Edit(product, "Return value"); 

     Product result = productRepo.Get(product.Id); 
     Assert.AreEqual(product.Id, result.Id); 
     Assert.AreEqual(3, result.BrandId); 
     Assert.AreEqual(2, result.CategoryId); 
     Assert.AreEqual(2, result.SegmentId); 
     Assert.AreEqual(3, result.TypeId); 
    } 
} 

我只提供了失敗的考驗。

這裏是被稱爲

[HttpPost] 
public ActionResult Edit([Bind(Include = "Id,Title,ParentAsin,ReleaseDate,BrandId,CategoryId,SegmentId,TypeId")]Product model, string returnAction) 
{ 
    if(!ModelState.IsValid) 
    { 
     Dictionary<string, int> selectedIds = new Dictionary<string, int>(); 
     selectedIds.Add("BrandId", model.BrandId); 
     selectedIds.Add("CategoryId", model.CategoryId); 
     selectedIds.Add("SegmentId", model.SegmentId); 
     selectedIds.Add("TypeId", model.TypeId); 
     PopulateAllDropDownLists(selectedIds); 
     return View(model); 
    } 

    model.Brand = _brandRepo.Get(model.BrandId); 
    model.Category = _categoryRepo.Get(model.CategoryId); 
    model.Segment = _segmentRepo.Get(model.SegmentId); 
    model.Type = _typeRepo.Get(model.TypeId); 
    _repository.Update(model); 
    _unitOfWork.SaveChanges(); 
    return RedirectToAction(returnAction); 
} 

_brandRepoIBrandRepository類型的和所有的實施方式和繼承後,功能Get()處於通用庫類的控制器功能。

這是Get函數被調用。

public virtual TEntity Get(TId id) 
{ 
    return this.DbSet.Single(x => (object)x.Id == (object)id); 
} 

返回行是什麼引發錯誤。

由於這是一個測試,我嘲笑的數據,我知道傳入的Id是正確的。它以int開始,而IdBrand也是int,但是爲了使其具有這種通用性,屬性的類型爲TId是所有模型實現的接口TEntity的通用類型。

這裏是TEntity

public interface IEntity<TId> 
{ 
    /// <summary> 
    /// Gets or sets the unique identifier. 
    /// </summary> 
    /// <value>The unique identifier.</value> 
    TId Id { get; set; } 
} 

我不知道這是否是一個嘲諷的問題或使用泛型類型的問題。有人可以幫助這個。

+0

你能用較小的單元測試重現這個嗎?你的例子中有很多。 – 2014-09-23 20:53:46

+0

@RyanGates我拿出了測試設置的代碼,我仍然能夠重新創建錯誤。當我查詢嘲笑的'DbSet ' – DFord 2014-09-23 21:09:53

回答

0

如果您在任何時候嘗試通過其屬性訪問DbSets,而不是通過Set<>訪問DbSets,那麼如果它們沒有設置,會導致該問題。雖然在原始示例中調用基數爲true,但DbContext將在內部嘗試發現DbSets並對其進行初始化,這將在模擬DbContext時失敗。這是他們必須在模擬中設置以覆蓋默認行爲。

var mockContext = new Mock<ApplicationDbContext>(); 
mockContext.Setup(m => m.Set<Product>()).Returns(mockProductSet.Object); 
mockContext.Setup(m => m.Set<Brand>()).Returns(mockBrandSet.Object); 
mockContext.Setup(m => m.Products).Returns(mockProductSet.Object); 
mockContext.Setup(m => m.Brands).Returns(mockBrandSet.Object); 

還設立GetEnumerator()時,使用的功能,允許多個呼叫

mockProductSet.As<IQueryable<Product>>() 
    .Setup(m => m.GetEnumerator()) 
    .Returns(() => productData.GetEnumerator()); 
1

這看起來方式複雜,你可以只使用一個通用的方法來創建一個模擬對於任何DbSet ...

public static class DbSetMock 
{ 
    public static Mock<DbSet<T>> CreateFrom<T>(List<T> list) where T : class 
    { 
     var internalQueryable = list.AsQueryable(); 
     var mock = new Mock<DbSet<T>>(); 
     mock.As<IQueryable<T>>().Setup(x => x.Provider).Returns(internalQueryable.Provider); 
     mock.As<IQueryable<T>>().Setup(x => x.Expression).Returns(internalQueryable.Expression); 
     mock.As<IQueryable<T>>().Setup(x => x.ElementType).Returns(internalQueryable.ElementType); 
     mock.As<IQueryable<T>>().Setup(x => x.GetEnumerator()).Returns(()=> internalQueryable.GetEnumerator()); 
     mock.As<IDbSet<T>>().Setup(x => x.Add(It.IsAny<T>())).Callback<T>(element => list.Add(element)); 
     mock.As<IDbSet<T>>().Setup(x => x.Remove(It.IsAny<T>())).Callback<T>(element => list.Remove(element)); 
     return mock; 
    } 
} 

然後你可以使用它像:

var mockBrandSet = DbSetMock.CreateFrom(brandList); 

由於列表和DbSet內的數據是可以檢查列表,確認你的操作相同。