Rogala Rogala - 1 month ago 17
C# Question

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?

Answer

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