Como uso o Assert.Throws para afirmar o tipo da exceção?

246

Como uso Assert.Throwspara afirmar o tipo da exceção e o texto da mensagem real.

Algo assim:

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

O método que estou testando lança várias mensagens do mesmo tipo, com mensagens diferentes, e preciso de uma maneira de testar se a mensagem correta é lançada dependendo do contexto.

epitka
fonte

Respostas:

443

Assert.Throws retorna a exceção lançada, que permite afirmar a exceção.

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

Portanto, se nenhuma exceção for lançada ou uma exceção do tipo errado for lançada, a primeira Assert.Throwsasserção falhará. No entanto, se uma exceção do tipo correto for lançada, agora você poderá afirmar a exceção real que salvou na variável.

Ao usar esse padrão, você pode afirmar outras coisas além da mensagem de exceção, por exemplo, no caso de ArgumentExceptione derivados, pode afirmar que o nome do parâmetro está correto:

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

Você também pode usar a API fluente para fazer as seguintes afirmações:

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

ou alternativamente

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

Uma pequena dica ao declarar mensagens de exceção é decorar o método de teste com o SetCultureAttributepara garantir que a mensagem lançada esteja usando a cultura esperada. Isso entra em jogo se você armazenar suas mensagens de exceção como recursos para permitir a localização.

Patrik Hägne
fonte
Isso foi realmente útil para mim - eu queria uma maneira de exibir o erro, nem li se um valor foi retornado pelo método Assert.Throws. Obrigado
Haroon
6
+1 Obrigado por mostrar a API Fluent, por algum motivo eu estava tendo problemas para entender como usá-la apenas a partir dos documentos do NUnit.
aolszowka
Quando você deseja afirmar a mensagem, também pode usar diretamente a propriedade Message em vez da "Property".
Marcel
25

Agora você pode usar os ExpectedExceptionatributos, por exemplo

[Test]
[ExpectedException(typeof(InvalidOperationException), 
 ExpectedMessage="You can't do that!"]
public void MethodA_WithNull_ThrowsInvalidOperationException()
{
    MethodA(null);
}
Jackson Pope
fonte
2
Isso me perturbou um pouco quando foi visto pela primeira vez, porque o teste aparentemente não tinha nenhuma afirmação, o que era um cheiro para mim. Este é um bom recurso, mas deve-se discutir na equipe wheter este attribut deve se acostumar ao longo dos Assert.Throws
Marcel
14
+1 também é uma ótima maneira de testar exceções. A única coisa a ter em mente é que, teoricamente, qualquer linha de código que lança uma InvalidOperationException com essa mensagem passará no teste, incluindo o código em seu teste que prepara os dados / objetos de teste ou qualquer outro método que você precise executar antes do aquele que você está interessado em testar, possivelmente resultando em um falso positivo. Obviamente, isso depende da especificidade da mensagem e do tipo de exceção que você está testando. Com Assert.Throwvocê pode direcionar a linha exato que você está interessado.
Nope
21
Atributo ExpectedException está obsoleta no 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
fonte
2
Com a introdução do nome do operador, eu editaria esta excelente resposta para:Assert.That(myTestDelegate, Throws.ArgumentException .With.Property(nameof(ArgumentException.Message)).EqualTo("your argument is invalid."));
Samuel
@ Samuel Essa edição usaria uma referência fortemente tipada, o que é bom, mas, por outro lado, é um nome de propriedade com rotatividade extremamente baixa e a string mágica melhora a fluência. Uma questão de gosto, suponho #
Jordan Morris
1
Concordo inteiramente com você a respeito Exception.Message. Eu ainda recomendaria adicionar pelo menos essa alternativa, porque também With.Propertypode ser utilizada em outros objetos, os quais, nesse caso, melhorariam a estabilidade do código.
Samuel
5

Esta é uma pergunta antiga, mas relevante, com respostas desatualizadas, por isso estou adicionando a solução atual:

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);
}

Isso funciona com using Microsoft.VisualStudio.TestTools.UnitTesting;

Tvde1
fonte
Fiquei realmente surpreso que não havia uma maneira concisa de afirmar que um método lança uma exceção como no JUnit. A menos que haja implicações que não conheço, esta é provavelmente a resposta mais relevante atualmente.
NetherGranite
3

Para expandir a resposta do persistente e fornecer mais funcionalidades do NUnit, você pode fazer o seguinte:

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; 
}

Exemplos:

// 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
fonte
2

Percebi que já faz muito tempo que esse problema foi levantado, mas recentemente me deparei com a mesma coisa e sugiro esta função para o 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); }));

Mais aqui: http://phejndorf.wordpress.com/2011/02/21/assert-that-a-particular-exception-has-occured/

persistente
fonte
2

Como estou incomodado com a verbosidade de alguns dos novos padrões da NUnit, uso algo assim para criar código mais limpo para mim:

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);
}

O uso é então:

AssertBusinessRuleException(() => user.MakeUserActive(), "Actual exception message");
Selvagem
fonte
1
O que é o TestDelegate?
reggaeguitar 13/12/19
1
Ele permite que você passe o código para executar como parâmetro. É uma classe na estrutura do NUnit (v3.2.0.0).
Savage