DEV Community

François Borgies
François Borgies

Posted on

EF Core - Tester efficacement vos Repositories avec xUnit et les tests InMemory.

Dans cet article je vous montre comment tester efficacement vos repositories avec des tests unitaires InMemory. je recours régulièrement à cette méthode qui donne d'excellents résultats. Il serait dommage de passer à côté ! Cette méthode, simple et efficace, permet de simplifier les tests et d'éviter des effets de bords indésirables.

Prenons comme exemple la classe User et son repository avec les méthodes de création et de récupération :

public class User(){
    public int Id { get; set; }
    public string Lastname { get; set; }
    public string Firstname { get; set; }
}

public interface IUserRepository(){
    public User Get(int userId);
    public int Post(User user);
}

public class UserRepository(){
    private MyDbContext _context;
    public UserRepository(MyDbContext context){
        _context = context;
    }

    public User Get(int userId){
        return _context.Users.SingleOrDefault(u => u.Id == userId);
    }

    public int Create(User user){
        _context.Users.Add(user);
        _context.SaveChanges();
        return user.Id;
    }
}
Enter fullscreen mode Exit fullscreen mode

Si on souhaite tester unitairement les deux méthodes de ce Repository, il faut Mocker le DbContext et le DbSet.Cette pratique de développement est à éviter. En effet, elle complexifie les tests et, de facto, en diminue l'efficacité et en augmente le coût de maintenance.

Une possibilité serait de mocker les méthodes Get et Create et de définir leur comportement, ce qui rend le test pratiquement inutile, car c'est justement le comportement de ces méthodes que l'on souhaite tester. Une solution simple et efficace est d'utiliser l'approche de la base de données en mémoire, comme le montre le code ci-dessous :

public class UserRepositoryTests(){
    private readonly Fixture _fixture;

    public UserRepositoryTests(){
        _fixture = new Fixture();   
    }

    [Fact]
    public void GetShouldReturnsUser(){
        // Arrange
        var id = _fixture.Create<int>();

        // Ce code permets de simuler une base de données
        // directement en mémoire et de pouvoir interagir
        // avec celle-ci.   
        var options = new DbContextOptionsBuilder<MyDbContext>()
                .UseInMemoryDatabase("TestIMDatabase")
                .Options;

        using (var context = new MyDbContext(options))
        {
            context.Users.Add(new User
            {
                Id = id,
                Lastname = _fixture.Create<string>(),
                Firstname = _fixture.Create<string>(),
            });
            context.SaveChangesAsync();
        }

        // Act && Assert
        using (var context = new MyDbContext(options))
        {
            var sut = new UserRepository(context);
            var user = sut.Get(id);
            Assert.NotNull(user);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

J'utilise un vrai DbContext, dans lequel j'insère un User que je récupère via ma méthode de Get. Cela permet de tester unitairement le comportement de cette dernière.

Même chose pour la méthode Post :

[Fact]
public void PostShouldAddUser(){
    // Arrange
    var expectedUser = _fixture.Create<User>();
    var options = new DbContextOptionsBuilder<MyDbContext>()
            .UseInMemoryDatabase("TestIMDatabase")
            .Options;

    // Act && Assert
    using (var context = new MyDbContext(options))
    {
        var sut = new UserRepository(context);
        var actualId = sut.Create(user);
        Assert.NotNull(actualId);
         Assert.Equal(actualId, expectedUser.id);
    }
}
Enter fullscreen mode Exit fullscreen mode

Ce genre de test est très pratique quand on souhaite vérifier le comportement des opérations de type CRUD. Elle l'est tout autant lorsque la base de données en elle-même, n'a pas d'incidence pour le test. En effet, la Base de Données en mémoire est uniquement dédiée aux tests car ce n'est pas une réelle Base de Données, elle ne supporte pas les transactions ni les requêtes SQL en directe et bien sûr, n'est pas optimisée pour la performance.

Happy coding !

Top comments (0)