With Moq, unit test the EF Repository pattern.

entity-framework moq repository-pattern unit-testing

Question

I made the decision to begin developing unit tests for our application. It employs the repository pattern with Entity Framework.

I want to now begin testing the logic classes that use the repositories. Here, I provide a straightforward example.

I have three methods in the GenericRepository class.

public class GenericRepository : IRepository
{
    public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
    {
        var entityName = GetEntityName<TEntity>();
        return Context.CreateQuery<TEntity>(entityName);
    }
    private string GetEntityName<TEntity>() where TEntity : class
    {
        return typeof(TEntity).Name;
    }
    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return GetQuery<TEntity>().Where(predicate).AsEnumerable();
    }
}

A simple logic class that returns separate years in descending order from a calendar database (yes, I realize that the term "calendar" is misspelled in our code):

public class GetDistinctYearsFromCalendar
{
    private readonly IRepository _repository;

    public GetDistinctYearsFromCalendar()
    {
        _repository = new GenericRepository();
    }

    internal GetDistinctYearsFromCalendar(IRepository repository)
    {
        _repository = repository;
    }

    public int[] Get()
    {
        return _repository.Find<Calender_Tbl>(c => c.Year.HasValue).Select(c => c.Year.Value).Distinct().OrderBy(c => c).Reverse().ToArray();
    }
}

Here is my initial assessment:

[TestFixture]
public class GetDistinctYearsFromCalendarTest
{
    [Test]
    public void ReturnsDistinctDatesInCorrectOrder()
    {
        var repositoryMock = new Mock<IRepository>();

        repositoryMock.Setup(r => r.Find<Calender_Tbl>(c => c.Year.HasValue)).Returns(new List<Calender_Tbl>
        {
           new Calender_Tbl
              {
                  Date =
                      new DateTime(2010, 1, 1),
                  Year = 2010
              },
           new Calender_Tbl
              {
                  Date =
                      new DateTime(2010, 2, 1),
                  Year = 2010
              },
           new Calender_Tbl
              {
                  Date =
                      new DateTime(2011, 1, 1),
                  Year = 2011
              }
        }.AsQueryable());

        var getDistinct = new GetDistinctYearsFromCalendar(repositoryMock.Object).Get();

        Assert.AreEqual(2, getDistinct.Count(), "Returns more years than distinct.");
        Assert.AreEqual(2011, getDistinct[0], "Incorrect order, latest years not first.");
        Assert.AreEqual(2010, getDistinct[1], "Wrong year.");


    }
}

Everything is going well. However, this is not what I genuinely want to accomplish. I need to know how the function Find will be invoked in my logic class since I need to set it up on the fake object. I don't want to worry about this if I want to use TDD. I just need to know which Calendar entities my repository need to provide. I want to configure the GetQuery function. akin to this

repositoryMock.Setup(r => r.GetQuery<Calender_Tbl>()).Returns(new List<Calender_Tbl>
{
  new Calender_Tbl
      {
          Date =
              new DateTime(2010, 1, 1),
          Year = 2010
      },
  new Calender_Tbl
      {
          Date =
              new DateTime(2010, 2, 1),
          Year = 2010
      },
  new Calender_Tbl
      {
          Date =
              new DateTime(2011, 1, 1),
          Year = 2011
      }
}.AsQueryable());

As a result, the GenericRepository class' internal call to GetQuery by Find should return the right Calendar entities that I configured in GetQuery. However, this obviously isn't functioning. I don't obtain any entities since I haven't built up the Find function on my pretend object.

So what do you do? Of course, I could certainly use Moles or some other mocking framework, but I don't want to. Is there anything I can change about the way the course or exam is designed to address the problem?

If I had to stick with my existing approach, it won't be the end of the world, but what if the property year becomes a non nullable int? The test would also need to be changed, in addition to changing my implementation in the logic class. I want to make an effort to prevent this.

1
6
10/19/2014 5:58:08 AM

Accepted Answer

I see two options:

public class MockRepository : IRepository
{
    private List<object> entities;
    public MockRepository(params object[] entitites)
    {
      this.entities = entities.ToList();
    }

    public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
    {
        return this.entities.OfType<TEntity>().AsQueryable();
    }

    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return GetQuery<TEntity>().Where(predicate).AsEnumerable();
    }
}

That method is the simplest and my favorite. Not every problem can be solved using Moq;

In addition, you may do the following if you really insist on using Moq (I'm pleased, but it's unnecessary in this instance since you can do state-based testing on the returned entities):

public class GenericRepository : IRepository
{
    public virtual IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
    {
        var entityName = GetEntityName<TEntity>();
        return Context.CreateQuery<TEntity>(entityName);
    }
    private string GetEntityName<TEntity>() where TEntity : class
    {
        return typeof(TEntity).Name;
    }
    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return GetQuery<TEntity>().Where(predicate).AsEnumerable();
    }
}

