Testing Web API OData controllers

Microsoft's Entity Framework is an occasionally handy ORM framework that allows you to query database entities using LINQ and to easily add, update and delete entities via a variety of helper classes.

Recently I wrote a set of WebAPI controllers to expose an Entity Framework model as OData endpoints. Exposing an Entity Framework model as OData using WebAPI is incredibly easy. However, when it came to testing my controllers I found that mocking Entity Framework is tricky, due to a combination of erratic use of interfaces and the apparent complexity of IQueryable<T>.

Luckily my controllers only used a few parts of the EF API, so I decided to write some lightweight wrappers to make testing easier. This had the bonus of also removing dependencies on EF from my controllers.

OData controller structure

My original OData controllers all inherited from a common base class which was a tweaked version of the implementation given in this ASP.net article on creating ODAta services using WebAPI. (This is very similar to the EntitySetController<TEntity, TKey> defined in System.Web.Http.OData assembly, but somewhat simpler - you can find information about that type here.)

The common base class has two generic parameters, one specifying the type of the entity and another specifying the type of the entity's key. It accepted a DerivedContext in its constructor and used the Set<T> method of DbContext to get a reference to the set of entities it was exposing as OData.

private readonly DerivedContext _context;
private readonly DbSet<TEntity> _set;

public EntityControllerBase(DerivedContext context) {
  _context = context;
  _set = context.Set<TEntity>();
}

You can see the full source of the controller here. Looking over the controller's actions we can see that to test it we need to be able to mock the following:

  1. DbContext.SaveChanges()
  2. DbContext.Set<T>()
  3. DbSet<T>.Find(Object[])
  4. DbSet<T>.Add(T)
  5. DbSet<T>.Remove(T)
  6. The IQueryable<T> members of DbSet<T>

We can now define two simple interfaces that will allow us to mock this functionality and remove our controller's dependency on EF types.

public interface IBasicSet<T> : IQueryable<T> where T : class {
  T Add(T entity);
  T Remove(T entity);
  T Find(params Object[] keyValues);  
}

public interface IBasicContext : IDisposable {
  int SaveChanges();
  IBasicSet<T> GetSet<T>() where T : class;
}

Interface implementations

Both of these interfaces are easy to implement. First, we can simply wrap DbSet<T> for IBasicSet<T>.

public class BasicSet<T> : IBasicSet<T> where T : class {

  private readonly IDbSet<T> _wrapped;

  public BasicSet(IDbSet<T> wrapped) {
    _wrapped = wrapped;
  }

  public T Add(T entity) {
    return _wrapped.Add(entity);
  }

  // And so on for the other members of IBasicSet<T>
}

Then, add an extension method to IDbSet<T> for ease of use.

public static IBasicSet<T> Wrap(this IDbSet<T> set) where T : class {
  return new BasicSet<T>(set);
}

As DerivedContext inherits from DbContext it already has implementations for Dispose and SaveChanges, so when implementing IBasicContext the only member we need to explicitly implement is GetSet<T>, which is trivial:

public class DerivedContext : DbContext, IBasicContext {

  public IBasicSet<T> GetSet<T>() where T : class {
    return this.Set<T>().Wrap();
  }

  // Snip...
}

Now our controllers can be updated to use these interfaces instead of DerivedContext and DbSet<T>:

private readonly IBasicContext _context;
private readonly IBasicSet<TEntity> _set;

public EntityControllerBase(IBasicContext context) {
  _context = context;
  _set = context.GetSet<TEntity>();
}

As our new interfaces expose methods with the same signatures as those that the controller was using previously this is the only change required. The full source of the controllers after refactoring can be seen here.

Testing the controllers

For most scenarios testing our controllers now involves creating a few simple mocks - for example here I use Moq to create mocks of IBasicContext and IBasicSet<T> to test that entities are added to the correct set in the controller's Post action.

[Fact]
public void entity_is_added_to_set() {

    var set = new Mock<IBasicSet<Entity>>();

    var context = new Mock<IBasicContext>();
    context.Setup(x => x.GetSet<Entity>()).Returns(set.Object);

    var entity = new Entity();
    var controller = Helpers.CreateController(context.Object);

    controller.Post(entity);

    set.Verify(x => x.Add(entity), Times.Once());
}

