¿Cómo uso Assert.Throws para afirmar el tipo de excepción?

247

¿Cómo uso Assert.Throwspara afirmar el tipo de excepción y la redacción real del mensaje?

Algo como esto:

Assert.Throws<Exception>(
    ()=>user.MakeUserActive()).WithMessage("Actual exception message")

El método que estoy probando arroja múltiples mensajes del mismo tipo, con diferentes mensajes, y necesito una forma de probar que se arroja el mensaje correcto según el contexto.

epitka
fuente

Respuestas:

444

Assert.Throws devuelve la excepción lanzada que le permite hacer valer la excepción.

var ex = Assert.Throws<Exception>(() => user.MakeUserActive());
Assert.That(ex.Message, Is.EqualTo("Actual exception message"));

Entonces, si no se lanza una excepción, o si se lanza una excepción del tipo incorrecto, la primera Assert.Throwsaserción fallará. Sin embargo, si se produce una excepción del tipo correcto, ahora puede afirmar la excepción real que ha guardado en la variable.

Al usar este patrón, puede afirmar sobre otras cosas que no sean el mensaje de excepción, por ejemplo, en el caso de ArgumentExceptiony derivados, puede afirmar que el nombre del parámetro es correcto:

var ex = Assert.Throws<ArgumentNullException>(() => foo.Bar(null));
Assert.That(ex.ParamName, Is.EqualTo("bar"));

También puede usar la API fluida para hacer estas afirmaciones:

Assert.That(() => foo.Bar(null), 
Throws.Exception
  .TypeOf<ArgumentNullException>()
  .With.Property("ParamName")
  .EqualTo("bar"));

o alternativamente

