xUnit : framework de tests unitaires pour .NET

xUnit est le framework de tests de référence pour .NET, simple à utiliser et extensible.

Structure d'un test

Un test xUnit suit souvent le pattern Arrange‑Act‑Assert. Utilisez [Fact] pour un cas fixe et [Theory] avec InlineData pour des cas paramétrés.

using Xunit;

public class CalculatorTests
{
    [Fact]
    public void Add_TwoIntegers_ReturnsSum()
    {
        // Arrange
        var calculator = new Calculator();
        
        // Act
        var result = calculator.Add(2, 3);
        
        // Assert
        Assert.Equal(5, result);
    }
}

Theory et données

Les Theories permettent d'exécuter le même test avec différentes données d'entrée via [InlineData] ou des providers.

[Theory]
[InlineData(2, 3, 5)]
[InlineData(10, 20, 30)]
[InlineData(-1, 1, 0)]
public void Add_DifferentValues_ReturnsExpectedSum(int a, int b, int expected)
{
    // Arrange
    var calculator = new Calculator();
    
    // Act
    var result = calculator.Add(a, b);
    
    // Assert
    Assert.Equal(expected, result);
}

Tests asynchrones

xUnit supporte nativement async/await. Évitez .Result/.Wait() pour prévenir les deadlocks.

[Fact]
public async Task ProcessDataAsync_ValidInput_ReturnsProcessedData()
{
    // Arrange
    var service = new DataService();
    var inputData = "test";
    
    // Act
    var result = await service.ProcessDataAsync(inputData);
    
    // Assert
    Assert.NotNull(result);
    Assert.Contains("processed", result);
}

Fixtures et partage de données

IClassFixture et CollectionFixture permettent de partager un contexte entre tests sans ordre d'exécution imposé.

public class DatabaseFixture : IDisposable
{
    public DatabaseFixture()
    {
        // Setup database connection
        Database = new TestDatabase();
        Database.Initialize();
    }
    
    public TestDatabase Database { get; private set; }
    
    public void Dispose()
    {
        Database.Dispose();
    }
}

public class DatabaseTests : IClassFixture<DatabaseFixture>
{
    private readonly DatabaseFixture _fixture;
    
    public DatabaseTests(DatabaseFixture fixture)
    {
        _fixture = fixture;
    }
    
    [Fact]
    public void CanConnectToDatabase()
    {
        Assert.True(_fixture.Database.IsConnected);
    }
}

Mocking et isolation

Utilisez des frameworks comme NSubstitute ou Moq pour isoler les dépendances et vérifier les interactions.

public class UserServiceTests
{
    [Fact]
    public void GetUser_ExistingId_ReturnsUser()
    {
        // Arrange
        var mockRepository = new Mock<IUserRepository>();
        var expectedUser = new User { Id = 1, Name = "John" };
        mockRepository.Setup(x => x.GetById(1)).Returns(expectedUser);
        
        var service = new UserService(mockRepository.Object);
        
        // Act
        var result = service.GetUser(1);
        
        // Assert
        Assert.Equal(expectedUser, result);
        mockRepository.Verify(x => x.GetById(1), Times.Once);
    }
}

Collections de tests

Les collections regroupent des classes de tests qui partagent un même fixture, utile pour éviter des initialisations coûteuses répétées.

[Collection("Database collection")]
public class IntegrationTest1
{
    // Tests qui partagent la même instance de base de données
}

[Collection("Database collection")]
public class IntegrationTest2
{
    // Tests qui partagent la même instance de base de données
}

Sortie de test (ITestOutputHelper)

Injectez ITestOutputHelper pour tracer des informations de debug lorsqu'un test est exécuté.

public class DebuggingTests
{
    private readonly ITestOutputHelper _output;
    
    public DebuggingTests(ITestOutputHelper output)
    {
        _output = output;
    }
    
    [Fact]
    public void ComplexTest_WithDebugging()
    {
        _output.WriteLine("Starting complex test");
        
        var data = GenerateTestData();
        _output.WriteLine($"Generated {data.Count} items");
        
        var result = ProcessData(data);
        _output.WriteLine($"Processing completed with result: {result}");
        
        Assert.True(result);
    }
}

Ignorer / mettre en attente un test

Utilisez l'attribut Skip sur [Fact] ou [Theory] pour désactiver temporairement un test. Renseignez toujours une raison explicite pour faciliter le suivi.

using Xunit;

public class SkippingExamples
{
    [Fact(Skip = "Feature temporarily disabled")]
    public void This_test_is_skipped()
    {
        // Not executed
    }

    [Theory(Skip = "Unstable on CI for now")]
    [InlineData(1)]
    [InlineData(2)]
    public void Skipped_theory(int value)
    {
        // Not executed
    }
}

Préférez corriger ou isoler un test instable. N'utilisez Skip que temporairement (ex: fonctionnalité désactivée, données externes indisponibles).

Bonnes pratiques

  • Nommez vos tests de manière descriptive

  • Séparez clairement Arrange, Act et Assert

  • Une seule assertion logique par test

  • Rendez les tests indépendants

  • Gardez des tests rapides

Conclusion

xUnit fournit une base solide pour des tests fiables et maintenables, avec un modèle extensible et moderne.