You can see all of the controller tests here.

Some tests require slightly more complicated mocks and are worth discussing.

Mocking IQueryable<T>

At some point in your controller tests you will want to check that the data returned is correct, and to do this you will need to provide your controller with a mock of IBasicSet<T> with a working implementation of IQueryable<T>. Thankfully the vast majority of the functionality of IQueryable<T> is provided via extension methods, and the interface itself is actually very simple, consisting of 3 properties.

The easiest way I've found of mocking IQueryable<T> is to create a simple collection that also implements IQueryable<T> and then use its ElementType, Expression and Provider properties in my mock.

var entities = new List<Entity>();
entities.Add(new Entity() { Id = 1 });
entities.Add(new Entity() { Id = 2 });

var queryable = entities.AsQueryable();

var set = new Mock<IBasicSet<Entity>>();
set.Setup(x => x.ElementType).Returns(queryable.ElementType);
set.Setup(x => x.Expression).Returns(queryable.Expression);
set.Setup(x => x.Provider).Returns(queryable.Provider);

This gives your mock of IBasicSet<T> a working implementation of IQueryable<T> (essentially pointing at the collection you borrowed the 3 properties from). It has the added benefit of allowing you to manipulate the contents of the IBasicSet<T> during the test, meaning you can simulate concurrency issues.

[Fact]
public void concurrency_exception_returns_not_found_if_entity_no_longer_exists() {

  var entities = new List<Entity>(new Entity[] {
    new Entity() { Id = 1 }
  });

  var context = Helpers.CreateContext(entities);

  context.Setup(x => x.SaveChanges()).Callback(() => {

    //Simulate another process deleting the entity by removing it
    //from the list used as the basis for the DbSet.
    entities.Clear();

    //Now tell the controller there was a concurrency error.
    throw new DbUpdateConcurrencyException();
  });

  var controller = Helpers.CreateController(context.Object);

  var result = controller.Patch(1, new Delta<Entity>());

  Assert.NotNull(result);
  Assert.IsType<NotFoundResult>(result);
}

In this test we want to test that the controller correctly handles the case where an entity is deleted by another process while we are in the process of updating it. We create a collection of entities and then when SaveChanges is called we remove everything from the collection and throw an exception. To the controller it will appear as if the entity that it was attempting to update has been deleted by another process.

Testing Delta<T>

Figuring out how Delta<T> worked too me a while.

I wanted to be able to test that entities were being correctly updated in the controller's Patch action - unfortunately the Patch method of Delta<T> is not virtual so frameworks like Moq cannot override or verify its behaviour.

Instead I started looking at simply constructing an instance of Delta<T>, populating it with changes and then checking the state of the entity to verify that the update had been performed. It was some time before I realised that Delta<T> is created using dynamic by WebAPI and updated properties are "tacked on" rather than there being methods to add changes to the delta. This is best illustrated by an example:

var entity = new Entity() { Id = 1, Value = "X" };

dynamic delta = new Delta<Entity>();
delta.Value = "Y"; //Dynamic property, Delta<T> does not contain a Value property

delta.Patch(entity);

Console.WriteLine(entity.Value); //Y

This makes testing quite easy, if nothing else.

[Fact]
public void entity_is_patched() {

  var entities = new Entity[] { 
    new Entity() { Id = 1, Name = "Original" }
  };

  var controller = Helpers.CreateController(entities);

  dynamic delta = new Delta<Entity>();
  delta.Name = "Updated";

  var result = controller.Patch(1, delta);

  Assert.Equal("Updated", _entities[0].Name);
}

Conclusion

In this post I've shown that it's possible to take a simple Entity Framework / Web API OData service controller and make it testable by creating some simple wrappers for the EF components. If your project is a simple database-as-OData affair then this allows you to get high test coverage without having to introduce too many new layers or abstractions on top of the basic controller structure set out in the original ASP.Net article.

You can see the full source code for the before and after versions of the controller as well as the tests in this GitHub repository.

Comments