Como testar se nenhuma exceção é lançada?

238

Eu sei que uma maneira de fazer isso seria:

@Test
public void foo(){
   try{
      //execute code that you expect not to throw Exceptions.
   }
   catch(Exception e){
      fail("Should not have thrown any exception");
   }
}

Existe alguma maneira mais limpa de fazer isso. (Provavelmente usando o Junit @Rule?)

Ankit Dhingra
fonte
10
Considera-se que um teste JUnit falhou se lançar alguma exceção que não seja uma exceção esperada. Geralmente, nenhuma exceção é esperada.
Raedwald
Não existe uma distinção entre falha e erro no JUnit? O primeiro significa que o teste falhou, o segundo significa que algo inesperado aconteceu.
Vituel
1
possível duplicata de Como posso testar se uma exceção específica não é lançada?
Ciro Santilli escreveu:

Respostas:

198

Você está abordando isso da maneira errada. Apenas teste sua funcionalidade: se uma exceção for lançada, o teste falhará automaticamente. Se nenhuma exceção for lançada, todos os seus testes ficarão verdes.

Percebi que essa pergunta gera interesse de tempos em tempos, por isso vou expandir um pouco.

Antecedentes do teste de unidade

Quando você faz testes de unidade, é importante definir para si mesmo o que você considera uma unidade de trabalho. Basicamente: uma extração da sua base de código que pode ou não incluir vários métodos ou classes que representa uma única peça de funcionalidade.

Ou, conforme definido em The art of Unit Testing, 2ª edição de Roy Osherove , página 11:

Um teste de unidade é um trecho de código automatizado que chama a unidade de trabalho que está sendo testada e, em seguida, verifica algumas suposições sobre um único resultado final dessa unidade. Um teste de unidade é quase sempre escrito usando uma estrutura de teste de unidade. Pode ser escrito facilmente e é executado rapidamente. É confiável, legível e sustentável. É consistente em seus resultados, desde que o código de produção não tenha sido alterado.

O que é importante perceber é que uma unidade de trabalho geralmente não é apenas um método, mas no nível muito básico é um método e depois disso é encapsulado por outra unidade de obras.

insira a descrição da imagem aqui

Idealmente, você deve ter um método de teste para cada unidade de trabalho separada, para poder sempre ver imediatamente onde as coisas estão dando errado. Neste exemplo, existe um método básico chamado getUserById()que retornará um usuário e há um total de 3 unidades de obras.

A primeira unidade de trabalho deve testar se um usuário válido está ou não sendo retornado no caso de entrada válida e inválida.
Quaisquer exceções que estão sendo lançadas pela fonte de dados devem ser tratadas aqui: se nenhum usuário estiver presente, deve haver um teste que demonstre que uma exceção será lançada quando o usuário não puder ser encontrado. Uma amostra disso pode ser a IllegalArgumentExceptionque é capturada com a @Test(expected = IllegalArgumentException.class)anotação.

Depois de ter tratado todos os seus casos de uso para esta unidade básica de trabalho, você sobe de nível. Aqui você faz exatamente o mesmo, mas apenas lida com as exceções que vêm do nível logo abaixo do atual. Isso mantém seu código de teste bem estruturado e permite que você percorra rapidamente a arquitetura para descobrir onde as coisas dão errado, em vez de ter que pular por todo o lugar.

Manipulação de entrada válida e defeituosa de um teste

Neste ponto, deve ficar claro como vamos lidar com essas exceções. Existem 2 tipos de entrada: entrada válida e entrada com defeito (a entrada é válida no sentido estrito, mas não está correta).

Quando você trabalha com entrada válida, está definindo a expectativa implícita de que qualquer teste que você escrever funcionará.

Tal chamada de método pode ficar assim: existingUserById_ShouldReturn_UserObject. Se esse método falhar (por exemplo: uma exceção for lançada), você saberá que algo deu errado e poderá começar a cavar.

