Testing Entity Framework context in-memory

I’m a great believer in having your tests as integrated and as “end-to-end” as possible, avoiding mocks on anything that does not cross a port or boundary, ala https://blogs.msdn.microsoft.com/ericgu/2014/12/01/unit-test-success-using-ports-adapters-and-simulators/

One thing I have always found was that this became tricky when it came to mocking out your data access, particularly with EF. You can quite easily abstract away your data access layer and that’s absolutely fine. However, I always found I lost something doing this, especially if a test encompasses that  more than one bit of database access – as you start dealing in fake data too often. Not to mention if you anything more funky than simple read/writes in the layer you’re abstracting then this isn’t going to be covered.

Entity Framework 7 or core are providing in memory versions of your collections which is great for this: https://prashantbrall.wordpress.com/2015/08/31/entity-framework-7-in-memory-testing/ so look into that if you’re on that version.

I am, however, still using EF6. Some third parties exists to do this, like Effort: https://github.com/tamasflamich/effort which works nicely. I like to have a wee bit more control over the mocked instance and have started creating my own library for mocking your context.





I found I like to deal with the context in the same way I would any other mocked service/object etc and so I decided to create variations of the library for various popular mocking frameworks. To date, just Rhino.Mocks and Moq. Let’s say we are using MockEF.Rhino…  Then the library will use Rhino to create its mock of the context. I.e. An in-memory collection version of the context will be created by Rhino’s MockRepository, and thus any functions you like to use of Rhino still work – this also has the added benefit of being able to mock anything that exists on your context’s interface – so if you have a bunch of methods you’ve created – theses are just mocked in the same way you always would.

Getting Started

To set up your context mock, simply call:

var context = new ContextBuilder<IMyContext>()
      .Setup(x => x.Authors, new List<Author> { new Author { Id = 1, Name = "bob" }})
      .Setup(x => x.Books)

The type argument of Context builder must be an interface. Then to use this in your code, you can set up a context factory, like so:

public interface IFactory
  IMyContext Create();

public class Factory : IFactory
  public IMyFactory Create()
    return new MyContext();

//class that needs to use the context
public class Example
  private IFactory _factory;
  public Example(IFactory factory)
    _factory = factory;

  public bool MethodThatUsesContext()
    //Important. Your context interface MUST implement IDisposable. 
    //Firstly because this won't work otherwise, Secondonly because - you should anyway.
    using (var context = _factory.Create())
      //Use context here.
    return true;

//When testing you can then stub your factory to return the context you have set up, like so:
var factory = MockRepository.GenerateMock<IFactory>();
factory.Stub(x => x.Create()).Return(myMockedContext); //context built using Rhino.MockEF.
var result = new Example(factory).MethodThatUsesContext();

//When running the code not in test, in you DI registrations you can use something like:
container.Register<IFactory, Factory>();

Current Limitations

Currently, if you have any calls to .Find(..) this will only look for fields with the [PrimaryKey] attribute – if this is set up as something else or setup elsewhere -it’s likely to fail.

.SaveChanges() or .SaveChangesAsync() will pretty much do nothing. Any adds or updates are automatically applied to the in memory collection. Which is not the same behaviour as real world. A good example of this is that the Attach(..) and Add(..) methods do the same thing.

To be Done

Plan to add more mocking frameworks.

Extension methods on libraries. Rather than manually creating a ContextBuilder – hide the methods behind an extension applied to the mocking framework’s library.

Test support for versions < EF6