Click here to Skip to main content
13,731,369 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

4.4K views
7 bookmarked
Posted 7 Dec 2017
Licenced CPOL

Using FakeItEasy with Entity Framework 6

, 7 Dec 2017
Rate this:
Please Sign up or sign in to vote.
How to use FakeItEasy with Entity Framework 6

What This Article is About

I have quickly become a fan of FakeItEasy. If you are not familiar with it, visit https://fakeiteasy.github.io/ and check out the super fluent interface yourself.

I couldn’t resist looking for a way to quickly fake out EF6 and I am going to share how that went.

What I Will Not Be Discussing

  • The differences between mocks, stubs and so on (see Gerard Meszaro‘s list for that)
  • The usefulness of mocking ORMs
  • The limitations of in-memory test doubles for a database

Background – Testing with EF5

In EF5 (before my time!), we would create a mock DbSet class that implemented IDbSet, with a whole load of properties and methods. Then, create an interface for a DbContext and implement that with a mocked context. I did this following the instructions here and it generated quite a lot of code to maintain for my relatively small project.

Changes in EF6

In EF6, DbSet gained an additional protected internal constructor. So, instead of creating all those mocks, we mark the DbSet properties virtual, allowing FakeItEasy to override them:

public virtual DbSet<Product> Products { get; set; }

Working with the DbSet Methods

This is fairly straightforward and requires the behaviour of any DbSet method called to be defined.

Create instances of a fake context and fake DbSet, arrange the behaviour for calls to the Products getter and calls to the Add method on the DbSet:

var fakeContext = A.Fake<ApplicationDbContext>();
var fakeDbSet = A.Fake<DbSet<Product>>();
A.CallTo(() => mockContext.Products).Returns(fakeDbSet);
A.CallTo(() => fakeDbSet.Add(A<Product>.Ignored)).Returns(fakeProduct);

Why not setup calls directly to the methods given as we need to define their behaviour individually?

A.CallTo(() => fakeContext.Products.Add(A<Product>.Ignored)).Returns(fakeProduct);

We can but I prefer the first way as it has clearer logic and keeps things consistent with the way we can work with LINQ extension methods below. Opinions?

Note that there is no difference in arrangement between DbSet’s asynchronous and non-asynchronous methods.

Working with the LINQ Extension Methods (Non-asynchronously)

So what if the method under test is using LINQ extension methods on the DbSet?

