Unit test an Entity Framework generic repository using Moq

16,650

Lets say you have a IProuctService defined as

public interface IProductService {
    string GetProductName(int productId);
}

where the concrete implementation depends on 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");
    }
}

If the method under test is IProductService.GetProductName, here is an example of test that can be done.

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

In this scenario there was no need to mock up DbSet or DbContext as the SUT had no need for the implementations of the dependent interfaces. They can be mocked to be used by the system under test.

Share:
16,650
NKD
Author by

NKD

Updated on June 05, 2022

Comments

  • NKD
    NKD almost 2 years

    Question/Problem

    I am not able to get a passing test because the Generic Repository class this.dbSet = context.Set<T>(); is always null. As you can see in the code below, I have mocked up the DbSet and the context. I have also setup the mocked context to return mocked DbSet. The EnityRepository constructor takes the mocked context as expected, but this.dbSet = context.Set<T>(); isn't picking up my mocked DbSet. I'm not sure what I did wrong. Am I not mocking this the right way?

    Structure:

    • DAL - entity framework, generic repository, unit of work
    • BLL - service, automapper (mapping entity generated classes/objects to business objects)
    • Interface - IService
    • Model - business objects
    • Web - ASP.NET MVC
    • Test - unit test

    Generic Repository

    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
    }
    

    Interface for Generic Repository

    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
    }
    

    Unit of Work

    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
    }
    

    Interface for Unit Of Work

    public interface IUnitOfWork
    {
        IEntityRepository<Customer> customerRepository { get; set; }
        IEntityRepository<Product> productRepository { get; set; }
        void Dispose();
        void Save();
    }
    

    Service

    public class SomeService : ISomeService 
    {
        readonly IUnitOfWork _unitOfWork;
        public SomeService (IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }
        // DoSomethingMethod
    }
    

    Interface for Service

    public interface ISomeService
    {
        // IDoSomethingMethod 
    }
    

    Extension

    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());
        }
    }
    

    Test 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)
        }
    }
    
  • NKD
    NKD almost 8 years
    thanks for looking into this. This solution definitely achieves the goal. I plan to mock DbSet and DbContext in the TestInitialize() and eventually move var prod = new Product() { ProductName= "AAA", ProductID = 1 }; and _mockProductDBSet.SetSource(new List<Product> { prod }); to TestInitialize() as well. This way, I don't have to repeat my code creating a fake product for every single test. As you can see I wrote the extension code and TestInitialize() for this purpose.
  • NKD
    NKD almost 8 years
    From what you are saying... I take it as this a wrong way to Unit Test? Should I at no time mocking dbset / dbcontext? Is there a good scenario when we want to mock dbset and dbcontext?
  • Nkosi
    Nkosi almost 8 years
    It's not that it is wrong it's just that you should try not to mock classes you have no control over and ones that yo also don't own. Microsoft would have tested the those classes already till kingdom come. you would probably only need them when you are making actual calls to the database.
  • NKD
    NKD almost 8 years
    lol. I hear ya. I guess as far as reducing the repeated creation of a fake product in every test, I'll just have global variable and add product = new Product() { ... }; in the TestInitialize() to reuse on every test. Having to mockup the whole dbcontext and dbset are unnecessary. Thank you for you time!!
  • officer
    officer over 3 years
    What is the benefit of asserting productRepositoryMock.Verify(); from the view of the SUT/ProductService? Doesn't this test implementation details; and, therefore hampers refactoring?