Unit Testing EF Core Code
Unit Testing EF Core Code

.NET EF Core – Unit Testing EF Core Repositories

I already have discussed briefly about InMemory database provider for EF Core in one of the past articles. That article was a small step by step guide to explain how InMemory provider can be used. This article is kind of an extension to the previous article.

In this article, we are going to have look at which options are available for unit testing the EF Core repositories and how can we decide which option is better for our project. The article concludes with a demo of unit testing using InMemory provider.

What are available options ?

First of all, let’s have a look at what all options we have when it comes to unit testing the code which uses EF core. There are various approaches that can be used:

  • In Memory provider, which creates an in memory database. No need to mock anything. While creating DbContext instance, use this provider and that’s it.
  • Sqlite provider is also another choice. Similar to previous option, it would perform all the operations on Sqlite database. It would create a file in the working directory which would contain the database data.
  • Use LocalDb and do not mock calls to database. This would perform all the operations on the LocalDb file.
  • Use Moq to mock the DbContext (or mock it using any other mocking framework). Common methods like AddAsync, SaveChangesAsync or Get calls can be mocked.

Which is the best option ?

First three options are different kinds of providers and hence they make the task of using DbContext in unit tests a bit easy. No need to setup mocks.

However, like other EF Core data providers, every provider has its limitations. For example, In Memory provider does not support transactions or raw SQL. LocalDb or Sqlite provider may not support all the features that are supposed to be supported by the actual database engine running on production environment.

I would not personally choose fourth option, because it needs extra work – of setting mocks. When the project is in its early stage, it may feel easy to setup mocks. But, with time, the project may start using advanced features from EF Core and mocking those features in Unit Tests might be a very time-consuming and tedious task (if not impossible).

That simply means, no option is “best” essentially. Every option has its own advantages and limitations.

Then, which one should we use ?

This question could be tricky to answer. You will have to see which EF Core features are you using (or going to use, in future) in your repositories. Then select the appropriate approach which supports most of the features.

In some cases, you may want to use combination of some of the options mentioned above. For example, if you are using raw SQL query for one repository, then you can use In-Memory provider for all other repositories, while some other provider like LocalDb or Sqlite for the repository which uses raw SQL.

Other important thing to note is – when you start on a project, you may not know beforehand, which features of EF Core are you going to use. Hence, the choice of mocking / using appropriate provider should be evaulated after every regular intervals to ensure that the selected option is still well suited.

Ok ! That’s enough of text ! Let’s move to the code now.

Overview of Steps

We are using Moq (or similar framework) since very long. It’s easy, we decide the logic to be tested. Then we mock the dependencies. On the mocks, we can setup the the expectations, telling what should be returned if the dependency is called.

If any other provider is used for unit testing, then below is the list of activities to be performed:

  • Instantiation of DbContext using the intended provider.
  • Setup the data. This means inserting the data in the DbSets and then calling SaveChangesAsync.
  • Mock other dependencies using mocking framework (if there are any).
  • Then call the logic which needs to be tested
  • Assert the returned values to verify correctness

Example: Repository

Let’s create a C# .NET Core class library using Visual Studio. Add references of below mentioned packages to it:

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.Design
  • Microsoft.EntityFrameworkCore.SqlServer

Then, add two entities – AuthorEntity and PostEntity as shown in below code. For this demo, we are going to assume that an author can have zero or more posts published.

Let’s also add a BlogDbContext which has two DbSets – Authors and Posts.

Let’s add a new class – AuthorRepository– which performs CRUD operations on these entities as shown below.

Example: Tests

So, now let’s create tests for this repository. Let’s create an xUnit Test project in Visual Studio for this purpose and add a new file AuthorRepositoryTests.cs and add the code given below in it.

There are two important things to note in this class:

  • The DbContextOptions instance is created using UseInMemoryDatabase
  • The database name here is using current date time value to create a unique database name. (why ?)
  • Every test then creates a BlogDbContext instance using options instance created in first step. This is, in turn, used to create the repository instance.

For this demo, I am going to create tests only for success scenarios. The failure scenarios are not covered. But ideally, it would be easy to cover failure scenarios too, you just need to provide invalid inputs, and no need to change any mocks.

As you can see, the above code does not really use mocks. It just sets up the context and then we call the methods from repository as we would have called them from the application code.

Wrapping Up

We have discussed about various options which can be used for unit testing EF core repositories. We have seen a high level overview of the thought process that can be applied to select the appropriate option. We also saw a demo of how InMemory provider can be used for unit testing.

I hope you find this information helpful. Let me know your thoughts.

Leave a ReplyCancel reply

This Post Has 5 Comments

  1. James

    I recently needed to change one of our tables to utilize Full Text searching, as the users needed to search internal sub-strings. What was less than 50 total lines of change in code, completely borked the unit and integration tests that were based on in memory, or localdb. Neither of which support full text. In memory doesn’t care about inserting identity values, but sql server sure does. Even worse when you’re attempting to insert into multiple tables.

    I would highly recommend ensuring all your tests run against your production service provider (sql server in my case) as well as your standard test provider

    1. Manoj Choudhari
      Manoj Choudhari

      Thanks James for your response ! IMO, Unit testing need not cross process boundaries. Integration tests can be written to ensure that data access layer works with database.

  2. jan4711 – Nederland
    jan4711

    “The database name here is using current date time value to create a unique database name. (why ?)”
    Tell us why…

    1. Manoj Choudhari
      Manoj Choudhari

      I would suggest to try it out once and let me know what happens ! 😉

      1. jan4711 – Nederland
        jan4711

        Okay…. thanks for asking me to try it. I learnt something new about inMemoryDatabase. 🙂