Adicionando outro teste ( nonExistingUserById_ShouldThrow_IllegalArgumentException) que usa a entrada defeituosa e espera uma exceção, você pode ver se o seu método faz o que é suposto fazer com a entrada incorreta.

TL; DR

Você estava tentando fazer duas coisas em seu teste: verifique se há entradas válidas e com defeito. Dividindo isso em dois métodos, cada um fazendo uma coisa, você terá testes muito mais claros e uma visão geral muito melhor de onde as coisas dão errado.

Ao manter em mente a unidade de trabalho em camadas, você também pode reduzir a quantidade de testes necessários para uma camada que é mais alta na hierarquia, porque não é necessário contabilizar tudo o que pode ter dado errado nas camadas inferiores: o as camadas abaixo da atual são uma garantia virtual de que suas dependências funcionam e, se algo der errado, está na sua camada atual (supondo que as camadas inferiores não causem erros).

Jeroen Vannevel
fonte
2
O problema é que estou tentando TDD e um dos colaboradores que uso está lançando uma exceção. Então eu preciso testar o fato de que estou consumindo a exceção lançada pelo colaborador
Ankit Dhingra
6
Você está dizendo que sua funcionalidade depende do tratamento de uma exceção? É um cheiro de código: existem exceções para permitir que você pegue problemas de maneira elegante; eles não são usados ​​para controle de fluxo. Se você deseja testar um cenário no qual uma exceção deve ser lançada, use a expectedanotação. Se você deseja testar um cenário em que seu código falha e deseja ver se o erro foi tratado corretamente: use expectede talvez use asserts para determinar se ele foi resolvido.
Jeroen Vannevel 18/07/2013
O problema é que não consigo me recuperar da exceção que ocorreu no colaborador e tudo o que faço é registrar o problema usando um log.debug ("mensagem de erro"). Portanto, não há efeitos colaterais acontecendo como parte do bloco de captura que eu possa afirmar.
Ankit Dhingra
5
@JeroenVannevel, é perfeitamente válido testar se uma situação de erro que causa uma exceção seja tratada corretamente.
Thorbjørn Ravn Andersen
1
@dpk sim você pode. Você adiciona throws IllegalArgumentExceptionao seu teste. O que você deseja no final é que seu teste fique vermelho se houver uma exceção. Bem, adivinhe? Você não precisa escrever fail(). Como escreveu @Jeroen Vannevel: "se uma exceção for lançada, o teste falhará automaticamente".
Amedee Van Gasse
132

Eu me deparei com isso por causa da regra "squid: S2699" do SonarQube: "Adicione pelo menos uma asserção a este caso de teste."

Eu fiz um teste simples, cujo único objetivo era passar sem lançar exceções.

Considere este código simples:

public class Printer {

    public static void printLine(final String line) {
        System.out.println(line);
    }
}

Que tipo de afirmação pode ser adicionada para testar esse método? Claro, você pode tentar o problema, mas isso é apenas o inchaço do código.

A solução vem da própria JUnit.

Caso nenhuma exceção seja lançada e você queira ilustrar explicitamente esse comportamento, basta adicionar expectedcomo no exemplo a seguir:

@Test(expected = Test.None.class /* no exception expected */)
public void test_printLine() {
    Printer.printLine("line");
}

Test.None.class é o padrão para o valor esperado.

Sven Döring
fonte
30
Eu acho que essa é a melhor resposta. A resposta aceita é ótima, e o autor está correto ao apontar o cheiro do código. No entanto, ele realmente não respondeu à pergunta específica.
HellishHeat
4
é interessante notar que o valor padrão para o esperado é None, portanto, basta fazer anotações no método com @Test.
Oziomajnr 23/12/19
42

Com as assertivas fluentes do AssertJ 3.7.0 :

Assertions.assertThatCode(() -> toTest.method())
    .doesNotThrowAnyException();
