【问题标题】:Unit test an Entity Framework generic repository using Moq使用 Moq 对实体框架通用存储库进行单元测试
【发布时间】:2016-06-15 18:55:40
【问题描述】:

问题/问题

我无法通过测试,因为 Generic Repositorythis.dbSet = context.Set<T>(); 始终为 null。正如您在下面的代码中看到的,我已经模拟了DbSet 和上下文。我还设置了模拟上下文以返回模拟DbSetEnityRepository 构造函数按预期采用模拟的上下文,但 this.dbSet = context.Set<T>(); 没有接受我的模拟 DbSet。我不确定我做错了什么。我不是在用正确的方式嘲笑这个吗?

结构:

  • DAL - 实体框架、通用存储库、工作单元
  • BLL - 服务,自动映射器(将实体生成的类/对象映射到 业务对象)
  • 接口 - 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());
    }
}

测试类

[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)
    }
}

【问题讨论】:

  • 被测系统是什么。因为根据您拥有的接口,如果被测系统仅引用接口,那么您无需模拟 DbSet 或 DbContext。它们没有被各自的接口公开。
  • 在这一点上,我只是在测试之前尝试看看我是否可以模拟假数据/记录。我可以在服务类中放置一个方法调用作为我的 sut 和模型 IUnitOfWork 然后对其进行测试。这在理论上是可以做到的,我现在不太担心。我更担心能够模拟一些假记录......这似乎失败了=(
  • 澄清一下 - 我的目标只是能够从产品存储库中调用 GetByID(1) 并能够看到我创建的假记录。您如何建议在不模拟上下文和数据库集的情况下进行测试?

标签: c# entity-framework unit-testing repository moq


【解决方案1】:

假设您有一个 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 不需要依赖接口的实现。它们可以被模拟以供被测系统使用。

【讨论】:

  • 感谢您对此进行调查。这个解决方案肯定达到了目标。我计划在 TestInitialize() 中模拟 DbSet 和 DbContext 并最终移动 var prod = new Product() { ProductName= "AAA", ProductID = 1 };和 _mockProductDBSet.SetSource(new List { prod }); TestInitialize() 也是如此。这样,我不必重复我的代码来为每个测试创建一个假产品。如您所见,我为此目的编写了扩展代码和 TestInitialize()。
  • 从你所说的......我认为这是一种错误的单元测试方式?我应该在任何时候嘲笑 dbset / dbcontext 吗?当我们要模拟 dbset 和 dbcontext 时,有没有好的场景?
  • 这不是错的,只是你应该尽量不要模拟你无法控制的类和你也不拥有的类。微软会一直测试这些类,直到王国来临。您可能只在实际调用数据库时才需要它们。
  • 哈哈。我听到了。我想就减少在每次测试中重复创建假冒产品而言,我将只使用全局变量并添加 product = new Product() { ... };在 TestInitialize() 中重用每个测试。不必模拟整个 dbcontext 和 dbset。谢谢你的时间!
  • 从 SUT/ProductService 的角度断言 productRepositoryMock.Verify(); 有什么好处?这不是测试实现细节吗?并因此阻碍重构?
猜你喜欢
  • 2012-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多