_dbContext.Products.Where(p => p.Id == 2).Select(d => new...

On the face of it, these are tidier to work with because after setting up your fake DbSet, LINQ-to-Objects kicks in and there is no need to identify the behaviour of individual methods. We will take advantage of DbSet implementing IQueryable:

public class DbSet<TEntity> : DbQuery<TEntity>, IDbSet<TEntity>,
        IQueryable<TEntity>, IEnumerable<TEntity>, IQueryable, IEnumerable
where TEntity : class

For our in-memory storage, we will use a List that implements IQueryable, which allows us to return its properties and method to use in the fake DbSet.

IQueryable<Product> fakeIQueryable =
      new List<Product>().AsQueryable();

Next, we setup the fake DbSet. Note that FakeItEasy needs to be told explicitly to implement IQueryable in order for the Castle proxy to intercept.

var fakeDbSet = A.Fake<DbSet<Product>>( (d => d
    .Implements(typeof(IQueryable<Product>)));

To setup the behaviour for the DbSet under LINQ methods, we can ‘redirect’ all calls to the method and properties of the IQueryable interface to the fakeIQueryable.

A.CallTo(() => ((IQueryable<Product>)fakeDbSet).GetEnumerator())
  .Returns(fakeIQueryable .GetEnumerator());
A.CallTo(() => ((IQueryable<Product>)fakeDbSet).Provider)
  .Returns(fakeIQueryable .Provider);
A.CallTo(() => ((IQueryable<Product>)fakeDbSet).Expression)
  .Returns(fakeIQueryable .Expression);
A.CallTo(() => ((IQueryable<Product>)fakeDbSet).ElementType)
  .Returns(fakeIQueryable .ElementType);

Finally, we setup the fake context and its behaviour when the Products DbSet getter is called; then go ahead and instantiate the object under test, passing it the fake context and a dummy to the Get method.

var fakeContext = A.Fake<ApplicationDbContext>();
A.CallTo(() => fakeContext.Products).Returns(fakeDbSet);
var productRepository = new ProductRepository(fakeContext);
var results = productRepository.Get(A<int>.Ignored);

Working with the LINQ Extension Methods (Asynchronously)

Problem 1: What if we used a similar test arrangement for a test method that calls an asynchronous LINQ extension method? e.g.

await _dbContext.Products.SingleOrDefaultAsync(e => e.Id == id);

We get an error: The provider for the source IQueryable does not implement IDbAsyncQueryProvider

So here, we must change our behaviour for the Provider property. I have made use of a class provided by MSDN that wraps up our fakeIQueryable to provide an implementation of IDbAsyncQueryProvider:

await _dbContext.Products.SingleOrDefaultAsync(e => e.Id == id);
                A.CallTo(() => ((IQueryable<Product>)fakeDbSet).Provider)
               .Returns(new TestDbAsyncQueryProvider<Product>( fakeIQueryable.Provider));

Problem 2: What if we want to enumerate through the DbSet asynchronously?

IEnumerable<Product> products = await _dbContext.Products.ToListAsync();

We’ll get a different error message, and the solution here is to use another class provided in the link above to wrap up our fakeIQueryable to implement IDbAsyncEnumerator, replacing the original call to GetEnumerator form earlier.

A.CallTo(() => ((IDbAsyncEnumerable<Product>)fakeDbSet).GetAsyncEnumerator())
         .Returns(new TestDbAsyncEnumerator<Product>(mockIQueryable.GetEnumerator()));

FakeItEasy now also needs to be told to implement IDbAsyncEnumerable.

var fakeDbSet = A.Fake<DbSet<Product>>(d => d
  .Implements(typeof(IDbAsyncEnumerable<Product>))
  .Implements(typeof(IQueryable<Product>)));

Validation

I did not have time to look at FakeItEasy and EF’s validation in depth.

I briefly considered one scenario where we might be relying on EF for our data validation, catching any DbEntityValidationException. We could quite easily check how our code handles validation errors by throwing a DbEntityValidationException, e.g.

A.CallTo(() => mockContext.SaveChangesAsync()).Throws(new DbEntityValidationException());

Then construct an IEnumerable of DbEntityValidationResult. However, this is just theory and I have not tried this myself yet!

Summary

If you are already using FakeItEasy in your tests, it is quite nice for consistency to be able to use it with Entity Framework without having to maintain mock code for DbContext and DbSet.

There are obvious limitations to how much behaviour can be faked this way. I did say I would not be debating the merits of mocking an ORM but here are just two issues with mocking EF:

  • The differences in behaviour between LINQ-to-Objects and LINQ-to-Entities are many – there is a good Stackoverlow.com answer here detailing some of these
  • Mocking the behaviour of EF’s validation would likely be unmanageable

I would be interested in hearing others’ experiences of scenarios where it has been useful to mock EF.

Other Options

There are a few other efforts (get the reference?) out there to provide support for testing EF but they are often poorly maintained. Highway.Data looks interesting but I have not tried it yet (link).

.NET Core now has an InMemory provider for testing (link).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

BenHall_io
Software Developer (Senior)
United Kingdom United Kingdom
Left an enjoyable career teaching Computer Science & programming in 2016 and started out in software development.

Have an unhealthy obsession with .NET / C# internals.

Currently an Expert .NET Software Engineer at the United Kingdom Hydrographic Office.

C# / ASP.NET
NServiceBus
SQL Server
JavaScript, jQuery etc…
PowerShell + DSC

You may also be interested in...

Pro
Pro

Comments and Discussions

 
GeneralWill try... Pin
BenHall_io8-Dec-17 8:48
professionalBenHall_io8-Dec-17 8:48 
Questionplease upload the full code Pin
Mou_kol7-Dec-17 23:51
memberMou_kol7-Dec-17 23:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01-2016 | 2.8.180920.1 | Last Updated 8 Dec 2017
Article Copyright 2017 by BenHall_io
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid