11

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)
    }
}
3
  • What is the system under test. Because based on the interfaces you have, if system under test is only referencing the interfaces then you have no need to mock DbSet or DbContext. They are not being exposed by their respective interfaces. Commented Jun 16, 2016 at 0:03
  • At this point, I only try to see if I can mock up fake data/records before testing. I can possibly put a method call in the service class as my sut and mockup IUnitOfWork then test it. This is theoretically can be done which I'm not too worry at the moment. I am more concern of being able to mockup some fake records... which appearantly failed =( Commented Jun 16, 2016 at 0:19
  • To clarify - my goal is just to be able to call GetByID(1) from the Product repository and able to see my fake record created. How do you suggest testing this without mocking the context and dbset? Commented Jun 16, 2016 at 0:28

1 Answer 1

15

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.

Sign up to request clarification or add additional context in comments.

5 Comments

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.
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?
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.
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!!
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?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.