Using Generic Test Classes and EntityFramework test doubles to facilitate Unit Testing

This is some kind of follow-up to my previous posts regarding generic Odata controllers and decoupled domain managers.

If you have followed my posts Using Dependency Injection with StructureMap to decouple business logic from WebApi OData Controllers and Simplifying OData Controllers by using generic controller classes you will remember that we use managers that contain the business logic and odata controllers only to expose that logic via Odata REST.

All of these managers base their operations on a common BaseEntity which contains properties that are useful for all entities (such as Name, CreatedById). The individual domain managers derive from a generic BaseManager. There we perform most of the data access operations which are internally based on EntityFramework.

Until recently we used (the commercial version of) Telerik JustMock for its ability to not only mock virtual methods but also being able to mock objects such as DbContext, DbSet (requiring an active CLR profiler which turns out to be quite slow). And when writing unit tests for our domain managers we actually had a lot of code nearly identical code in our unit tests across all managers (for common operations such as Create, Read, Update, Delete).

So we tried to streamline our approach by introducing two things:

  1. using in-memory test doubles for our DbSets
  2. creating a generic manager base class for common unit tests

We used the in-memory test doubles as described (very well) in Entity Framework Testing with Your Own Test Doubles and added some logic for our environment (mimicking some data base features such as generating unique and fresh ids upon insert). With that we could easily attach a DbSet via JustMock on to an existing DbContext (not requiring the profiler to be active):

Db = Mock.Create<ApcDbContext>();
var DbSet = new DbSetTDataAccess<TDataAccess>();
Mock.Arrange(() => Db.Set<TDataAccess>())

The second part involved writing a test manager base class that would accept the generic parameters we would need for concrete testing of our managers. As our managers would use an internal data access model (TDataAccess : DataAccessBase) and return a public model (TEntity : BaseEntity) we needed the following signature on our test class:

public class BaseEntityManagerTestT<TEntity, TDataAccess, TManager>
  where TEntity : BaseEntity, new()
  where TDataAccess : DataAccessBase, new()
  where TManager : BaseEntityManager<TEntity, TDataAccess>, new()
  // ...

However as the unit test runner does not support running these classes we had to wrap this class into concrete test classes like this:

public partial class ItemManagerTest 
  : BaseEntityManagerTestT
  // ...

The ItemManagerStub was needed, as our managers do not have a public parameter-less constructor and we cannot use them as a generic type argument. Therefore we created the stub for these managers:

public class ItemManagerStub : ItemManager
  public ItemManagerStub()
    : base(default(ApcDbContext), default(AccessManager))
    throw new NotImplementedException();

  public ItemManagerStub
    ApcDbContext dbContext, 
    IAccessManager accessManager
    : base(dbContext, accessManager)
    // N/A

As this stub class only acts as a placeholder so we can use it as a type argument, it does not matter that the constructor would instantly throw an exception (it is never called anyway). The actual manager will be created in the base manager tests class and there we use the correct constructor with arguments for dbContext and our accessManager:

Activator.CreateInstance(typeof(TManager), null, new AccessManager());

Note: using Activator is necessary as new TManager(null, new AccessManager()) does not work with arguments. The compiler raises the following error when trying to do this:

CS0417  'TManager': 
cannot provide arguments when creating an instance of a variable type

We could also have used our IoC container (we use StructureMap) for getting an instance, but we did not want to have this active on our test projects.

This is all it takes to define a common set of tests across all domain managers, so we can focus on writing tests regarding the specific logic of the domain we are currently working on. And as a benefit, our tests run much faster as we do not need the profiler to be active any more.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: