Tous mes tests passent, mais ça plante en prod


Le scénario qui devrait être impossible

Couverture de tests à 85%. Tous au vert. Déploiement en production. Et puis :

  • L’API retourne des erreurs 500
  • Les données ne sont pas sauvegardées en base
  • La sérialisation JSON casse les dates
  • L’injection de dépendances n’est pas configurée correctement

Comment est-ce possible ? Les tests passent.

Pourquoi : les tests unitaires testent l’isolation, pas l’assemblage

Un test unitaire vérifie qu’un composant fonctionne seul. Il remplace toutes les dépendances par des doublures (stubs, fakes) et vérifie le comportement en isolation.

C’est sa force. C’est aussi sa limite.

Test unitaire du UserRepository   → passe ✅  (avec FakeDbContext)
Test unitaire du UserService       → passe ✅  (avec FakeUserRepository)
Test unitaire du UserController    → passe ✅  (avec FakeUserService)

En production : le Controller parle au Service qui parle au Repository
qui parle à SQL Server... et quelque chose dans cette chaîne est cassé.

Les composants fonctionnent chacun de leur côté. Ce qui est cassé, c’est le câblage entre eux.

Les bugs que les tests unitaires ne voient pas

Mapping ORM incorrect Le repository passe les tests avec une base en mémoire. En production, la migration SQL n’a pas été jouée, ou le mapping d’une colonne est mauvais.

Sérialisation JSON Le serializer par défaut d’ASP.NET sérialise DateTime différemment selon les cultures. Les tests ne passent pas par le pipeline HTTP.

Configuration de l’injection de dépendances On a oublié d’enregistrer IEmailSender dans le conteneur DI. Startup.cs compile. Les tests unitaires injectent directement. En production : InvalidOperationException.

Comportement de la vraie base de données EF Core InMemoryDatabase n’exécute pas de SQL. Les contraintes de clé étrangère, les transactions, les comportements de concurrence — tout ça n’existe pas en mémoire.

La solution : monter dans la pyramide

La pyramide des tests propose trois niveaux :

         /\
        /e2e\          ← Peu nombreux, coûteux, vérifient l'ensemble
       /------\
      / intégr \       ← Vérifient les connexions entre composants
     /----------\
    / unitaires  \     ← Nombreux, rapides, vérifient l'isolation
   /--------------\

Les tests d’intégration comblent exactement le gap : ils vérifient qu’un composant fonctionne avec une vraie dépendance externe — une vraie base de données, un vrai pipeline HTTP, un vrai serializer.

// Test d'intégration : teste le repository avec une vraie BDD (SQLite in-memory)
public class UserRepositoryIntegrationTests : IDisposable
{
    private readonly AppDbContext _context;

    public UserRepositoryIntegrationTests()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseSqlite("DataSource=:memory:")
            .Options;
        _context = new AppDbContext(options);
        _context.Database.EnsureCreated();
    }

    [Fact]
    public void Add_PersistsUserToDatabase()
    {
        var repo = new UserRepository(_context);

        repo.Add(new User { Name = "Alice", Email = "alice@test.com" });

        var saved = _context.Users.Single();
        Assert.Equal("Alice", saved.Name);
    }

    public void Dispose() => _context.Dispose();
}

Ce test utilise une vraie base SQLite, pas une liste en mémoire. Il vérifie la vraie requête SQL, le vrai mapping EF Core, les vraies contraintes.

Choisir le bon niveau

La question à se poser n’est pas “est-ce que je dois faire des tests d’intégration ?” mais “quel est le niveau minimum pour vérifier ce comportement ?”

Je veux vérifier…Niveau approprié
Une règle de calcul métierUnitaire
Une validation de domaineUnitaire
Le mapping ORM et les requêtes SQLIntégration
Le routing et la sérialisation de l’APIIntégration
Le parcours utilisateur completE2E
Que l’application démarreE2E (smoke test)

Plus on monte dans la pyramide, plus c’est coûteux. On monte uniquement quand le niveau inférieur ne suffit pas.

En résumé

  • Les tests unitaires vérifient les composants en isolation — leur valeur est énorme, mais ils ont une limite
  • Ils ne voient pas les problèmes de câblage entre composants : ORM, DI, sérialisation, SQL
  • Les tests d’intégration comblent ce gap en testant une connexion à la fois avec de vraies dépendances
  • La pyramide n’est pas un dogme — c’est un outil de décision : quel est le niveau minimum pour vérifier ce comportement ?

Quand vos tests passent mais que la prod plante, ne cherchez pas un bug dans votre code métier. Cherchez ce que vos tests unitaires n’ont pas pu voir.


La pyramide des tests, Testcontainers, WebApplicationFactory et les tests d’intégration ASP.NET sont au programme de mes formations Tests & Validation au CNAM Strasbourg.