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
- Generate your data layer with the Reverse POCO Generator so you get the context, its interface and the
FakeDbContext. - Depend on the interface (e.g.
INorthwindDbContext) in your repositories and services, not the concrete context. - Register the real context for the interface in your app's DI container.
- In tests, new up the
FakeDbContextand seed itsDbSets with plain objects. - 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.