Como eu uso Assert para verificar se uma exceção foi lançada?

Como eu uso Assert (ou outra class de teste?) Para verificar se uma exceção foi lançada?

Para “Visual Studio Team Test”, 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 testes terá uma resposta para isso. Mas se não for flexível o suficiente, você sempre pode fazer isso:

 try { somethingThatShouldThrowAnException(); Assert.Fail(); // If it gets to this line, no exception was thrown } catch (GoodException) { } 

Como aponta @Jonas, isso NÃO funciona para pegar uma exceção de base:

 try { somethingThatShouldThrowAnException(); Assert.Fail(); // raises AssertionException } catch (Exception) { // Catches the assertion exception, and the test passes } 

Se você absolutamente deve pegar Exception, você precisa relançar o Assert.Fail (). Mas, na verdade, isso é um sinal de que você não deveria escrever isso a mão; Verifique sua estrutura de teste para opções, ou veja se você pode lançar uma exceção mais significativa para testar.

 catch (AssertionException) { throw; } 

Você deve ser capaz de adaptar essa abordagem ao que quiser – incluindo a especificação dos tipos de exceção a serem capturados. Se você espera apenas certos tipos, termine os blocos de catch 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, não é possível usar esse método como se ele realmente pertencesse à class Assert na class Assert; basta fazer outro chamado MyAssert ou algo semelhante. A turma fica assim:

 using System; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace YourProject.Tests { public static class MyAssert { public static void Throws( 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 é assim:

 [TestMethod()] public void ExceptionTest() { String testStr = null; MyAssert.Throws(() => testStr.ToUpper()); } 

Que parece e se comporta muito mais como o resto de suas syntaxs de teste de unidade.

Se você estiver usando o MSTest, que originalmente não tinha um atributo ExpectedException , você poderia fazer isso:

 try { SomeExceptionThrowingMethod() Assert.Fail("no exception thrown"); } catch (Exception ex) { Assert.IsTrue(ex is SpecificExceptionType); } 

se você usar o NUNIT, você pode fazer algo assim:

 Assert.Throws(() => methodToTest()); 

Também é possível armazenar a exceção lançada para validá-la ainda mais:

 ExpectedException ex = Assert.Throws(() => methodToTest()); Assert.AreEqual( "Expected message text.", ex.Message ); Assert.AreEqual( 5, ex.SomeNumber); 

Veja: http://nunit.org/docs/2.5/exceptionAsserts.html

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, há menos maneiras de desaprovar. 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 erros de outros e melhorou. Uma dessas melhorias é Assert.Throws, que fornece uma syntax muito melhor para a declaração de exceções.

Você pode encontrar o xUnit.NET no github: http://xunit.github.io/

Em um projeto em que estou trabalhando, temos outra solução fazendo isso.

Primeiro eu não gosto do ExpectedExceptionAttribute, porque leva em consideração qual chamada de método que causou a exceção.

Eu faço isso com um método auxiliar em vez disso.

Teste

 [TestMethod] public void AccountRepository_ThrowsExceptionIfFileisCorrupt() { var file = File.Create("Accounts.bin"); file.WriteByte(1); file.Close(); IAccountRepository repo = new FileAccountRepository(); TestHelpers.AssertThrows(()=>repo.GetAll()); } 

HelperMethod

 public static TException AssertThrows(Action action) where TException : Exception { try { action(); } catch (TException ex) { return ex; } Assert.Fail("Expected exception was not thrown"); return null; } 

Puro, 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 da Nuget usando: PM> Install-Package MSTestExtensions que adiciona a syntax Assert.Throws () no estilo de nUnit / xUnit para MsTest.

Instruções de alto nível: baixe o assembly e herde do BaseTest e você pode usar a syntax Assert.Throws () .

O método principal para a implementação do Throws é o seguinte:

 public static void Throws(Action task, string expectedMessage, ExceptionMessageCompareOptions options) where T : Exception { try { task(); } catch (Exception ex) { AssertExceptionType(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

MSTest agora tem uma function Assert.ThrowsException que pode ser usada assim:

 Assert.ThrowsException(() => { Story actual = PersonalSite.Services.Content.ExtractHeader(String.Empty); }); 

Eu não recomendo usar o atributo ExpectedException (pois é muito restrito 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 declaração bem projetado – seja fornecido por sua estrutura de teste ou escreva o seu próprio. Aqui está o que eu escrevi e usei.

 public static class ExceptionAssert { private static T GetException(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(Action action) where T : Exception { Propagates(action, ""); } public static void Propagates(Action action, string message) where T : Exception { GetException(action, message); } public static void Propagates(Action action, Action validation) where T : Exception { Propagates(action, validation, ""); } public static void Propagates(Action action, Action validation, string message) where T : Exception { validation(GetException(action, message)); } } 

Exemplos de usos:

  [TestMethod] public void Run_PropagatesWin32Exception_ForInvalidExeFile() { (test setup that might propagate Win32Exception) ExceptionAssert.Propagates( () => 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( () => 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 suportar um retorno de chamada de validação é uma idéia razoável, exceto que isso faz com que a syntax de chamada dessa afirmação seja muito diferente das outras declarações que eu uso.

Ao contrário de outros, eu uso ‘propaga’ em vez de ‘lances’, já que só podemos testar se uma exceção se propaga de uma chamada. Não podemos testar diretamente que uma exceção é lançada. Mas eu suponho que você poderia imaginar jogadas para dizer: jogadas e não pegas.

PENSAMENTO FINAL

Antes de mudar para esse tipo de abordagem, considerei usar o atributo ExpectedException quando um teste apenas verificava o tipo de exceção e usava um bloco try / catch se mais validação fosse necessária. Mas, não só teria que pensar sobre qual técnica usar para cada teste, mas mudar o código de uma técnica para outra conforme as necessidades mudaram não foi 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 assistente fornecido pelo @Richiban acima funciona muito bem, exceto que ele não manipula a situação em que uma exceção é lançada, mas não o tipo esperado. Os seguintes endereços que:

 using System; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace YourProject.Tests { public static class MyAssert { ///  /// Helper for Asserting that a function throws an exception of a particular type. ///  public static void Throws( 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)) ); } } } } 

Bem, eu vou resumir o que todos os outros aqui disseram antes … De qualquer forma, aqui está o código que eu construí de acordo com as boas respostas 🙂 Tudo o que resta a fazer é copiar e usar …

 ///  /// Checks to make sure that the input delegate throws a exception of type TException. ///  /// The type of exception expected. /// The method to execute to generate the exception. public static void AssertRaises(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."); } 

Como você mencionou o uso de outras classs de teste, uma opção melhor do que o atributo ExpectedException é usar o Should.Throw de Shoudly .

 Should.Throw(() => { MyDivideMethod(1, 0); }); 

Digamos que tenhamos uma exigência de que o cliente tenha um endereço para criar um pedido . Caso contrário, o método CreateOrderForCustomer deve resultar em um ArgumentException . Então poderíamos escrever:

 [TestMethod] public void NullUserIdInConstructor() { var customer = new Customer(name := "Justin", address := null}; Should.Throw(() => { var order = CreateOrderForCustomer(customer) }); } 

Isso é melhor do que usar um atributo ExpectedException porque estamos especificando o que deve lançar o erro. Isso torna os requisitos em nossos testes mais claros e também facilita o diagnóstico quando o teste falha.

Observe que também há um Should.ThrowAsync para teste de método asynchronous.

Confira o nUnit Docs para exemplos sobre:

 [ExpectedException( typeof( ArgumentException ) )] 

Isso vai depender de qual framework de teste você está usando?

Em MbUnit, por exemplo, você pode especificar a exceção esperada com um atributo para garantir que esteja obtendo a exceção que realmente espera.

 [ExpectedException(typeof(ArgumentException))] 

Como alternativa, você pode tentar testar exceções estão sendo lançadas com as próximas 2 linhas em seu teste.

 var testDelegate = () => MyService.Method(params); Assert.Throws(testDelegate); 

No caso de usar o NUnit , tente isto:

 Assert.That(() => { Your_Method_To_Test(); }, Throws.TypeOf().With.Message.EqualTo("Your_Specific_Message")); 

No teste de unidade interna do VS, se você quiser simplesmente verificar se “qualquer exceção” é lançada, mas não conhece o tipo, é possível usar uma captura de todos:

 [TestMethod] [ExpectedException(typeof(Exception), AllowDerivedTypes = true)] public void ThrowExceptionTest() { //... } 

Embora essa seja uma pergunta antiga, gostaria de acrescentar um novo pensamento à discussão. Eu estendi o padrão Arrange, Act, Assert para Expected, Arrange, Act, Assert. Você pode fazer um ponteiro de exceção esperado e, em seguida, declarar que foi atribuído. Isso parece mais limpo do que fazer suas afirmações em um bloco catch, deixando sua seção Act principalmente apenas para a linha de código para chamar o método em teste. Você também não precisa Assert.Fail(); ou return de vários pontos no código. Qualquer outra exceção lançada fará com que o teste falhe, porque não será detectado, e se uma exceção do seu tipo esperado for lançada, mas não foi a que você estava esperando, Afirmando contra a mensagem ou outras propriedades de a ajuda de exceção garante que seu teste não seja passado 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); }