Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement FIND method of EF in Unit Test?

I have a Web API 2.0 project that I am unit testing. My controllers have a Unit of Work. The Unit of Work contains numerous Repositories for various DbSets. I have a Unity container in the Web API and I am using Moq in the test project. Within the various repositories, I use the Find method of Entity Framework to locate an entity based on it's key. Additionally, I am using Entity Framework 6.0.

Here is an very general example of the Unit of Work:

public class UnitOfWork
{
    private IUnityContainer _container;
    public IUnityContainer Container
    {
        get
        {
            return _container ?? UnityConfig.GetConfiguredContainer();
        }
    }

    private ApplicationDbContext _context;    
    public ApplicationDbContext Context
    {
        get { _context ?? Container.Resolve<ApplicationDbContext>();  }
    }

    private GenericRepository<ExampleModel> _exampleModelRepository;
    public GenericRepository<ExampleModel> ExampleModelRepository
    {
        get { _exampleModelRepository ?? 
            Container.Resolve<GenericRepository<ExampleModel>>(); }
    }

    //Numerous other repositories and some additional methods for saving
}

The problem I am running into is that I use the Find method for some of my LINQ queries in the repositories. Based on this article, MSDN: Testing with your own test doubles (EF6 onwards), I have to create a TestDbSet<ExampleModel> to test the Find method. I was thinking about customizing the code to something like this:

namespace TestingDemo 
{ 
    class TestDbSet : TestDbSet<TEntity> 
    { 
        public override TEntity Find(params object[] keyValues) 
        { 
            var id = (string)keyValues.Single(); 
            return this.SingleOrDefault(b => b.Id == id); 
        } 
    } 
}

I figured I would have to customize my code so that TEntity is a type of some base class that has an Id property. That's my theory, but I'm not sure this is the best way to handle this.

So I have two questions. Is the approach listed above valid? If not, what would be a better approach for overriding the Find method in the DbSet with the SingleOrDefault method? Also, this approach only really works if their is only one primary key. What if my model has a compound key of different types? I would assume I would have to handle those individually. Okay, that was three questions?

like image 940
Rogala Avatar asked Jun 25 '15 14:06

Rogala


People also ask

How do you unit test methods?

A typical unit test contains 3 phases: First, it initializes a small piece of an application it wants to test (also known as the system under test, or SUT), then it applies some stimulus to the system under test (usually by calling a method on it), and finally, it observes the resulting behavior.

How many types of methods we can test by using unit testing?

There are 2 types of Unit Testing: Manual, and Automated.


2 Answers

To expand on my comment earlier, I'll start with my proposed solution, and then explain why.

Your problem is this: your repositories have a dependency on DbSet<T>. You are unable to test your repositories effectively because they depend on DbSet<T>.Find(int[]), so you have decided to substitute your own variant of DbSet<T> called TestDbSet<T>. This is unnecessary; DbSet<T> implements IDbSet<T>. Using Moq, we can very cleanly create a stub implementation of this interface that returns a hard coded value.

class MyRepository
{
   public MyRepository(IDbSet<MyType> dbSet) 
   {
     this.dbSet = dbSet;
   }

   MyType FindEntity(int id)
   {
     return this.dbSet.Find(id);
   }
}

By switching the dependency from DbSet<T> to IDbSet<T>, the test now looks like this:

public void MyRepository_FindEntity_ReturnsExpectedEntity()
{
  var id = 5;
  var expectedEntity = new MyType();
  var dbSet = Mock.Of<IDbSet<MyType>>(set => set.Find(It.is<int>(id)) === expectedEntity));
  var repository = new MyRepository(dbSet);

  var result = repository.FindEntity(id);

  Assert.AreSame(expectedEntity, result);
}

There - a clean test that doesn't expose any implementation details or deal with nasty mocking of concrete classes and lets you substitute out your own version of IDbSet<MyType>.

On a side note, if you find yourself testing DbContext - don't. If you have to do that, your DbContext is too far up the stack and it will hurt if you ever try and move away from Entity Framework. Create an interface that exposes the functionality you need from DbContext and use that instead.

Note: I used Moq above. You can use any mocking framework, I just prefer Moq.

If your model has a compound key (or has the capability to have different types of keys), then things get a bit trickier. The way to solve that is to introduce your own interface. This interface should be consumed by your repositories, and the implementation should be an adapter to transform the key from your composite type into something that EF can deal with. You'd probably go with something like this:

interface IGenericDbSet<TKeyType, TObjectType>
{
  TObjectType Find(TKeyType keyType);
}

This would then translate under the hood in an implementation to something like:

class GenericDbSet<TKeyType,TObjectType> 
{        
  GenericDbSet(IDbSet<TObjectType> dbset)   
  {
    this.dbset = dbset;   
  }

  TObjectType Find(TKeyType key)   
  {
    // TODO: Convert key into something a regular dbset can understand
    return this.dbset(key);   
  } 
}
like image 179
Dan Avatar answered Sep 22 '22 21:09

Dan


I realise this is an old question, but after coming up against this issue myself when mocking data for unit tests I wrote this generic version of the 'Find' method that can be used in the TestDBSet implementation that is explained on msdn

Using this method means you dont have to create concrete types for each of your DbSets. One point to note is that this implementaion works if your entities have primary keys in one of the following forms (im sure you could modify to suite other forms easily):

  1. 'Id'
  2. 'ID'
  3. 'id'
  4. classname +'id'
  5. classname +'Id'
  6. classname + 'ID'

    public override T Find(params object[] keyValues)
    {
        ParameterExpression _ParamExp = Expression.Parameter(typeof(T), "a");
    
        Expression _BodyExp = null;
    
        Expression _Prop = null;
        Expression _Cons = null;
    
        PropertyInfo[] props = typeof(T).GetProperties();
        var typeName = typeof(T).Name.ToLower() + "id";
        var key = props.Where(p => (p.Name.ToLower().Equals("id")) || (p.Name.ToLower().Equals(typeName))).Single();
    
    
        _Prop = Expression.Property(_ParamExp, key.Name);
        _Cons = Expression.Constant(keyValues.Single(), key.PropertyType);
    
        _BodyExp = Expression.Equal(_Prop, _Cons);
    
        var _Lamba = Expression.Lambda<Func<T, Boolean>>(_BodyExp, new ParameterExpression[] { _ParamExp });
    
        return this.SingleOrDefault(_Lamba);
    
    }
    

Also from a performance point of view its not going to be as quick as the recommended method, but for my purposes its fine.

like image 27
Lee Dyche Avatar answered Sep 25 '22 21:09

Lee Dyche