How to Unit Test Entity Framework Core Without a Database

Code that depends on an Entity Framework Core DbContext is notoriously fiddly to unit test. You either wrestle with mocking DbSet and IQueryable, or you spin up a database-like provider that behaves differently from your real one. This guide compares the common approaches and shows how a generated FakeDbContext lets you test your repositories and services with no database, no provider and no mocking framework.

Why testing a DbContext is hard

A unit test should be fast, isolated and deterministic - no network, no shared database, no leftover state from the last run. But a DbContext exists precisely to talk to a database, so the moment your repository or service depends on one, your "unit" test wants to reach out to SQL Server. The goal is to swap that real context for a lightweight in-memory stand-in that behaves enough like the real thing for your logic tests.

Approach 1: mocking the DbContext

You can mock the context and its DbSets with a framework such as Moq, but it is more work than it looks. A DbSet implements IQueryable and IAsyncEnumerable, so to make Where, ToListAsync and FirstOrDefaultAsync behave you have to wire up query providers and async enumerators by hand. The result is brittle test plumbing that has nothing to do with the behaviour you are actually testing - and it breaks whenever the code under test calls a method you forgot to set up.

Approach 2: the EF Core InMemory provider

Microsoft ships Microsoft.EntityFrameworkCore.InMemory, which swaps your database for an in-memory store. It removes the mocking pain, but it comes with caveats: it is a separate dependency, it is not a relational database (no real SQL, no constraints, different transaction and concurrency behaviour), and Microsoft themselves advise against using it as a substitute for a relational provider - recommending SQLite in in-memory mode if you need relational-like behaviour. Either way you are configuring a provider and a connection just to test a method.

Approach 3: a generated FakeDbContext

When you generate your data layer with the EntityFramework Reverse POCO Generator, it produces an interface for your context (for example INorthwindDbContext) and a ready-made FakeDbContext that implements it with in-memory DbSets. Program against the interface, inject the real context in production and the fake one in tests:

  • No mocking framework - the fake is real, compiled code with working DbSets.
  • No extra NuGet package or provider to configure.
  • Always in sync - it is regenerated from your schema along with the rest of the model.
  • Fast - it is plain in-memory objects, so tests run at memory speed.

A worked example

Suppose a repository takes the context interface in its constructor. In the test you new up the fake context, seed it with plain objects, and exercise the repository - no database in sight:

[SetUp]
public void Setup()
{
    // Arrange: an in-memory context, no database connection
    _context = new FakeNorthwindDbContext();

    _context.Customers.AddRange(new List<Customer>
    {
        new Customer { CustomerId = "1", CompanyName = "abc" },
        new Customer { CustomerId = "2", CompanyName = "def" }
    });

    // The repository depends on the INorthwindDbContext interface,
    // so the fake drops straight in where the real context would go.
    _repository = new CustomersRepository(_context);
}

[Test]
public void GetAll_returns_all_seeded_customers()
{
    var count = _repository.GetAll().Count();

    Assert.AreEqual(2, count);
}

That is the whole setup: construct, seed, assert. No query-provider wiring, no provider registration, no connection string.

What a fake context does (and does not) test

Be honest with yourself about what this buys you. A fake context runs your LINQ as LINQ-to-Objects in memory, so it is perfect for testing your logic - repository methods, filtering, projections, service rules - quickly and deterministically. It does not execute real SQL, so it will not catch provider-translation errors, database constraints, collation or concurrency behaviour. The same is true of EF Core's InMemory provider - it also runs in memory, so neither approach catches a query that fails to translate to SQL.

There is one more difference worth understanding. The InMemory provider runs EF Core's real change tracker, so it performs identity resolution, relationship fix-up (set one end of a navigation property and EF populates the other) and store-generated key assignment on SaveChanges. A fake context backed by List<T> is deliberately simpler: it stores your objects, but it does not emulate that bookkeeping unless the generated code does so explicitly. So a FakeDbContext is lighter than the InMemory provider, not higher-fidelity - if the behaviour you are testing leans on EF fixing up keys or navigation properties for you, assert on it deliberately or cover it with an integration test.

Check the async operators once

A plain List<T> does not implement IAsyncEnumerable, so ToListAsync() and FirstOrDefaultAsync() only work against the fake if its DbSets wrap the data in an async-capable queryable. If your repositories use the async EF operators, write one async test against the fake up front to confirm it behaves - a 30-second check that saves a surprise later.

The pragmatic split

Use the FakeDbContext for the many fast unit tests that verify your code's behaviour, and keep a smaller set of integration tests against a real database for the things only a real provider can prove. Fast feedback for logic; real SQL for the edges.

Step-by-step

  1. Generate your data layer with the Reverse POCO Generator so you get the context, its interface and the FakeDbContext.
  2. Depend on the interface (e.g. INorthwindDbContext) in your repositories and services, not the concrete context.
  3. Register the real context for the interface in your app's DI container.
  4. In tests, new up the FakeDbContext and seed its DbSets with plain objects.
  5. Assert against your code's behaviour - fast, isolated, no database.

See the FakeDbContext page in the project wiki for details.

Frequently asked questions

How do I unit test EF Core without a database?

Depend on an interface for your DbContext and substitute an in-memory implementation in tests. The Reverse POCO Generator produces both the interface and a FakeDbContext for you, so you can seed data and test your logic with no database and no mocking.

Should I mock the DbContext?

You can, but mocking DbSet and IQueryable (including async operators) is verbose and brittle. A real in-memory fake that implements your context interface is simpler and less likely to break when the code under test changes.

Is the EF Core InMemory provider good for testing?

It is convenient, but it is not a relational database and behaves differently from SQL Server. Microsoft recommends against using it as a stand-in for a relational provider; SQLite in-memory mode is closer if you need relational behaviour. For testing your own logic, a generated fake context is lighter still.

How do I test a repository without hitting the database?

Have the repository take the context interface, then inject a FakeDbContext in the test, seed its DbSets, and assert on the repository's output - as shown in the example above.

Does a fake context replace integration tests?

No. A fake context runs LINQ in memory and is ideal for fast logic tests, but it does not run real SQL. Keep a smaller suite of integration tests against a real database to cover query translation, constraints and concurrency.

Test your data layer without a database

Generate a context interface and a ready-made FakeDbContext alongside your model, and write fast unit tests with no mocking.

View Pricing & Get Started