C# 模拟数据库集不返回对象
我试图通过使用Moq模拟数据来测试更新函数。我使用的是实体框架6 我可以打印出C# 模拟数据库集不返回对象,c#,.net,entity-framework,generics,moq,C#,.net,Entity Framework,Generics,Moq,我试图通过使用Moq模拟数据来测试更新函数。我使用的是实体框架6 我可以打印出DbSet的计数,它是预期的数量。但是,当它尝试选择一个对象时,会抛出一个异常,NullReferenceException:object reference未设置为对象的实例。 下面是我的测试类,它设置模拟的dbset和DbContext [TestFixture] public class ProductControllerTest { private ProductController controlle
DbSet
的计数,它是预期的数量。但是,当它尝试选择一个对象时,会抛出一个异常,NullReferenceException:object reference未设置为对象的实例。
下面是我的测试类,它设置模拟的dbset
和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);
}
}
返回行是抛出错误的内容
因为这是一个测试,我在模拟数据,所以我知道传入的Id是正确的。它以int
开始,而Brand
的Id
也是int
,但为了使该属性具有通用性,该属性的类型为TId
是所有模型都实现的接口tenty
的通用类型
这里是帐篷
public interface IEntity<TId>
{
/// <summary>
/// Gets or sets the unique identifier.
/// </summary>
/// <value>The unique identifier.</value>
TId Id { get; set; }
}
公共接口的可扩展性
{
///
///获取或设置唯一标识符。
///
///唯一标识符。
TId Id{get;set;}
}
我不确定这是一个模拟问题还是使用泛型类型的问题。有人可以帮忙吗。如果在任何时候您试图通过数据库集的属性而不是通过
Set来访问数据库集,那么如果没有设置这些数据库集,就会出现问题。尽管原始示例中的call base为true,DbContext
会在内部尝试发现dbset
并初始化它们,这在模拟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);
这看起来太复杂了;您可以使用一个通用方法为任何DbSet创建一个mock
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;
}
}
由于列表和DbSet中的数据是相同的,您可以检查列表来断言您的操作。您是否能够用较小的单元测试重现此操作?您的示例中有很多内容。@RyanGates我从测试设置的代码中取出,仍然能够重新创建错误。当我为EFCore查询模拟的DbSet
时,不要忘记删除.As(),因为没有这样的接口,.As()将失败。添加和删除的设置将按预期工作。对于EFCore,从上下文派生新的TestDatabaseContext并将其设置为内存数据库可能更简单。public TestDatabaseContext(DbContextOptions选项):base(options){}public static DbContextOptions GetDBOptions(){return new DbContextOptions builder().UseInMemoryDatabase(databaseName:Guid.NewGuid().ToString()).enableSensitiveDataloging().options;}正如一些人在类似文章中指出的,设置InMemoryDb将一个依赖项替换为另一个依赖项,使其更像是一个集成测试。这当然是真的。但最终,您需要编写涵盖业务逻辑的测试,并且需要一个难以置信的快速且易于使用的设置。因此,在这一点上,我推荐InMemory方法而不是DbSetMock,因为它类似于fast,并且没有.Include.thenClude等的扩展方法问题。
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);
mockProductSet.As<IQueryable<Product>>()
.Setup(m => m.GetEnumerator())
.Returns(() => productData.GetEnumerator());
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);