Como uso
Assert
(ou outra classe de teste?) Para verificar se uma exceção foi lançada?
Como uso
Assert
(ou outra classe de teste?) Para verificar se uma exceção foi lançada?
Respostas:
Para "Teste da equipe do Visual Studio", parece que você aplica o atributo ExpectedException ao método do teste.
Exemplo da documentação aqui: Um passo a passo de teste de unidade com o Visual Studio Team Test
[TestMethod]
[ExpectedException(typeof(ArgumentException),
"A userId of null was inappropriately allowed.")]
public void NullUserIdInConstructor()
{
LogonInfo logonInfo = new LogonInfo(null, "P@ss0word");
}
Normalmente, sua estrutura de teste terá uma resposta para isso. Mas se não for flexível o suficiente, você sempre poderá fazer isso:
try {
somethingThatShouldThrowAnException();
Assert.Fail(); // If it gets to this line, no exception was thrown
} catch (GoodException) { }
Como @Jonas aponta, isso NÃO funciona para capturar uma exceção de base:
try {
somethingThatShouldThrowAnException();
Assert.Fail(); // raises AssertionException
} catch (Exception) {
// Catches the assertion exception, and the test passes
}
Se você absolutamente precisar capturar Exception, será necessário repetir novamente o Assert.Fail (). Mas, realmente, este é um sinal de que você não deveria escrever isso à mão; verifique se há opções na estrutura de teste ou veja se você pode lançar uma exceção mais significativa para testar.
catch (AssertionException) { throw; }
Você poderá adaptar essa abordagem ao que quiser - incluindo a especificação de quais tipos de exceções serão capturadas. Se você espera apenas determinados tipos, finalize os catch
blocos com:
} catch (GoodException) {
} catch (Exception) {
// not the right kind of exception
Assert.Fail();
}
Meu método preferido para implementar isso é escrever um método chamado Throws e usá-lo como qualquer outro método Assert. Infelizmente, o .NET não permite que você escreva um método de extensão estática; portanto, você não pode usá-lo como se realmente pertencesse à compilação na classe Assert; basta criar outro chamado MyAssert ou algo semelhante. A classe fica assim:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace YourProject.Tests
{
public static class MyAssert
{
public static void Throws<T>( Action func ) where T : Exception
{
var exceptionThrown = false;
try
{
func.Invoke();
}
catch ( T )
{
exceptionThrown = true;
}
if ( !exceptionThrown )
{
throw new AssertFailedException(
String.Format("An exception of type {0} was expected, but not thrown", typeof(T))
);
}
}
}
}
Isso significa que seu teste de unidade se parece com isso:
[TestMethod()]
public void ExceptionTest()
{
String testStr = null;
MyAssert.Throws<NullReferenceException>(() => testStr.ToUpper());
}
Que se parece e se comporta muito mais com o resto das suas sintaxes de teste de unidade.
Assert.ThrowsException<T>
e Assert.ThrowsExceptionAsync<T>
- consulte blogs.msdn.microsoft.com/visualstudioalm/2017/02/25/…
se você usa NUNIT, pode fazer algo assim:
Assert.Throws<ExpectedException>(() => methodToTest());
Também é possível armazenar a exceção lançada para validá-la ainda mais:
ExpectedException ex = Assert.Throws<ExpectedException>(() => methodToTest());
Assert.AreEqual( "Expected message text.", ex.Message );
Assert.AreEqual( 5, ex.SomeNumber);
Se você estiver usando o MSTest, que originalmente não tinha um ExpectedException
atributo, você pode fazer o seguinte:
try
{
SomeExceptionThrowingMethod()
Assert.Fail("no exception thrown");
}
catch (Exception ex)
{
Assert.IsTrue(ex is SpecificExceptionType);
}
Desconfie de usar ExpectedException, pois isso pode levar a várias armadilhas, conforme demonstrado aqui:
http://geekswithblogs.net/sdorman/archive/2009/01/17/unit-testing-and-expected-exceptions.aspx
E aqui:
http://xunit.github.io/docs/comparisons.html
Se você precisar testar exceções, haverá menos maneiras desaprovadas. Você pode usar o método try {act / fail} catch {assert}, que pode ser útil para estruturas que não têm suporte direto para testes de exceção diferentes de ExpectedException.
Uma alternativa melhor é usar o xUnit.NET, que é uma estrutura de teste de unidade muito moderna, voltada para o futuro e extensível, que aprendeu com todos os outros erros e melhorou. Um desses aprimoramentos é o Assert.Throws, que fornece uma sintaxe muito melhor para a declaração de exceções.
Você pode encontrar o xUnit.NET no github: http://xunit.github.io/
O MSTest (v2) agora tem uma função Assert.ThrowsException que pode ser usada assim:
Assert.ThrowsException<System.FormatException>(() =>
{
Story actual = PersonalSite.Services.Content.ExtractHeader(String.Empty);
});
Você pode instalá-lo com nuget: Install-Package MSTest.TestFramework
Em um projeto em que estou trabalhando, temos outra solução para isso.
Primeiro, não gosto do ExpectedExceptionAttribute, pois leva em consideração qual chamada de método causou a exceção.
Eu faço isso com um método auxiliar em seu lugar.
Teste
[TestMethod]
public void AccountRepository_ThrowsExceptionIfFileisCorrupt()
{
var file = File.Create("Accounts.bin");
file.WriteByte(1);
file.Close();
IAccountRepository repo = new FileAccountRepository();
TestHelpers.AssertThrows<SerializationException>(()=>repo.GetAll());
}
HelperMethod
public static TException AssertThrows<TException>(Action action) where TException : Exception
{
try
{
action();
}
catch (TException ex)
{
return ex;
}
Assert.Fail("Expected exception was not thrown");
return null;
}
Legal, não é;)
É um atributo no método de teste ... você não usa Assert. Se parece com isso:
[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()
Você pode baixar um pacote do Nuget usando: PM> Install-Package MSTestExtensions que adiciona a sintaxe Assert.Throws () no estilo de nUnit / xUnit ao MsTest.
Instruções de alto nível: baixe o assembly e herde do BaseTest e você pode usar a sintaxe Assert.Throws () .
O método principal para a implementação Throws é o seguinte:
public static void Throws<T>(Action task, string expectedMessage, ExceptionMessageCompareOptions options) where T : Exception
{
try
{
task();
}
catch (Exception ex)
{
AssertExceptionType<T>(ex);
AssertExceptionMessage(ex, expectedMessage, options);
return;
}
if (typeof(T).Equals(new Exception().GetType()))
{
Assert.Fail("Expected exception but no exception was thrown.");
}
else
{
Assert.Fail(string.Format("Expected exception of type {0} but no exception was thrown.", typeof(T)));
}
}
Divulgação: Eu montei este pacote.
Mais informações: http://www.bradoncode.com/blog/2012/01/asserting-exceptions-in-mstest-with.html
Você pode conseguir isso com uma linha simples.
Se sua operação foo.bar()
é assíncrona:
await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());
Se foo.bar()
não for assíncrono
Assert.ThrowsException<Exception>(() => foo.bar());
ArgumentException
por exemplo. A antiga tentativa de captura e teste da resposta de exceção ainda é preferida se você tiver critérios avançados para testar, mas, para muitos dos meus casos, isso ajuda muito!
Não recomendo usar o atributo ExpectedException (já que é muito restritivo e propenso a erros) ou escrever um bloco try / catch em cada teste (já que é muito complicado e propenso a erros). Use um método de afirmação bem projetado - fornecido pela sua estrutura de teste ou escreva seu próprio. Aqui está o que eu escrevi e uso.
public static class ExceptionAssert
{
private static T GetException<T>(Action action, string message="") where T : Exception
{
try
{
action();
}
catch (T exception)
{
return exception;
}
throw new AssertFailedException("Expected exception " + typeof(T).FullName + ", but none was propagated. " + message);
}
public static void Propagates<T>(Action action) where T : Exception
{
Propagates<T>(action, "");
}
public static void Propagates<T>(Action action, string message) where T : Exception
{
GetException<T>(action, message);
}
public static void Propagates<T>(Action action, Action<T> validation) where T : Exception
{
Propagates(action, validation, "");
}
public static void Propagates<T>(Action action, Action<T> validation, string message) where T : Exception
{
validation(GetException<T>(action, message));
}
}
Exemplo usa:
[TestMethod]
public void Run_PropagatesWin32Exception_ForInvalidExeFile()
{
(test setup that might propagate Win32Exception)
ExceptionAssert.Propagates<Win32Exception>(
() => CommandExecutionUtil.Run(Assembly.GetExecutingAssembly().Location, new string[0]));
(more asserts or something)
}
[TestMethod]
public void Run_PropagatesFileNotFoundException_ForExecutableNotFound()
{
(test setup that might propagate FileNotFoundException)
ExceptionAssert.Propagates<FileNotFoundException>(
() => CommandExecutionUtil.Run("NotThere.exe", new string[0]),
e => StringAssert.Contains(e.Message, "NotThere.exe"));
(more asserts or something)
}
NOTAS
Retornar a exceção em vez de oferecer suporte a um retorno de chamada de validação é uma idéia razoável, exceto que isso torna a sintaxe de chamada dessa declaração muito diferente das outras que eu uso.
Diferentemente de outros, eu uso 'propaga' em vez de 'lança', pois só podemos testar se uma exceção se propaga a partir de uma chamada. Não podemos testar diretamente se uma exceção é lançada. Mas suponho que você possa imaginar lances como significados: jogados e não capturados.
PENSAMENTO FINAL
Antes de mudar para esse tipo de abordagem, considerei usar o atributo ExpectedException quando um teste verificou apenas o tipo de exceção e usar um bloco try / catch se mais validação fosse necessária. Mas não apenas precisaria pensar em qual técnica usar para cada teste, mas alterar o código de uma técnica para outra conforme as necessidades mudassem não era um esforço trivial. Usar uma abordagem consistente economiza esforço mental.
Então, em resumo, essa abordagem é esportiva: facilidade de uso, flexibilidade e robustez (difícil de fazer errado).
O auxiliar fornecido pelo @Richiban acima funciona muito bem, exceto que não lida com a situação em que uma exceção é lançada, mas não com o tipo esperado. Os seguintes endereços que:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace YourProject.Tests
{
public static class MyAssert
{
/// <summary>
/// Helper for Asserting that a function throws an exception of a particular type.
/// </summary>
public static void Throws<T>( Action func ) where T : Exception
{
Exception exceptionOther = null;
var exceptionThrown = false;
try
{
func.Invoke();
}
catch ( T )
{
exceptionThrown = true;
}
catch (Exception e) {
exceptionOther = e;
}
if ( !exceptionThrown )
{
if (exceptionOther != null) {
throw new AssertFailedException(
String.Format("An exception of type {0} was expected, but not thrown. Instead, an exception of type {1} was thrown.", typeof(T), exceptionOther.GetType()),
exceptionOther
);
}
throw new AssertFailedException(
String.Format("An exception of type {0} was expected, but no exception was thrown.", typeof(T))
);
}
}
}
}
Como você menciona o uso de outras classes de teste, uma opção melhor que o ExpectedException
atributo é usar o Should.Throw de Shoudly .
Should.Throw<DivideByZeroException>(() => { MyDivideMethod(1, 0); });
Digamos que tenhamos um requisito de que o cliente precise de um endereço para criar um pedido . Caso contrário, o CreateOrderForCustomer
método deve resultar em um ArgumentException
. Então poderíamos escrever:
[TestMethod]
public void NullUserIdInConstructor()
{
var customer = new Customer(name := "Justin", address := null};
Should.Throw<ArgumentException>(() => {
var order = CreateOrderForCustomer(customer) });
}
Isso é melhor do que usar um ExpectedException
atributo, porque estamos sendo específicos sobre o que deve gerar o erro. Isso torna os requisitos de nossos testes mais claros e também facilita o diagnóstico quando o teste falha.
Observe que também existe um Should.ThrowAsync
teste de método assíncrono.
Bem, vou resumir o que todo mundo aqui disse antes ... De qualquer forma, aqui está o código que construí de acordo com as boas respostas :) Tudo o que resta fazer é copiar e usar ...
/// <summary>
/// Checks to make sure that the input delegate throws a exception of type TException.
/// </summary>
/// <typeparam name="TException">The type of exception expected.</typeparam>
/// <param name="methodToExecute">The method to execute to generate the exception.</param>
public static void AssertRaises<TException>(Action methodToExecute) where TException : System.Exception
{
try
{
methodToExecute();
}
catch (TException) {
return;
}
catch (System.Exception ex)
{
Assert.Fail("Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
}
Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");
}
Confira o nUnit Docs para exemplos sobre:
[ExpectedException( typeof( ArgumentException ) )]
No caso de usar o NUnit , tente o seguinte:
Assert.That(() =>
{
Your_Method_To_Test();
}, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));
Existe uma biblioteca incrível chamada NFluent que acelera e facilita a maneira como você escreve suas afirmações .
É bem simples escrever uma afirmação para lançar uma exceção:
[Test]
public void given_when_then()
{
Check.ThatCode(() => MethodToTest())
.Throws<Exception>()
.WithMessage("Process has been failed");
}
Embora essa seja uma pergunta antiga, eu gostaria de acrescentar um novo pensamento à discussão. Eu estendi o padrão Organizar, Agir, Assertar para ser Esperado, Organizar, Agir, Assertar. Você pode criar um ponteiro de exceção esperado e afirmar que ele foi atribuído. Isso parece mais limpo do que fazer seus Asserts em um bloco catch, deixando a seção Act principalmente apenas para uma linha de código chamar o método em teste. Você também não precisa de Assert.Fail();
ou return
para vários pontos no código. Qualquer outra exceção lançada fará com que o teste falhe, porque não será capturado e, se uma exceção do seu tipo esperado for lançada, mas não for a que você estava esperando, afirmando contra a mensagem ou outras propriedades de a exceção ajuda a garantir que seu teste não seja aprovado inadvertidamente.
[TestMethod]
public void Bar_InvalidDependency_ThrowsInvalidOperationException()
{
// Expectations
InvalidOperationException expectedException = null;
string expectedExceptionMessage = "Bar did something invalid.";
// Arrange
IDependency dependency = DependencyMocks.Create();
Foo foo = new Foo(dependency);
// Act
try
{
foo.Bar();
}
catch (InvalidOperationException ex)
{
expectedException = ex;
}
// Assert
Assert.IsNotNull(expectedException);
Assert.AreEqual(expectedExceptionMessage, expectedException.Message);
}