Assert.That(
    Assert.Throws<ArgumentNullException>(() =>
        foo.Bar(null)
    .ParamName,
Is.EqualTo("bar"));

Un pequeño consejo al hacer valer mensajes de excepción es decorar el método de prueba con el SetCultureAttributefin de asegurarse de que el mensaje lanzado utilice la cultura esperada. Esto entra en juego si almacena sus mensajes de excepción como recursos para permitir la localización.

Patrik Hägne
fuente
Esto fue realmente útil para mí: quería una forma de mostrar el error, ni siquiera leí si el método Assert.Throws devolvió un valor. Gracias
Haroon
66
+1 Gracias por mostrar la API Fluent, por alguna razón estaba teniendo problemas para entender cómo usarla solo desde los documentos de NUnit.
aolszowka
Cuando desee afirmar el mensaje, también puede usar directamente la propiedad Mensaje en lugar de la "Propiedad".
Marcel
25

Ahora puede usar los ExpectedExceptionatributos, p. Ej.

[Test]
[ExpectedException(typeof(InvalidOperationException), 
 ExpectedMessage="You can't do that!"]
public void MethodA_WithNull_ThrowsInvalidOperationException()
{
    MethodA(null);
}
Jackson Pope
fuente
2
Esto me molestó un poco cuando lo vi por primera vez, porque la prueba aparentemente no tenía ninguna afirmación, lo cual era un olor para mí. Esta es una buena característica, pero se debe discutir en el equipo wheter este attribut debe acostumbrarse a través de los Assert.Throws
Marcel
14
+1 también es una buena manera de probar excepciones. Lo único a tener en cuenta al respecto es que, en teoría, cualquier línea de código que arroje una InvalidOperationException con ese mensaje pasará la prueba, incluido el código en su prueba que prepara los datos / objetos de prueba o cualquier otro método que pueda necesitar ejecutar antes de uno que le interese probar, posiblemente resultando en un falso positivo. Por supuesto, eso depende de cuán específico sea el mensaje y el tipo de excepción que esté probando. Con Assert.Throwusted puede orientar la línea exacta que le interesa.
No
21
El atributo ExpectedException está en desuso en NUnit 3: github.com/nunit/docs/wiki/Breaking-Changes
Frank Sebastià
13
Assert.That(myTestDelegate, Throws.ArgumentException
    .With.Property("Message").EqualTo("your argument is invalid."));
Jordan Morris
fuente
2
Con la introducción del nombre de operador, editaría esta excelente respuesta para:Assert.That(myTestDelegate, Throws.ArgumentException .With.Property(nameof(ArgumentException.Message)).EqualTo("your argument is invalid."));
Samuel
@Samuel Esa edición usaría una referencia fuertemente tipada, lo cual es bueno, pero por otro lado, es un nombre de propiedad de baja rotación y la cadena mágica mejora la fluidez. Una cuestión de gustos, supongo
Jordan Morris
1
Estoy totalmente de acuerdo con usted con respecto Exception.Message. Todavía recomendaría al menos agregar esta alternativa porque también With.Propertyse puede utilizar en otros objetos, lo que en este caso mejoraría la estabilidad del código.
Samuel
5

Esta es una pregunta antigua pero relevante con respuestas obsoletas, así que estoy agregando la solución actual:

public void Test() {
    throw new MyCustomException("You can't do that!");
}

[TestMethod]
public void ThisWillPassIfExceptionThrown()
{
    var exception = Assert.ThrowsException<MyCustomException>(
        () => Test(),
        "This should have thrown!");
    Assert.AreEqual("You can't do that!", exception.Message);
}

Esto funciona con using Microsoft.VisualStudio.TestTools.UnitTesting;

Tvde1
fuente
Realmente me sorprendió que no hubiera una forma concisa de afirmar que un método arroja una excepción como en JUnit. A menos que haya implicaciones que no conozco, esta es probablemente la respuesta más relevante actualmente.
NetherGranite
3

Para ampliar la respuesta del persistente y proporcionar más de la funcionalidad de NUnit, puede hacer esto:

public bool AssertThrows<TException>(
    Action action,
    Func<TException, bool> exceptionCondition = null)
    where TException : Exception 
{
    try
    {
        action();
    }
    catch (TException ex)
    {
        if (exceptionCondition != null)
        {
            return exceptionCondition(ex);
        }

        return true;
    }
    catch
    {
        return false;
    }

    return false; 
}

Ejemplos:

// No exception thrown - test fails.
Assert.IsTrue(
    AssertThrows<InvalidOperationException>(
        () => {}));

// Wrong exception thrown - test fails.
Assert.IsTrue(
    AssertThrows<InvalidOperationException>(
        () => { throw new ApplicationException(); }));

// Correct exception thrown - test passes.
Assert.IsTrue(
    AssertThrows<InvalidOperationException>(
        () => { throw new InvalidOperationException(); }));

// Correct exception thrown, but wrong message - test fails.
Assert.IsTrue(
    AssertThrows<InvalidOperationException>(
        () => { throw new InvalidOperationException("ABCD"); },
        ex => ex.Message == "1234"));

// Correct exception thrown, with correct message - test passes.
Assert.IsTrue(
    AssertThrows<InvalidOperationException>(
        () => { throw new InvalidOperationException("1234"); },
        ex => ex.Message == "1234"));
fre0n
fuente
2

Es mucho tiempo desde que se planteó este problema, me doy cuenta, pero recientemente me encontré con lo mismo y sugiero esta función para MSTest:

public bool AssertThrows(Action action) where T : Exception 
{ 
try {action();} 
catch(Exception exception) 
{ 
    if (exception.GetType() == typeof(T)) return true; 
} 
return false; 
}

uso:

Assert.IsTrue(AssertThrows<FormatException>(delegate{ newMyMethod(MyParameter); }));

Más aquí: http://phejndorf.wordpress.com/2011/02/21/assert-that-a-particular-exception-has-occured/

persistente
fuente
2

Como me molesta la verbosidad de algunos de los nuevos patrones de NUnit, uso algo como esto para crear un código que sea más limpio para mí personalmente:

public void AssertBusinessRuleException(TestDelegate code, string expectedMessage)
{
    var ex = Assert.Throws<BusinessRuleException>(code);
    Assert.AreEqual(ex.Message, expectedMessage);
}

public void AssertException<T>(TestDelegate code, string expectedMessage) where T:Exception
{
    var ex = Assert.Throws<T>(code);
    Assert.AreEqual(ex.Message, expectedMessage);
}

El uso es entonces:

AssertBusinessRuleException(() => user.MakeUserActive(), "Actual exception message");
Salvaje
fuente
1
¿Qué es TestDelegate?
reggaeguitar
1
Le permite pasar el código para ejecutarlo como parámetro. Es una clase en el marco de NUnit (v3.2.0.0).
Salvaje