Unit test an Entity Framework generic repository using Moq
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.
NKD
Updated on June 05, 2022Comments
-
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 alwaysnull
. As you can see in the code below, I have mocked up theDbSet
and the context. I have also setup the mocked context to return mockedDbSet
. TheEnityRepository
constructor takes the mocked context as expected, butthis.dbSet = context.Set<T>();
isn't picking up my mockedDbSet
. 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 almost 8 yearsthanks 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 almost 8 yearsFrom 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 almost 8 yearsIt'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 almost 8 yearslol. 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 over 3 yearsWhat is the benefit of asserting
productRepositoryMock.Verify();
from the view of the SUT/ProductService? Doesn't this test implementation details; and, therefore hampers refactoring?