How to unit Test Entity Framework

So, now we have the picked the best ORM for our purposes, chances are, you want to test it. We all test our code all of the time with close to 100% coverage, right?

Yea, that's what I thought.

Throughout the years, I've found that most developers don't test their code thoroughly because a handful of reasons:

  • The tests written are too brittle and break with every new feature.
  • It's too tedious to try to and mock out every little thing
  • Its just too difficult; things are abstracted out way too much nowadays and I cant get them to pass

 You know, they're not entirely wrong; I've seen code written to perform CRUD on a table that could only be described as 'awful'. Firstly, the amount of code to do that simple operation was just absurd. Secondly, the repository pattern needs to go; it just doesn't make any sense to me to try to anticipate every possible request that's needed and create a member for it (read over-complicating things). Thirdly, the degree of indirection in the code made it feel like it was trying to do something illegal and trying to shake the cops before doing it.

The worst part though, is that this was using Entity Framework! How can you take something that's so simple, testable and tune-able and 'abstract' it out beyond recognition? Sometimes you need to stop and ask yourself why you're mocking a SqlConnection. Is that the best use of your time?

In case you haven't answered that last question yet, the answer is "No, not its not the best use of your time". 

Nonetheless, these things exist, and testing them is just awful. Unfortunately, somewhere down the line, managers, architects, bloggers and peers made that kind of code not only mainstream, but sometimes, even required.

So, back to the topic of this post, how to best test your code that uses Entity Framework? Well, lets take a look at some code and go over it step by step:

Mark Your DbSets as virtual


public class AdventureWorksContext : DbContext
   {
       public virtual DbSet<Product> Products { getset; }
 
       public virtual DbSetransactionHistory> TransactionHistories {get;set;}


I hope this is common practice, but if not, here is why its important: They can be mocked/faked/overridden. That is paramount to testing your context. If you cant break that dependency to an underlying database, you cant test the context.

Create a way to pass in your DbContext

You can read that as make the context injectable. There are a ton of ways to do that, some people prefer Constructor Injection, others Property Injection and some like using Service Factories. I am a fan of the Factory pattern in general since I can get everything done without any additional 'frameworks' or other unknown performance bottlenecks

public static Func<DbConnectionboolAdventureWorksContext> FactoryInjectedContext =
            (connection, ownsConnection) => new AdventureWorksContext(connection, ownsConnection);
or...

public SomeDal(AdventureWorksContext constructorInjectedContext)
       {
           _adventureWorksContext = constructorInjectedContext;
       }

Mock your DbContext and DbSets and Pass them in

Now the fun begins, time to mock the DbContext and Sets. Mocking the DbContext is trivial and you really wont need to set up anything on it except for the Save methods if you'd like to verify they were called.

            _mockConext = new Mock<TContext>();
            _mockConext.Setup(x => x.SaveChanges()).Returns(() =>
            {
                var copy = _changeCount;
                _changeCount = 0;
                return copy;
            });
 
            _mockConext.Setup(x => x.SaveChangesAsync()).Returns(() =>
            {
                var copy = _changeCount;
                _changeCount = 0;
                return Task.FromResult(copy);
            });

As far as the DbSet, well, that's a bit more involved, but stay with me, its actually pretty simple. The DbSet implements an IQueryable, this is because they are intended to be used with some sort of query provider to translate Expressions to some other specific query language. Obviously, in the default case, its a SQL query provider, translating your LINQ statements to SQL statements.

Well, guess what else has a query provider? LINQ to Objects! How do you get that? Take a simple list and call the .AsQueryable() extension on it! Now its pretty straight forward to mock the members of the IQueryable with those from our list.

            Mock<DbSet<TEntity>> _innerMock = new Mock<DbSet<TEntity>>();
            if (seed == null)
                seed = new List<TEntity>();
 
            var queryableSeed = seed.AsQueryable();
 
            _innerMock.As<IDbAsyncEnumerable<TEntity>>()
                .Setup(m => m.GetAsyncEnumerator())
                .Returns(new TestDbAsyncEnumerator<TEntity>(queryableSeed.GetEnumerator()));
 
            _innerMock.As<IQueryable<TEntity>>()
                .Setup(m => m.Provider)
                .Returns(new TestDbAsyncQueryProvider<TEntity>(queryableSeed.Provider));
 
            _innerMock.As<IQueryable<TEntity>>().Setup(m => m.Expression).Returns(queryableSeed.Expression);
            _innerMock.As<IQueryable<TEntity>>().Setup(m => m.ElementType).Returns(queryableSeed.ElementType);
            _innerMock.As<IQueryable<TEntity>>()
                .Setup(m => m.GetEnumerator())
                .Returns(() => queryableSeed.GetEnumerator());
 
            _innerMock.Setup(x => x.Add(It.IsAny<TEntity>())).Returns<TEntity>(e =>
            {
                _changeCount++;
                seed.Add(e);
                queryableSeed = seed.AsQueryable();
                return e;
            });
            _innerMock.Setup(x => x.AddRange(It.IsAny<IEnumerable<TEntity>>())).Returns<IEnumerable<TEntity>>(e =>
            {
                _changeCount += e.Count();
                seed.AddRange(e);
                queryableSeed = seed.AsQueryable();
                return e;
            });


But what about all those bad@$$ asynchronous methods that make our DALs seem to be able to process twice what it used to? Well, I'm not going to pretend to have come up with this, but I can help explain it:
Your Expression will get translated to a SQL query using your query provider when you execute the enumerable. Now depending on which method you choose, synchronous or async, a different IQueryable enumerator is executed. By default, LINQ to Objects don't have an async option that required, but the EF team created a class that allows us to fake it and pass it in. (Its a good read, have a look)

Get Testing

That's it! You're ready to create tests that you can initialize either from an empty list, or create a predetermined one with your test data to verify.

            var mockedContext = new InjectableMockedContext<AdventureWorksContext>();
 
            mockedContext.MockEntity(x => x.Products); // No seed to mock empty db
 
            var classUnderTest = new SomeDal(mockedContext.MockedContext.Object);

What You Wont Get

Since you aren't using the actual query provider, you wont get all of the features of that query provider. that basically means you wont get lazy loading features. That means it will not follow navigation properties and it will not just load entities if you've defined their children. So, if you give it a list of Posts and each has Comments, you will get back a Post with its Comments.

Shameless Plug


This is pretty standard cookie-cutter stuff. I use it in my own code and hate having to rewrite the same stuff over and over... So, I put it in a nuget package called "Entity Framework Testing Helpers".
Pull it down and give it a shot if you'd like or fork the repo and submit a Pull Request.

Install-Package EntityFramework.Testing.Helpers


Cautionary Last Words

Remember, when you Mock something, you aren't fully testing the functionality. Similarly, when you Mock a DbContext, you aren't testing the quality of the generated query. Furthermore, you shouldn't be testing the query provider, what that means is don't try test that if you send in an actual Expression in it will be executed properly. That's what the EF teams has to worry about. What you should worry about is your code surrounding the context and is most probably building that Expression.

Happy testing and let me know your thoughts!




Comments

Popular posts from this blog

//TODO: Tackling Technical Debt

The Yin and Yang of software