Unit test with DbContext and Entity Framework Core

One of the most recurrent questions on the internet is how to create Unit Tests within Entity Framework when using DbContext.

As always, all the code is available on GitHub.

 

 

1 - Unit test or Integration tests in entity framework

In this post I am not going to go into whether we should create tests or not, obviously yes, I have explained it a thousand times already. At this point, I want to get into the differences we're going to see between unit tests and integration tests.

Let's start with integration tests. In my opinion, they should use real databases, real services, etc. You can save or simulate the response of third-party systems, but something basic like the database, we must use it, either running it in Docker or using a real one, but do NOT simulate in memory or anything alike.

For unit or component tests, this is where we simulate in memory, or create a fake, double, or moq, and that's where they are allowed. In the end, the idea of unit tests is to test minimal functionalities; it will depend on the complexity you want to include in your tests, whether to do minimal unit tests or component tests.

For me, a component test is a test that tests a complete functionality, working completely in memory, and therefore in our case, does not include the use of a real database.

 

 

2 - How to create unit tests with repository pattern

The first point I’ll go through quickly: if we use the repository pattern or unit of work, we can do mock, fake or stub of the interfaces. So there’s no mystery, you take moq, NSubstitute or FakeItEasy, and simulate the behavior of the interfaces.

 

 

3 - Mock DbContext of Entity Framework

If what we want is to mock the DbContext, it's a bit more complicated, but not much.

 

It will mainly depend on the library we want to use for mocks. If we are using moq, we must install moq.EntityFrameworkCore. If we are using NSubstitute, we will install MockQueryable.NSubstitute, and if using FakeItEasy, we will use the library MockQueryable.FakeItEasy. Moq also has MockQueryable.Moq.

For this example, I will use NSubstitute and therefore MockQueryable.NSubstitute.

 

First we have to think about the use case, which in our case is inserting a user. We saw this example in the past using unit of work, but as I say, here we will use DbContext directly:

public class InsertUserDbContext
{
    private readonly CursoEfContext _context;

    public InsertUserDbContext(CursoEfContext context)
    {
        _context = context;
    }

    public async Task<bool> Execute(int id)
    {
        User user = new User()
        {
            Email = $"{Guid.NewGuid()}@mail.com",
            UserName = $"id{id}"
        };

        List<Wokringexperience> workingExperiences = new List<Wokringexperience>()
        {
            new Wokringexperience()
            {
                User = user,
                Name = $"experience1 user {id}",
                Details = "details1",
                Environment = "environment"
            },
            new Wokringexperience()
            {
                User = user,
                Name = $"experience user {id}",
                Details = "details2",
                Environment = "environment"
            }
        };

        if (_context.Users.Any(a.UserName == user.UserName))
            return false;

        _ = await _context.Users.AddAsync(user);
        await _context.Wokringexperiences.AddRangeAsync(workingExperiences);
        _ = await _context.SaveChangesAsync();

        return true;
    }
}

 

Now we only need to create the test, where we are going to simulate the dbset. Before continuing, as I said, simulating a DbContext is a bit tricky, but this method will allow you to perfectly simulate the behavior of a DbSet with NSubstitute:

private static DbSet<T> FakeDbSet<T>(List<T> data) where T : class
{
    var _data = data.AsQueryable();
    var fakeDbSet = Substitute.For<DbSet<T>, IQueryable<T>>();
    ((IQueryable<T>)fakeDbSet).Provider.Returns(_data.Provider);
    ((IQueryable<T>)fakeDbSet).Expression.Returns(_data.Expression);
    ((IQueryable<T>)fakeDbSet).ElementType.Returns(_data.ElementType);
    ((IQueryable<T>)fakeDbSet).GetEnumerator().Returns(_data.GetEnumerator());

    fakeDbSet.AsQueryable().Returns(fakeDbSet);

    return fakeDbSet;
}

 

And now we just have to write the test, where we are using NSubstitute on the Dbset of the DbContext to return the one we have created.

[Fact]
public async Task WhenUsernameDoesNotExist_ThenUserInserted()
{
    CursoEfContext context = Substitute.For<CursoEfContext>(new DbContextOptions<CursoEfContext>());


    DbSet<User> userDbSet = FakeDbSet(new List<User>());
    context.Users.Returns(userDbSet);

    DbSet<Wokringexperience> dbSetWorkingExperiences = FakeDbSet(new List<Wokringexperience>());
    context.Wokringexperiences.Returns(dbSetWorkingExperiences);

    InsertUserDbContext subject = new InsertUserDbContext(context);

    var result = await subject.Execute(10);

    Assert.True(result);
}

As we see, it is very simple. Of course, if we include an element in one of the lists, it will be checked against the one we have inserted ourselves:

[Fact]
public async Task WhenUsernameDoesExist_ThenNotInserted()
{
    int id = 1;
    CursoEfContext context = Substitute.For<CursoEfContext>(new DbContextOptions<CursoEfContext>());


    DbSet<User> userDbSet = FakeDbSet(new List<User>() { new User() { UserName = $"id{id}" } });
    context.Users.Returns(userDbSet);

    InsertUserDbContext subject = new InsertUserDbContext(context);

    var result = await subject.Execute(id);

    Assert.False(result);
}

As we remember, the use case checks whether a username exists or not.

 

 

4 - In-memory DbContext for component testing

In the previous point, we saw how to mock our DbContext, but many times we simply want to simulate in memory. For this, in our test we have to use the UseInMemoryDatabase method when defining the dbcontext:

private CursoEfContext GetInMemoryDbContext()
{
    DbContextOptions<CursoEfContext> databaseOptions = new DbContextOptionsBuilder<CursoEfContext>()
        .UseInMemoryDatabase("CursoDatabase")
        .Options;
    
    return new CursoEfContext(databaseOptions);
}

With this, we greatly simplify the functionality since it's a single instruction and it works perfectly. Moreover, this in-memory database is very common and highly recommended to use if you do component tests that include the database or are using API tests with testserver, since it works perfectly.

 

Now let's just write the same tests we created in the previous point to see the simplicity I'm talking about:

[Fact]
public async Task WhenUsernameDoesNotExist_ThenUserInsertedInDatabase()
{
    CursoEfContext context = GetInMemoryDbContext();
    InsertUserDbContext subject = new InsertUserDbContext(context);

    var result = await subject.Execute(10);

    Assert.True(result);
}

As we see, we are not simulating any behavior and everything works perfectly.

Of course, we can create CRUD operations in that DbContext in the test itself:

[Fact]
public async Task WhenUsernameDoesExist_ThenNotInsertedInDatabase() //inMemory
{
    int id = 1;
    CursoEfContext context = GetInMemoryDbContext();
    await context.Users.AddAsync(new User() { UserName = $"id{id}", Email = "[email protected]"});
    await context.SaveChangesAsync();

    InsertUserDbContext subject = new InsertUserDbContext(context);
    var result = await subject.Execute(id);

    Assert.False(result);
}

Before finishing, and I want to make this clear, here we are using an in-memory database, which means that we are not hitting a real database. So if we are using functionality that does not exist in the in-memory database, but does exist in Entity Framework, our tests will pass but not in production.

 

For this reason, we MUST ALWAYS do integration tests for each of our happy paths.

This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café