100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
C#

Unit Testing with the In-Memory Provider

How to use EF Core's InMemory provider and SQLite in-memory mode to test data access code, and where each approach falls short.

Practical EF CoreIntermediate9 min readJul 10, 2026
Analogies

Testing Data Access Without a Real Database

The Microsoft.EntityFrameworkCore.InMemory package provides a database provider that stores entities in an in-memory collection instead of a real database, letting tests construct a DbContext quickly without any external dependency. It's commonly wired up in xUnit or NUnit tests by configuring UseInMemoryDatabase(databaseName) in the DbContextOptions, with a unique database name per test to prevent state leaking between tests that run in parallel. This makes it attractive for fast, isolated tests of repository or service classes that depend on a DbContext, since there's no connection string, container, or file system to manage in CI.

🏏

Cricket analogy: A batter practices technique against a bowling machine in the nets instead of facing a live match attack from Mitchell Starc, getting fast, repeatable reps without needing a full XI or stadium — the same convenience the InMemory provider gives a unit test.

csharp
public class OrderServiceTests
{
    private static AppDbContext CreateContext() =>
        new AppDbContext(new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options);

    [Fact]
    public async Task PlaceOrder_ReducesInventory()
    {
        using var context = CreateContext();
        context.Products.Add(new Product { Id = 1, Name = "Widget", Stock = 10 });
        await context.SaveChangesAsync();

        var service = new OrderService(context);
        await service.PlaceOrderAsync(productId: 1, quantity: 3);

        var product = await context.Products.FindAsync(1);
        Assert.Equal(7, product.Stock);
    }
}

Where the InMemory Provider Diverges from a Real Database

The InMemory provider does not enforce relational constraints the way a real database does — it silently ignores unique indexes, foreign key cascade behavior, required column constraints at the database level, and check constraints, so a test can pass against InMemory while the same code would throw a DbUpdateException against SQL Server. It also doesn't translate raw SQL, doesn't support many advanced LINQ translations the same way (some queries that would fail against a relational provider succeed silently in memory by falling back to client evaluation), and has no concept of transactions in the same sense a relational database does. Because of these gaps, Microsoft's own documentation recommends the InMemory provider only for simple crud-style unit tests, not as a substitute for integration testing against the real target database engine.

🏏

Cricket analogy: Net practice against a bowling machine can't replicate a reverse-swinging yorker from Shaheen Afridi under lights, so a batter who only trains in the nets can be caught out by conditions a real match introduces — the same gap between InMemory tests and a real database's constraints.

Because InMemory doesn't enforce unique constraints, foreign keys, or required fields at the storage level, tests that verify 'saving invalid data throws a DbUpdateException' will fail or behave unexpectedly against InMemory even though they'd correctly throw against SQL Server or PostgreSQL. Don't use InMemory to test constraint-violation behavior.

SQLite In-Memory Mode as a More Faithful Alternative

For tests that need closer-to-real relational behavior — actual foreign key enforcement, real SQL translation, and genuine transaction semantics — many teams use the Microsoft.EntityFrameworkCore.Sqlite provider pointed at an in-memory SQLite database (a connection string of 'DataSource=:memory:' with the connection kept open for the test's lifetime) instead of the InMemory provider. This runs real SQL through SQLite's engine, so LINQ queries go through the actual relational query pipeline and constraint violations throw the same kinds of exceptions production code would encounter, at the cost of being slightly slower to set up than InMemory and occasionally diverging from SQL Server or PostgreSQL syntax in edge cases like specific date functions or case sensitivity.

🏏

Cricket analogy: A day-night pink-ball practice match is a much closer simulation of real Test conditions than net practice, even though it's still not the real tournament, the same middle ground SQLite in-memory occupies between InMemory and a full production database.

For true confidence in production behavior, the most reliable approach is testing against the actual target database engine using a disposable container (for example, via Testcontainers) spun up in CI. InMemory and SQLite in-memory are good for fast unit-level feedback, but integration tests against the real engine should still exist for critical query paths.

  • The InMemory provider stores data in a plain in-memory collection and requires a unique database name per test to avoid state leakage.
  • InMemory does not enforce unique constraints, foreign keys, or required fields the way a real relational database does.
  • Some LINQ queries that fail against a real provider can silently succeed via client evaluation in InMemory.
  • SQLite's in-memory mode (DataSource=:memory:) runs real SQL through a relational engine, giving closer-to-production fidelity.
  • SQLite in-memory can still diverge from SQL Server/PostgreSQL in specific functions and case sensitivity.
  • Neither InMemory nor SQLite in-memory replaces integration tests against the real target database for critical paths.

Practice what you learned

Was this page helpful?

Topics covered

#EntityFrameworkCoreStudyNotes#MicrosoftTechnologies#UnitTestingWithTheInMemoryProvider#Unit#Testing#Memory#Provider#StudyNotes#SkillVeris#ExamPrep