20 Nov 2014

Gérer les exceptions dans les Tests Unitaires

Nous sommes tous concernés par la qualité logicielle, et donc, de nos tests unitaires. Nous sommes souvent confrontés à un problème : Comment tester qu’une méthode lève bien telle exception ?

Je vous propose trois réponses.

Dans un premier temps, en faisant des recherches sur le net, on tombe rapidement sur une réponse rapide : l’attribut.

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void MethodTest()
{
var obj = new ClassRequiringNonNullParameter( null );
}

Cela peut sembler une bonne approche.

Sauf que quand on applique le AAA (Arrange, Act, Assert) on est en droit de se demander où l’exception a-t-elle été levée.

En effet, si une exception de même type est levée lors de l’Arrange, le test est quand même au vert.

Seconde approche, on teste au bon endroit :

[TestMethod]
public void MethodTest()
{
try{
var obj = new ClassRequiringNonNullParameter( null );
throw AssertException("Une erreur devrait etre levée");
}
catch(Exception ex){
Assert.IsInstanceOfType(ex, ArgumentNullException);
Assert.Equal("Parameter cannot be null or empty.", ex.Message);
}
}

Cela correspond bien à ce que l’on recherche, mais dans le cas d’un ensemble assez complet de tests, cela devient illisible.

La troisième approche est d’externaliser tout ça dans un fichier. On ajoutera également la version asynchrone.

public static Exception ShouldThrowException(Action action) where TExceptionType : Exception
{
if (action == null)
{
throw new ArgumentNullException("action");
}
try
{
action();
Assert.Fail("La méthode doit lever une exception");
}
catch (AssertFailedException)
{
throw;
}
catch (Exception ex)
{
Assert.IsInstanceOfType(ex, typeof(TExceptionType), "L'exception n'est pas du type attendu");
return ex;
}
return null;
}

public static async Task ShouldThrowExceptionAsync(Func action) where TExceptionType : Exception
{
if (action == null)
{
throw new ArgumentNullException("action");
}
try
{
await action();
Assert.Fail("La méthode doit lever une exception");
}
catch (AssertFailedException)
{
throw;
}
catch (Exception ex)
{
Assert.IsInstanceOfType(ex, typeof(TExceptionType), "L'exception n'est pas du type attendu");
return ex;
}
return null;
}

Il ne reste plus qu’à l’appeler dans le test.

public void MethodTest()
{
// ACT
Exception ex = TestServices.ShouldThrowException<ArgumentNullException>(() => new ClassRequiringNonNullParameter(null));
// ASSERT
Assert.Equal("Parameter cannot be null or empty.", ex.Message);
}

Bonus : Sinon on peut toujours utiliser XUnit qui inclut directement cette méthode :

Exception ex = Assert.Throws<ArgumentNullException>(() => new ClassRequiringNonNullParameter(null));
Assert.Equal("Parameter cannot be null or empty.", ex.Message);
Share