denu
fonte
1
@emeraldhieu A questão nem é sobre assertJ, então como isso "diretamente" responde à pergunta da OP?
MauriceNino 22/10/19
41

O JUnit 5 (Jupiter) fornece três funções para verificar a ausência / presença de exceção:

assertAll​()

Afirma que todos os fornecidos executables
  não lançam exceções.

assertDoesNotThrow​()

Afirma que a execução do
  fornecido executable/ supplier
não gera nenhum tipo de exceção .

  Esta função está disponível
  desde JUnit 5.2.0 (29 de abril de 2018).

assertThrows​()

Indica que a execução do fornecido executable
lança uma exceção expectedType
  e retorna a exceção .

Exemplo

package test.mycompany.myapp.mymodule;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

class MyClassTest {

    @Test
    void when_string_has_been_constructed_then_myFunction_does_not_throw() {
        String myString = "this string has been constructed";
        assertAll(() -> MyClass.myFunction(myString));
    }

    @Test
    void when_string_has_been_constructed_then_myFunction_does_not_throw__junit_v520() {
        String myString = "this string has been constructed";
        assertDoesNotThrow(() -> MyClass.myFunction(myString));
    }

    @Test
    void when_string_is_null_then_myFunction_throws_IllegalArgumentException() {
        String myString = null;
        assertThrows(
            IllegalArgumentException.class,
            () -> MyClass.myFunction(myString));
    }

}
olibre
fonte
1
Esta é a melhor resposta agora. Outras respostas estão discutindo versões mais antigas do JUnit
Tejesh Raut
29

O Java 8 facilita muito isso, e o Kotlin / Scala duplamente.

Podemos escrever uma pequena classe de utilidade

class MyAssertions{
  public static void assertDoesNotThrow(FailingRunnable action){
    try{
      action.run()
    }
    catch(Exception ex){
      throw new Error("expected action not to throw, but it did!", ex)
    }
  }
}

@FunctionalInterface interface FailingRunnable { void run() throws Exception }

e então seu código se torna simplesmente:

