使用 Moq 对 Entity Framework 通用存储库进行单元测试
Unit test an Entity Framework generic repository using Moq
Question/Problem
我无法通过测试,因为 通用存储库 class this.dbSet = context.Set<T>();
始终是 null
。正如您在下面的代码中看到的,我模拟了 DbSet
和上下文。我还将模拟上下文设置为 return 模拟 DbSet
。 EnityRepository
构造函数按预期获取模拟上下文,但 this.dbSet = context.Set<T>();
没有获取我的模拟 DbSet
。我不确定我做错了什么。我嘲讽的方式不对吗?
结构:
- DAL - entity framework,通用存储库,工作单元
- BLL -
服务,automapper(映射实体生成 classes/objects 到
业务对象)
- 接口 -
IService
- 模型 - 业务对象
- Web - ASP.NET MVC
- 测试 - 单元测试
通用存储库
public class EntityRepository<T> : IEntityRepository<T> where T : class
{
internal MyDB_Entities context;
internal DbSet<T> dbSet;
public EntityRepository(MyDB_Entities context)
{
this.context = context;
this.dbSet = context.Set<T>();
}
public virtual T GetByID(object id)
{
return dbSet.Find(id);
}
// more code
}
通用存储库的接口
public interface IEntityRepository<T> where T : class
{
IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "");
T GetByID(object id);
// more code
}
工作单元
public class UnitOfWork : IUnitOfWork, IDisposable
{
MyDB_Entities _context;
public IEntityRepository<Customer> customerRepository { get; set; }
public IEntityRepository<Product> productRepository { get; set; }
public UnitOfWork(MyDB_Entities context)
{
_context = context;
customerRepository = new EntityRepository<Customer>(_context);
productRepository = new EntityRepository<Product>(_context);
}
public void Save()
{
_context.SaveChanges();
}
// more code
}
工作单元接口
public interface IUnitOfWork
{
IEntityRepository<Customer> customerRepository { get; set; }
IEntityRepository<Product> productRepository { get; set; }
void Dispose();
void Save();
}
服务
public class SomeService : ISomeService
{
readonly IUnitOfWork _unitOfWork;
public SomeService (IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
// DoSomethingMethod
}
服务接口
public interface ISomeService
{
// IDoSomethingMethod
}
分机
public static class MockDBSetExtension
{
public static void SetSource<T>(this Mock<DbSet<T>> mockSet, IList<T> source) where T : class
{
var data = source.AsQueryable();
mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
}
}
测试Class
[TestClass]
public class My_Test
{
Mock<DbSet<Product>> _mockProductDBSet;
Mock<MyDB_Entities> mockContext;
[TestInitialize]
public void TestInitialize()
{
_mockProductDBSet = new Mock<DbSet<Product>>();
mockContext = new Mock<MyDB_Entities>();
mockContext.Setup(s => s.Products).Returns(_mockProductDBSet.Object);
}
[TestMethod]
public void TestMocking()
{
var prod = new Product() { ProductName= "AAA", ProductID = 1 };
_mockProductDBSet.SetSource(new List<Product> { prod });
// more code here (new up the service, then test the service method, etc)
}
}
假设您有一个 IProuctService
定义为
public interface IProductService {
string GetProductName(int productId);
}
其中具体实现取决于IUnitOfWork
public class ProductService : IProductService {
readonly IUnitOfWork _unitOfWork;
public ProductService(IUnitOfWork unitOfWork) {
_unitOfWork = unitOfWork;
}
public string GetProductName(int productId) {
var item = _unitOfWork.productRepository.GetByID(productId);
if (item != null) {
return item.ProductName;
}
throw new ArgumentException("Invalid product id");
}
}
如果被测方法是IProductService.GetProductName
,这里有一个可以做的测试例子
[TestMethod]
public void ProductService_Given_Product_Id_Should_Get_Product_Name() {
//Arrange
var productId = 1;
var expected = "AAA";
var product = new Product() { ProductName = expected, ProductID = productId };
var productRepositoryMock = new Mock<IEntityRepository<Product>>();
productRepositoryMock.Setup(m => m.GetByID(productId)).Returns(product).Verifiable();
var unitOfWorkMock = new Mock<IUnitOfWork>();
unitOfWorkMock.Setup(m => m.productRepository).Returns(productRepositoryMock.Object);
IProductService sut = new ProductService(unitOfWorkMock.Object);
//Act
var actual = sut.GetProductName(productId);
//Assert
productRepositoryMock.Verify();//verify that GetByID was called based on setup.
Assert.IsNotNull(actual);//assert that a result was returned
Assert.AreEqual(expected, actual);//assert that actual result was as expected
}
在这种情况下,不需要模拟 DbSet 或 DbContext,因为 SUT 不需要依赖接口的实现。它们可以被模拟以供被测系统使用。
Question/Problem
我无法通过测试,因为 通用存储库 class this.dbSet = context.Set<T>();
始终是 null
。正如您在下面的代码中看到的,我模拟了 DbSet
和上下文。我还将模拟上下文设置为 return 模拟 DbSet
。 EnityRepository
构造函数按预期获取模拟上下文,但 this.dbSet = context.Set<T>();
没有获取我的模拟 DbSet
。我不确定我做错了什么。我嘲讽的方式不对吗?
结构:
- DAL - entity framework,通用存储库,工作单元
- BLL - 服务,automapper(映射实体生成 classes/objects 到 业务对象)
- 接口 -
IService
- 模型 - 业务对象
- Web - ASP.NET MVC
- 测试 - 单元测试
通用存储库
public class EntityRepository<T> : IEntityRepository<T> where T : class
{
internal MyDB_Entities context;
internal DbSet<T> dbSet;
public EntityRepository(MyDB_Entities context)
{
this.context = context;
this.dbSet = context.Set<T>();
}
public virtual T GetByID(object id)
{
return dbSet.Find(id);
}
// more code
}
通用存储库的接口
public interface IEntityRepository<T> where T : class
{
IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "");
T GetByID(object id);
// more code
}
工作单元
public class UnitOfWork : IUnitOfWork, IDisposable
{
MyDB_Entities _context;
public IEntityRepository<Customer> customerRepository { get; set; }
public IEntityRepository<Product> productRepository { get; set; }
public UnitOfWork(MyDB_Entities context)
{
_context = context;
customerRepository = new EntityRepository<Customer>(_context);
productRepository = new EntityRepository<Product>(_context);
}
public void Save()
{
_context.SaveChanges();
}
// more code
}
工作单元接口
public interface IUnitOfWork
{
IEntityRepository<Customer> customerRepository { get; set; }
IEntityRepository<Product> productRepository { get; set; }
void Dispose();
void Save();
}
服务
public class SomeService : ISomeService
{
readonly IUnitOfWork _unitOfWork;
public SomeService (IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
// DoSomethingMethod
}
服务接口
public interface ISomeService
{
// IDoSomethingMethod
}
分机
public static class MockDBSetExtension
{
public static void SetSource<T>(this Mock<DbSet<T>> mockSet, IList<T> source) where T : class
{
var data = source.AsQueryable();
mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
}
}
测试Class
[TestClass]
public class My_Test
{
Mock<DbSet<Product>> _mockProductDBSet;
Mock<MyDB_Entities> mockContext;
[TestInitialize]
public void TestInitialize()
{
_mockProductDBSet = new Mock<DbSet<Product>>();
mockContext = new Mock<MyDB_Entities>();
mockContext.Setup(s => s.Products).Returns(_mockProductDBSet.Object);
}
[TestMethod]
public void TestMocking()
{
var prod = new Product() { ProductName= "AAA", ProductID = 1 };
_mockProductDBSet.SetSource(new List<Product> { prod });
// more code here (new up the service, then test the service method, etc)
}
}
假设您有一个 IProuctService
定义为
public interface IProductService {
string GetProductName(int productId);
}
其中具体实现取决于IUnitOfWork
public class ProductService : IProductService {
readonly IUnitOfWork _unitOfWork;
public ProductService(IUnitOfWork unitOfWork) {
_unitOfWork = unitOfWork;
}
public string GetProductName(int productId) {
var item = _unitOfWork.productRepository.GetByID(productId);
if (item != null) {
return item.ProductName;
}
throw new ArgumentException("Invalid product id");
}
}
如果被测方法是IProductService.GetProductName
,这里有一个可以做的测试例子
[TestMethod]
public void ProductService_Given_Product_Id_Should_Get_Product_Name() {
//Arrange
var productId = 1;
var expected = "AAA";
var product = new Product() { ProductName = expected, ProductID = productId };
var productRepositoryMock = new Mock<IEntityRepository<Product>>();
productRepositoryMock.Setup(m => m.GetByID(productId)).Returns(product).Verifiable();
var unitOfWorkMock = new Mock<IUnitOfWork>();
unitOfWorkMock.Setup(m => m.productRepository).Returns(productRepositoryMock.Object);
IProductService sut = new ProductService(unitOfWorkMock.Object);
//Act
var actual = sut.GetProductName(productId);
//Assert
productRepositoryMock.Verify();//verify that GetByID was called based on setup.
Assert.IsNotNull(actual);//assert that a result was returned
Assert.AreEqual(expected, actual);//assert that actual result was as expected
}
在这种情况下,不需要模拟 DbSet 或 DbContext,因为 SUT 不需要依赖接口的实现。它们可以被模拟以供被测系统使用。