[英]Mocked DbSet not returning an object
我正在嘗試通過使用 Moq 模擬數據來測試更新功能。 我正在使用實體框架 6。
我可以打印出DbSet
的計數,這是預期的數量。 但是,當它嘗試選擇一個對象時,它會拋出一個異常NullReferenceException: Object reference not set to an instance of an object.
這是我的測試類,它設置了DbSets
和DbContext
[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);
}
_brandRepo
的類型是IBrandRepository
的實現和繼承畢竟,函數Get()
是在一個通用存儲庫類。
這是正在調用的 Get 函數。
public virtual TEntity Get(TId id)
{
return this.DbSet.Single(x => (object)x.Id == (object)id);
}
返回行是拋出錯誤的內容。
由於這是一個測試並且我正在模擬數據,因此我知道傳入的 Id 是正確的。 它從一個int
開始, Brand
的Id
也是一個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; }
}
我不確定這是一個嘲笑問題還是使用泛型類型的問題。 有人可以幫忙嗎。
這看起來太復雜了; 您可以使用通用方法為任何 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 中的數據相同,您可以檢查列表以斷言您的操作。
如果在任何時候您嘗試通過 DbSet 的屬性而不是通過Set<>
訪問它們,如果未設置它們,則會導致該問題。 盡管原始示例中的 call base 為 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());
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.