@Test
public void foo(){
  MyAssertions.assertDoesNotThrow(() -> {
    //execute code that you expect not to throw Exceptions.
  }
}

Se você não tem acesso ao Java-8, eu usaria um recurso java dolorosamente antigo: blocos de códigos arbitrários e um simples comentário

//setup
Component component = new Component();

//act
configure(component);

//assert 
/*assert does not throw*/{
  component.doSomething();
}

E, finalmente, com o kotlin, uma linguagem pela qual me apaixonei recentemente:

fun (() -> Any?).shouldNotThrow() 
    = try { invoke() } catch (ex : Exception){ throw Error("expected not to throw!", ex) }

@Test fun `when foo happens should not throw`(){

  //...

  { /*code that shouldn't throw*/ }.shouldNotThrow()
}

Embora exista muito espaço para se mexer exatamente como você deseja expressar isso, eu sempre fui fã de afirmações fluentes .


A respeito de

Você está abordando isso da maneira errada. Apenas teste sua funcionalidade: se uma exceção for lançada, o teste falhará automaticamente. Se nenhuma exceção for lançada, todos os seus testes ficarão verdes.

Isso é correto em princípio, mas incorreto em conclusão.

Java permite exceções para o fluxo de controle. Isso é feito pelo próprio tempo de execução do JRE nas APIs, como Double.parseDoublevia a NumberFormatExceptione Paths.getvia a InvalidPathException.

Como você escreveu um componente que valida seqüências de números Double.ParseDouble, talvez usando um Regex, talvez um analisador escrito à mão ou talvez algo que incorpore outras regras de domínio que restrinjam o intervalo de um dobro a algo específico, qual a melhor forma de testar isso componente? Penso que um teste óbvio seria afirmar que, quando a string resultante é analisada, nenhuma exceção é lançada. Eu escreveria esse teste usando o bloco acima assertDoesNotThrowou acima /*comment*/{code}. Algo como

@Test public void given_validator_accepts_string_result_should_be_interpretable_by_doubleParseDouble(){
  //setup
  String input = "12.34E+26" //a string double with domain significance

  //act
  boolean isValid = component.validate(input)

  //assert -- using the library 'assertJ', my personal favourite 
  assertThat(isValid).describedAs(input + " was considered valid by component").isTrue();
  assertDoesNotThrow(() -> Double.parseDouble(input));
}

Gostaria também de encorajá-lo a parametrizar este teste ao inputusar Theoriesou Parameterizedpara que você possa reutilizar mais facilmente esse teste para outras entradas. Como alternativa, se você quiser ficar exótico, pode optar por uma ferramenta de geração de teste (e isso ). O TestNG tem melhor suporte para testes parametrizados.

O que eu acho particularmente desagradável é a recomendação de uso @Test(expectedException=IllegalArgumentException.class), essa exceção é perigosamente ampla . Se o seu código for alterado de forma que o componente sob o construtor do teste tenha if(constructorArgument <= 0) throw IllegalArgumentException(), e seu teste esteja fornecendo 0 para esse argumento por ser conveniente - e isso é muito comum, porque a boa geração de dados de teste é um problema surpreendentemente difícil -, seu teste será barra verde mesmo que não teste nada. Tal teste é pior que inútil.

Groostav
fonte
2
(sobre o uso da exceção esperada) Desde o JUnit 4.13, você pode usar Assert.assertThrowspara verificar se algum código gera uma exceção.
MageWind
22

Se você tiver a sorte de detectar todos os erros no seu código. Você pode fazer estupidamente

class DumpTest {
    Exception ex;
    @Test
    public void testWhatEver() {
        try {
            thisShouldThrowError();
        } catch (Exception e) {
            ex = e;
        }
        assertEquals(null,ex);
    }
}
Ben Tennyson
fonte
1
Apenas uma pequena sugestão, Exception exdeve ser = null;antes que você possa testá-lo.
Denees
4
Esta não é uma ótima solução. Se o método que não deve lançar uma exceção for lançado, você não receberá uma mensagem de erro útil. Basta chamar o método que não deve lançar uma exceção e testar seu valor de retorno (ou efeitos colaterais, como registrar a exceção). Se posteriormente lançar inesperadamente uma exceção, o teste falhará.
NamshubWriter
3
Ou apenas coloque Assert.fail () na captura, IMO mais fácil e mais bonita.
Isaac.hazan 17/11/2015
Sim, eu concordo com você. Mais uma forma é adicionar uma anotação na parte superior do método @Test (esperado = InvalidRequestException.class)
Ben Tennyson
Sua ortografia incorreta é confusa: thisShouldThroughError -> thisShouldThrowError .
Oscar Bravo
7

JUnit5 adiciona o método assertAll () para essa finalidade exata.

assertAll( () -> foo() )

fonte: API do JUnit 5

razalghul
fonte
7

Embora este post tenha 6 anos, no entanto, muita coisa mudou no mundo do Junit. Com o Junit5, agora você pode usar

org.junit.jupiter.api.Assertions.assertDoesNotThrow()

Ex:

public void thisMethodDoesNotThrowException(){
   System.out.println("Hello There");
}

@Test
public void test_thisMethodDoesNotThrowException(){
  org.junit.jupiter.api.Assertions.assertDoesNotThrow(
      ()-> thisMethodDoesNotThrowException()
    );
}

Espero que ajude as pessoas que estão usando a versão mais recente do Junit5

Dinesh Arora
fonte
Eu gostaria que houvesse uma maneira de especificar classe de exceção concreta aqui. Eu tenho que fazer isso dentro Awaitilityé untilAsserted(ThrowingRunnable assertion). Atualmente, o sistema em teste está lançando uma exceção específica no ThrowingRunnable que forneço, mas quero dar um tempo até que ele pare de fazê-lo. No entanto, se isso gerasse uma exceção diferente, gostaria que o teste falhasse instantaneamente.
Ubeogesh 17/06
1

Se você deseja testar se o seu destino de teste consome a exceção. Apenas deixe o teste como (colaborador simulado usando jMock2):

@Test
public void consumesAndLogsExceptions() throws Exception {

    context.checking(new Expectations() {
        {
            oneOf(collaborator).doSth();
            will(throwException(new NullPointerException()));
        }
    });

    target.doSth();
 }

O teste passaria se o seu destino consumisse a exceção lançada, caso contrário, o teste falharia.

Se você deseja testar sua lógica de consumo de exceção, as coisas ficam mais complexas. Sugiro delegar o consumo a um colaborador que pode ser ridicularizado. Portanto, o teste pode ser:

@Test
public void consumesAndLogsExceptions() throws Exception {
    Exception e = new NullPointerException();
    context.checking(new Expectations() {
        {
            allowing(collaborator).doSth();
            will(throwException(e));

            oneOf(consumer).consume(e);
        }
    });

    target.doSth();
 }

Mas, às vezes, é superprojetado, se você quiser apenas registrá-lo. Nesse caso, este artigo ( http://java.dzone.com/articles/monitoring-declarative-transac , http://blog.novoj.net/2008/09/09/20/testing-aspect-pointcuts-is-there -an-easy-way / ) pode ajudar se você insistir em tdd neste caso.

Yugang Zhou
fonte
1

Use assertNull (...)

@Test
public void foo() {
    try {
        //execute code that you expect not to throw Exceptions.
    } catch (Exception e){
        assertNull(e);
    }
}
Mike Rapadas
fonte
6
Eu diria que isso é enganoso. O bloco catch nunca é atingido e, portanto, também assertNullnunca é executado. No entanto, o leitor rápido tem a impressão de que é feita uma afirmação que realmente verifica o caso que não é de lançamento. Em outras palavras: se o bloco catch for atingido, a exceção será sempre não nula - portanto, poderá ser substituída por uma simples fail.
Andreas
enganoso de fato, ..... mas espere, ... oh, eu vejo ... assertNull(e)relatará o teste como falhou, como afirmado enão pode estar nullno catchbloco ... Mike, isso é apenas uma programação estranha: - /. .. sim, pelo menos uso fail()como Andreas diz
Julien
1

Você pode esperar que a exceção não seja lançada criando uma regra.

@Rule
public ExpectedException expectedException = ExpectedException.none();
LazerBanana
fonte
ExpectedExceptions são usadas para declarar exceções lançadas. O código que você fornece é apenas para inicializar a regra, para que você possa adicionar seus requisitos para as asserções. Esse código em si não adiciona nenhum valor. O javadoc também afirma o seguinte: "/ ** * Retorna uma {@linkplain TestRule rule} que espera que nenhuma exceção seja * lançada (idêntica ao comportamento sem essa regra). * /" Portanto, ele terá exatamente o mesmo resultado que sem ela .
Pim Hazebroek
Concordo com você e não o usaria dessa maneira, mas é possível afirmar que nenhuma exceção foi lançada. Se o teste for aprovado, deve haver o suficiente para dizer que a exceção não foi lançada, mas, por outro lado, se houver uma pergunta, deve ser necessário. E raramente, mas ainda às vezes, é bom tê-lo visível. E se o código e as circunstâncias mudassem e não tivéssemos teste para algum caso específico?
LazerBanana
Estou curioso para ver como você afirmaria isso com exceção esperada. E sim, se os requisitos mudarem e você não tiver um teste para um caso de borda específico, você está ferrado ;-) sempre cubra todos os casos de canto.
Pim Hazebroek
O que você quer dizer? você não afirma, espera. Nesse caso, você não espera nenhuma exceção. Não tenho certeza do que você está falando.
LazerBanana
0

Essa pode não ser a melhor maneira, mas definitivamente garante que a exceção não seja lançada no bloco de código que está sendo testado.

import org.assertj.core.api.Assertions;
import org.junit.Test;

public class AssertionExample {

    @Test
    public void testNoException(){
        assertNoException();
    }    

    private void assertException(){
        Assertions.assertThatThrownBy(this::doNotThrowException).isInstanceOf(Exception.class);
    }

    private void assertNoException(){
        Assertions.assertThatThrownBy(() -> assertException()).isInstanceOf(AssertionError.class);
    }

    private void doNotThrowException(){
        //This method will never throw exception
    }
}
MLS
fonte
0

Você pode fazer isso usando uma @Rule e, em seguida, chame o método reportMissingExceptionWithMessage, conforme mostrado abaixo: Esse é o código Scala.

insira a descrição da imagem aqui

Crenguta S
fonte
1
private val? Qual é esse idioma? Claramente, não Java; p E, por favor, não forneça código como uma captura de tela, não é bem-vindo.
Andremoniy 29/07/19
Eu vejo que você mencionou é Scala, mas dizendo que ele pode ser facilmente feito em Java não é um argumento forte, lamento
Andremoniy
Tirei a parte que te incomodava. Vou tentar substituir a imagem também. Ainda não descobri como adicionar código ..
Crenguta S
-1

O seguinte falha no teste de todas as exceções, marcadas ou desmarcadas:

@Test
public void testMyCode() {

    try {
        runMyTestCode();
    } catch (Throwable t) {
        throw new Error("fail!");
    }
}
Rocky Inde
fonte
-1

Você pode criar qualquer tipo de suas próprias afirmações com base nas afirmações de junit:

static void assertDoesNotThrow(Executable executable) {
    assertDoesNotThrow(executable, "must not throw");
}
static void assertDoesNotThrow(Executable executable, String message) {
    try {
        executable.execute();
    } catch (Throwable err) {
        fail(message);
    }
}

E teste:

//the following will succeed
assertDoesNotThrow(()->methodMustNotThrow(1));
assertDoesNotThrow(()->methodMustNotThrow(1), "fail with specific message: facepalm");
//the following will fail
assertDoesNotThrow(()->methodMustNotThrow(2));
assertDoesNotThrow(()-> {throw new Exception("Hello world");}, "Fail: must not trow");

De um modo geral, existe a possibilidade de falhar instantaneamente ("bla bla bla") no teste em qualquer cenário, em qualquer lugar onde isso faça sentido. Por exemplo, use-o em um bloco try / catch para falhar se algo for lançado no caso de teste:

try{methodMustNotThrow(1);}catch(Throwable e){fail("must not throw");}
//or
try{methodMustNotThrow(1);}catch(Throwable e){Assertions.fail("must not throw");}

Esta é a amostra do método que testamos, supondo que tenhamos um método que não deve falhar em circunstâncias específicas, mas pode falhar:

void methodMustNotThrow(int x) throws Exception{
    if (x == 1) return;
    throw new Exception();
}

O método acima é uma amostra simples. Mas isso funciona para situações complexas, nas quais a falha não é tão óbvia. Existem as importações:

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import static org.junit.jupiter.api.Assertions.*;
armagedescu
fonte
Há uma opção bastante melhor para verificar se a asserção não foi lançada e não envolve a criação de código personalizado. @Rule é um deles
Vargan
@ Vargan Eu apontei o método para criar suas próprias asserções da maneira que ele é projetado pela JUnit, especialmente com a finalidade de criar suas próprias asserções. A JUnit fornece que, por design, especialmente para esse fim, para criar suas próprias regras, estenda o comportamento da JUnit com afirmações que ainda não foram implementadas. Porque nem tudo é implementado neste mundo. Essas asserções funcionam de maneira idêntica à da JUnit, em termos de aprovação ou falha, além de relatar falhas.
armagedescu 26/03