Skip to content

Rules for tests using EF Core

Jon P Smith edited this page Nov 9, 2021 · 9 revisions

There are some rules to follow when when accessing a database via EF Core in xUnit test. This page explains each rule and how EF Core or EfCore.TestSupport can allow you to follow the five rules.

1. You need a unique database within a test class

By default, xUnit will run each class containing tests in parallel, and every test method in a test class is then run in series. That means if you used the same database for all of your test, then many tests will be accessing / changing the database at the same time, which will make it impossible to check that the test works! There are two solutions:

  • Use an in-memory database, SqliteInMemory.CreateOptions<TContext> - see Using SQLite in-memory databases
  • Use one of EfCore.TestSupport's methods that returns a database name that contains the class type name on the end. This means the test class has database that is unique to its test class. See
    • SQL Server see ???
    • PostgreSQL see ???
    • Cosmos DB see ???

2. You need an empty database at the start of each test AND 3, the database schema matches the current EF Core Model

I have combined rules 2 and 3 because they are fixed by the same EfCore.TestSupport features, but here are the reasons:

  • Rule 2 is there because it much harder to write tests that don't care what the database contains. EF Core and EfCore.TestSupport provides ways to clear out all the rows in your tables (and more).
  • Rule 3 is there because if you change any of your entity classes or the EF Core configuration, then the current database schema (e.g. tables, views, function etc) will be out of date and most likely not work with your new . EF Core and EfCore.TestSupport provides wasy to delete the current schema and recreated the schema using EF Core's current Model of the database.

There are two ways to implement rule 2 and 3:

  • Call EF Core EnsuredDeleted method and then call EnsuredCreated - see the test TestEnsureDeletedEnsureCreatedOk in the TestSqlServerHelpers test class.
  • For SQL Server and PostgreSQL the EnsuredDeleted + EnsuredCreated approach is a bit slow. Therefore EfCore.TestSupport has a method called EnsureClean which is quicker - see the test TestSqlDatabaseEnsureCleanOk in the TestSqlServerHelpers test class.

NOTE: SqliteInMemory databases are empty by default and EnsuredCreated will set up the correct schema.

4. You must make sure your database test match your real-world usage

A automatic test

By default EF Core will track all the entities that were read or written to the database. That's perfect for your application, but can hide errors in the database code you are testing. For instance you might write some data into the database in the SETUP, but that tracked data can cover over problems in your ATTEMPT, e.g. in SETUP you write a Book class with collection of Review classes. In the ATTEMPT stage you that Book and you forget the .Include(book => book.Reviews). Because of the tracked entities the Book will have the Reviews collection filled, when in real use the Reviews collection wouldn't be filled.

There are a few ways to fix this, but adding the following line of code context.ChangeTracker.Clear() at the end of the SETUP and ATTEMPT stage will stop this issue.

NOTE: See this section in the "Changes in EfCore.TestSupport 5" article for a deeper explanation.

5. Your DbContext needs a constructor taking in DbContextOptions<YourDbContext>

You need a way to provide the database options to your DbContext. This is done via a constructor in your DbContext which has a DbContextOptions<YourDbContext> options parameter, which is normal for ASP.NET Core - see this example.

If you are using the OnConfiguring approach you need add if (!optionsBuilder.IsConfigured) into your OnConfiguring method - see this example.

Clone this wiki locally