Afterward, override GetQuery's behavior with Moq:

var repository = new Mock<GenericRepository> { CallBase = true };

repository.Setup(x => x.GetQuery<Foo>()).Returns(theFoos.AsQueryable());

The GenericRepository class's Find function will be called, which will then call the GetQuery, which Moq has modified to deliver the predetermined list of entities.

To guarantee that Find is always called in the event that you decide to make it virtual as well, I specifically set CallBase = true. Since the Find will always be used on the real class the mock is deriving from or mocking from if it is not virtual, it is not strictly required.

The first approach would be my preference since it is easier to comprehend what is happening and can be used to several tests (just pass any entities you need and it will work for everything).

12
9/2/2011 6:47:27 PM

Popular Answer

I just discovered a brand-new tool called Effort for EF 6+, and I found it to be quite useful for unit testing against a fake DB. Look at http://effort.codeplex.com/wikipage?title=Tutorials&referringTitle=Home.

Use the following package management console command to add it:

PM> Install-Package Effort.EF6

Then, if you are using the AdventureWorks database (see https://sql2012kitdb.codeplex.com/), build an interface for your DbContext:

the following two new parameterized constructors to your DbContext:

    /// 
    /// Create a new context based on database name or connection string.
    /// 
    /// Database name or connection string
    public AdventureWorksEntities(string nameOrConnectionString)
        : base(nameOrConnectionString)
    {
        this.Configuration.LazyLoadingEnabled = false;
    }

    public AdventureWorksEntities(DbConnection connection)
        : base(connection, true)
    {
        this.Configuration.LazyLoadingEnabled = false;
    }

A constructor that accepts the interface to your repository should be added:

    private IAdventureWorksDbContext _dbContext;

    public ProductRepository(IAdventureWorksDbContext dbContext)
    {
        dbContext.Configuration.AutoDetectChangesEnabled = false;
        this._dbContext = dbContext;
    }

Then, add an interface to the class and project you're using for unit testing:

public interface ITestDatabase : IDisposable
{
    IAdventureWorksDbContext CreateContext();

    void Dispose(IAdventureWorksDbContext context);
}

Add some bogus data to your project for unit testing:

public class ProductsTestData
{
    public static void AddTestData(IAdventureWorksDbContext dbContext)
    {
        dbContext.Products.Add(new Product() { Id = new Guid("23ab9e4e-138a-4223-bb42-1dd176d8583cB"), Name = "Product A", CreatedDate = DateTime.Now, Description = "Product description..." });
        dbContext.Products.Add(new Product() { Id = new Guid("97e1835f-4c1b-4b87-a514-4a17c019df00"), Name = "Product B", CreatedDate = DateTime.Now });
        dbContext.SaveChanges();
    }
}

Now set up your class for unit testing:

[TestClass]
public class ProductsTest
{
    private ITestDatabase _testDatabaseStrategy;
    private ProductRepository _productRepository;
    private IAdventureWorksDbContext _context;

    [TestInitialize]
    public void SetupTest()
    {
        // create the test strategy.  This will initialise a new database
        _testDatabaseStrategy = CreateTestStrategy();

        // add test data to the database instance
        using (_context = _testDatabaseStrategy.CreateContext())
        {
            ProductsTestData.AddTestData(_context);
            _context.SaveChanges();
        }

        // initialise the repository we are testing
        _context = _testDatabaseStrategy.CreateContext();
        _productRepository = new ProductRepository(_context);
    }

    protected ITestDatabase CreateTestStrategy()
    {
        return new EffortDatabaseStrategy();
    }

    [TestCleanup]
    public void CleanupTest()
    {
        // dispose of the database and connection
        _testDatabaseStrategy.Dispose(_context);
        _context = null;
    }

    [TestMethod]
    public void GetProductsByTagName()
    {
        IEnumerable<Product> products = _productRepository.GetProductsByTagName("Tag 1", false);
        Assert.AreEqual(1, products.Count());
    }

Location of EffortDatabaseStrategy:

public class EffortDatabaseStrategy : ITestDatabase
{
    public EffortDatabaseStrategy()
    {
    }

    private DbConnection _connection;

    public IAdventureWorksDbContext CreateContext()
    {
        if (_connection == null)
        {
            _connection = Effort.DbConnectionFactory.CreateTransient();
        }
        var context = new AdventureWorksDbContext(_connection);

        return context;
    }

    public void Dispose(IAdventureWorksDbContext context)
    {
        if (context != null)
        {
            context.Dispose();
        }
    }

    public void Dispose()
    {
    }
}

See http://www.codeproject.com/Articles/460175/Two-strategies-for-testing-Entity-Framework-Effort?msg=5122027#xx5122027xx for further information in its entirety.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow