6

I decided to start writing unit tests in our application. It uses Entity Framework with a repository pattern.

Now I want to start testing logic classes which are using the repositories. I provide a simple example here.

Three of my methods in the class GenericRepository:

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 returning distinct years from a calendar table in descending order (yes I know the word 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();
    }
}

And here is my first test:

[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.");


    }
}

This is working fine. But this is not actually what I want to do. Since I have to setup the method Find on the mock object I also need to know how it is going to be called in my logic class. If I would like to do TDD I don't want to mind about this. All I want to know is which Calendar entities my repository should provide. I would like to setup the GetQuery method. Like 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());

So when Find is calling GetQuery internally in the GenericRepository class it should get the correct Calendar entities that I setup in GetQuery. But this is not working of course. Since I haven't setup the Find method of my mock object I don't get any entities.

So what to do? Of course I could probably use Moles or some other framework which mocks everything but I don't want to do that. Is there anything I can do in the design of the class or test to solve the issue?

It is not the end of the world if I have to go with my current solution but what if the property year turns into a not nullable int? Then of course I will have to change my implementation in the logic class but I would also have to change the test. I would like to try to avoid this.

Adam Plocher
  • 13,994
  • 6
  • 46
  • 79
John
  • 2,043
  • 5
  • 28
  • 49
  • 1
    I think one way to do it is having 'GetDistinctYearsFromCalendar' be dependent on 'IRepository'. That way you can mock out the 'GenericRepository' an test 'GetDistinctYearsFromCalendar' isolated. See http://en.wikipedia.org/wiki/Dependency_injection for more details. – Michael Sep 02 '11 at 10:52
  • Okay, I didn't see the internal ctor. Do you have tests and production code in the same project? I think the right way to do this is doing dependency injection all the way through and skip the default ctor. – Michael Sep 02 '11 at 10:55
  • I realise I could just write my logic as: return _repository.GetQuery().Where(c => c.Year.HasValue).Select(c => c.Year.Value).Distinct().OrderBy(c => c).Reverse().ToArray(); No I keep the tests in another project but expose internals to the test project with assembly directives. But what if I never use another type than GenericRepository? Is it necessary to do dependency injection all the way then? That seems to be the best solution right? I was to blind and wanted to use all the functionality of the repository. – John Sep 02 '11 at 10:56
  • 1
    This repository cannot be reliably unit tested so mocking doesn't make sense. You must run integration tests against real database: http://stackoverflow.com/questions/6904139/fake-dbcontext-of-entity-framework-4-1-to-test/6904479#6904479 – Ladislav Mrnka Sep 02 '11 at 11:28
  • If I hide the GetQuery method with private so it's only used by the class itself and then mock Find method which returns IEnumerable then everything is fine, right? So that the DAL layer never returns a type of IQueryable. – John Sep 06 '11 at 08:35

2 Answers2

12

I can see two ways:

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's the easiest and my preferred way. Moq isn't the hammer for everything ;)

Alternatively, if you really insist on using Moq (I'm flattered, but it's very much unnecessary in this case, as you can do state based testing on the returned entities), you can do:

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

And then use Moq to override the behavior of GetQuery:

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

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

What will happen is that the Find method will get executed on the GenericRepository class, which will in turn the GetQuery, which has been overwritten by Moq to provide the fixed set of entities.

I set CallBase = true explicitly just in case you happen to make Find virtual too, so that we ensure it's always called. Not technically needed if the Find isn't virtual, as it will always be invoked on the actual class the mock is inheriting/mocking from.

I'd go for the first option, much simpler to understand what's going on, and it can be reused outside of the context of a single particular test (just pass any entities you need and it will work for everything).

kzu
  • 1,795
  • 15
  • 16
  • Since I understood that mocking a method returning IQueryable was not testable because you have to run a integration test with the database I wonder if it is possible to mock the method Find and have it return my entities no matter what arguments I pass? I tried with It.IsAny<> but it didn't work. – John Sep 06 '11 at 08:57
0

Recently, a new tool called Effort has come out for EF 6+ that I found to be tremendously helpful for unit testing against a fake DB. See http://effort.codeplex.com/wikipage?title=Tutorials&referringTitle=Home.

Add it by using this package manager console command:

PM> Install-Package Effort.EF6

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

Then update your DbContext to add two new parameterized constructors:

    /// 
    /// 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;
    }

Add a constructor that takes the interface to your repository:

    private IAdventureWorksDbContext _dbContext;

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

Then add an interface to your unit testing project and associated class:

public interface ITestDatabase : IDisposable
{
    IAdventureWorksDbContext CreateContext();

    void Dispose(IAdventureWorksDbContext context);
}

Add some fake data to your unit testing project:

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 setup your unit testing class:

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

Where EffortDatabaseStrategy is:

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

For full details, please see http://www.codeproject.com/Articles/460175/Two-strategies-for-testing-Entity-Framework-Effort?msg=5122027#xx5122027xx.

user8128167
  • 6,929
  • 6
  • 66
  • 79