Como você afirma que uma certa exceção é lançada nos testes do JUnit 4?


2000

Como posso usar o JUnit4 por idioma para testar se algum código gera uma exceção?

Embora eu possa certamente fazer algo assim:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  boolean thrown = false;

  try {
    foo.doStuff();
  } catch (IndexOutOfBoundsException e) {
    thrown = true;
  }

  assertTrue(thrown);
}

Lembro-me de que há uma anotação ou um Assert.xyz ou algo que é muito menos exigente e muito mais dentro do espírito da JUnit para esse tipo de situação.


21
O problema com qualquer outra abordagem, mas é que eles invariavelmente terminam o teste depois que a exceção é lançada. Por outro lado, muitas vezes ainda quero ligar org.mockito.Mockito.verifycom vários parâmetros para garantir que certas coisas aconteçam (como um serviço de logger foi chamado com os parâmetros corretos) antes que a exceção fosse lançada.
ZeroOne 17/01

5
Você pode ver como testar exceções na página wiki do JUnit github.com/junit-team/junit/wiki/Exception-testing
PhoneixS

6
@ZeroOne - Para isso, eu teria dois testes diferentes - um para a exceção e outro para verificar a interação com o seu mock.
Tddmonkey

Existe uma maneira de fazer isso com o JUnit 5. Atualizei minha resposta abaixo.
Dilini Rajapaksha

Respostas:


2363

Depende da versão do JUnit e do que afirmam as bibliotecas usadas.

A resposta original para JUnit <= 4.12foi:

@Test(expected = IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {

    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);

}

Embora a resposta https://stackoverflow.com/a/31826781/2986984 tenha mais opções para JUnit <= 4.12.

Referência:


66
Esse trecho de código não funcionará se você esperar uma exceção apenas em algum lugar do código e não um cobertor como este.
Oh Chin Boon

4
@skaffman Isso não iria funcionar com org.junit.experimental.theories.Theory Gerido por org.junit.experimental.theories.Theories
Artem Oboturov

74
Roy Osherove desencoraja esse tipo de teste de exceção em A arte do teste de unidade , pois a exceção pode estar em qualquer lugar dentro do teste e não apenas dentro da unidade sob teste.
precisa saber é o seguinte

21
Não concordo com @ Kiview / Roy Osherove. Na minha opinião, os testes devem ser de comportamento, não de implementação. Ao testar se um método específico pode gerar um erro, você está vinculando seus testes diretamente à implementação. Eu argumentaria que o teste no método mostrado acima fornece um teste mais valioso. A ressalva que eu acrescentaria é que, nesse caso, eu testaria uma exceção personalizada, para que eu saiba que estou recebendo a exceção que realmente quero.
Nickbdyer

6
Nem. Eu quero testar o comportamento da classe. O importante é que, se eu tentar recuperar algo que não está lá, recebo uma exceção. O fato de que a estrutura de dados é a ArrayListque responde get()é irrelevante. Se eu escolhesse no futuro mudar para uma matriz primitiva, teria que alterar essa implementação de teste. A estrutura de dados deve estar oculta, para que o teste possa se concentrar no comportamento da classe .
Nickbdyer

1317

Edit: Agora que o JUnit 5 e o JUnit 4.13 foram lançados, a melhor opção seria usar Assertions.assertThrows() (para o JUnit 5) e Assert.assertThrows()(para o JUnit 4.13). Veja minha outra resposta para obter detalhes.

Se você não migrou para o JUnit 5, mas pode usar o JUnit 4.7, use a ExpectedExceptionRegra:

public class FooTest {
  @Rule
  public final ExpectedException exception = ExpectedException.none();

  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    exception.expect(IndexOutOfBoundsException.class);
    foo.doStuff();
  }
}

Isso é muito melhor do que @Test(expected=IndexOutOfBoundsException.class)porque o teste falhará se IndexOutOfBoundsExceptionfor lançado antesfoo.doStuff()

Consulte este artigo para obter detalhes


14
@skaffman - Se eu entendi isso corretamente, parece que o exception.expect está sendo aplicado apenas em um teste, não em toda a classe.
Bacar

5
Se a exceção que esperamos ser lançada for uma exceção verificada, devemos adicionar jogadas ou tentar pegar ou testar essa situação de outra maneira?
Mohammad Jafar Mashhadi

5
@ MartinTrummer Nenhum código deve ser executado após foo.doStuff (), pois a exceção é lançada e o método é encerrado. Ter código após uma exceção esperada (com a exceção de encerrar os recursos em um finalmente) é inútil, pois nunca deve ser executado se a exceção for lançada.
Jason Thompson

9
Essa é a melhor abordagem. Existem duas vantagens aqui, em comparação com a solução de skaffman. Em primeiro lugar, a ExpectedExceptionclasse tem maneiras de corresponder à mensagem da exceção ou até de escrever seu próprio correspondente que depende da classe de exceção. Em segundo lugar, você pode definir sua expectativa imediatamente antes da linha de código que você espera lançar a exceção - o que significa que seu teste falhará se a linha de código errada lançar a exceção; considerando que não há como fazer isso com a solução de skaffman.
Dawood ibn Kareem

5
@MJafarMash se a exceção que você espera lançar estiver marcada, você poderá adicionar essa exceção à cláusula throws do método de teste. Você faz o mesmo sempre que estiver testando um método declarado para lançar uma exceção verificada, mesmo que a exceção não seja acionada no caso de teste específico.
NamshubWriter

472

Cuidado ao usar a exceção esperada, pois ela apenas afirma que o método lançou essa exceção, e não uma linha de código específica no teste.

Costumo usar isso para testar a validação de parâmetros, porque esses métodos geralmente são muito simples, mas testes mais complexos podem ser melhor atendidos com:

try {
    methodThatShouldThrow();
    fail( "My method didn't throw when I expected it to" );
} catch (MyException expectedException) {
}

Aplique julgamento.


95
Talvez eu seja da velha escola, mas ainda prefiro isso. Também me fornece um local para testar a exceção: às vezes, tenho exceções com getters para determinados valores, ou posso simplesmente procurar um valor específico na mensagem (por exemplo, procurando "xyz" na mensagem "código não reconhecido 'xyz' ").
Rodney Gitzel 6/10/10

3
Acho que a abordagem do NamshubWriter oferece o melhor dos dois mundos.
Eddie

4
Usando ExpectedException, você poderia chamar N exception.expect por método para testar como este exception.expect (IndexOutOfBoundsException.class); foo.doStuff1 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff2 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff3 ();
usar o seguinte comando

10
@ user1154664 Na verdade, você não pode. Usando ExpectedException, você só pode testar se um método lança uma exceção, porque quando esse método é chamado, o teste para de executar porque lançou a exceção esperada!
NamshubWriter

2
Sua primeira frase não é verdadeira. Ao usar ExpectedException, a coisa normal a fazer é definir a expectativa imediatamente antes da linha que você espera lançar a exceção. Dessa forma, se uma linha anterior lançar a exceção, ela não acionará a regra e o teste falhará.
Dawood ibn Kareem

213

Como respondido anteriormente, existem muitas maneiras de lidar com exceções no JUnit. Mas com o Java 8, existe outro: usando o Lambda Expressions. Com as Expressões Lambda, podemos obter uma sintaxe como esta:

@Test
public void verifiesTypeAndMessage() {
    assertThrown(new DummyService()::someMethod)
            .isInstanceOf(RuntimeException.class)
            .hasMessage("Runtime exception occurred")
            .hasMessageStartingWith("Runtime")
            .hasMessageEndingWith("occurred")
            .hasMessageContaining("exception")
            .hasNoCause();
}

assertThrown aceita uma interface funcional, cujas instâncias podem ser criadas com expressões lambda, referências de método ou referências de construtor. assertThrown, aceitando essa interface, esperará e estará pronto para lidar com uma exceção.

Esta é uma técnica relativamente simples, mas poderosa.

Dê uma olhada nesta postagem do blog que descreve esta técnica: http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

O código fonte pode ser encontrado aqui: https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

Divulgação: Sou o autor do blog e do projeto.


2
Gosto dessa solução, mas posso fazer o download em um repositório de ferramentas?
Selwyn 31/03

@Airduster uma implementação desta ideia que está disponível no Maven é stefanbirkner.github.io/vallado
NamshubWriter

6
@CristianoFontes uma versão mais simples desta API está prevista para o JUnit 4.13. Veja github.com/junit-team/junit/commit/…
NamshubWriter

@RafalBorowiec tecnicamente, new DummyService()::someMethodé um MethodHandle, mas essa abordagem funciona igualmente bem com expressões lambda.
28517 Andy

@NamshubWriter, parece que junit 4,13 foi abandonada em favor de junit 5: stackoverflow.com/questions/156503/...
Vadzim

154

em junho, há quatro maneiras de testar a exceção.

junit5.x

  • para junit5.x, você pode usar assertThrowso seguinte

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        Throwable exception = assertThrows(IndexOutOfBoundsException.class, () -> foo.doStuff());
        assertEquals("expected messages", exception.getMessage());
    }

junit4.x

  • para junit4.x, use o atributo opcional 'esperado' da Teste de Anonação

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
  • para junit4.x, use a regra ExpectedException

    public class XxxTest {
        @Rule
        public ExpectedException thrown = ExpectedException.none();
    
        @Test
        public void testFooThrowsIndexOutOfBoundsException() {
            thrown.expect(IndexOutOfBoundsException.class)
            //you can test the exception message like
            thrown.expectMessage("expected messages");
            foo.doStuff();
        }
    }
  • você também pode usar a maneira clássica de try / catch amplamente usada na estrutura de 3 de junho

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        try {
            foo.doStuff();
            fail("expected exception was not occured.");
        } catch(IndexOutOfBoundsException e) {
            //if execution reaches here, 
            //it indicates this exception was occured.
            //so we need not handle it.
        }
    }
  • tão

    • se você gosta de 5 de junho, então você deve gostar do primeiro
    • a segunda maneira é usada quando você deseja apenas testar o tipo de exceção
    • o primeiro e o último dois são usados ​​quando você deseja testar a mensagem de exceção
    • se você usa o dia 3 de junho, o quarto é o preferido
  • para obter mais informações, você pode ler este documento e o guia do usuário do junit5 para obter detalhes.


6
Para mim, esta é a melhor resposta, abrange todas as formas muito claramente, obrigado! Pessoalmente eu continuar usando a opção 3, mesmo com Junit4 para facilitar a leitura, para evitar bloco catch vazio você também pode pegar Throwable e tipo assert de e
Nicolas Cornette

É possível usar ExpectedException para esperar a exceção verificada?
Miuser #

Tudo o que é é um acúmulo das três principais respostas. Na IMO, essa resposta nem deveria ter sido postada se não estiver adicionando nada de novo. Apenas respondendo (uma pergunta popular) para o representante. Muito inútil.
Paul Samsotha 5/05/19

certeza, porque você pode passar qualquer tipo derivado de Trowableao método ExpectedException.expect. por favor, veja a sua assinatura . @miuser
walsh

116

tl; dr

  • pós-JDK8: Use AssertJ ou lambdas customizadas para afirmar um comportamento excepcional .

  • pré-JDK8: Vou recomendar o velho bom try- catchbloco. ( Não esqueça de adicionar uma fail()afirmação antes do catchbloco )

Independentemente do Junit 4 ou JUnit 5.

a longa história

É possível escrever você mesmo e fazê-lo try - catchbloqueie ou use as ferramentas JUnit ( @Test(expected = ...)ou o@Rule ExpectedException recurso de regra JUnit).

Mas essas maneiras não são tão elegantes e não combinam bem a legibilidade com outras ferramentas. Além disso, as ferramentas JUnit têm algumas armadilhas.

  1. O bloco try- catchvocê deve escrever o bloco em torno do comportamento testado e escrever a asserção no bloco catch, isso pode ser bom, mas muitos acham que esse estilo interrompe o fluxo de leitura de um teste. Além disso, você precisa escrever um Assert.failno final do trybloco. Caso contrário, o teste pode perder um lado das afirmações; O PMD , o findbugs ou o Sonar identificarão esses problemas.

  2. O @Test(expected = ...)recurso é interessante, pois você pode escrever menos código e, em seguida, escrever esse teste é menos propenso a erros de codificação. Mas essa abordagem está faltando em algumas áreas.

    • Se o teste precisar verificar coisas adicionais na exceção, como a causa ou a mensagem (boas mensagens de exceção são realmente importantes, ter um tipo de exceção preciso pode não ser suficiente).
    • Além disso, como a expectativa é contida no método, dependendo de como o código testado é gravado, a parte errada do código de teste pode gerar a exceção, levando ao teste de falso positivo e não tenho certeza de que PMD , findbugs ou Sonar dará dicas sobre esse código.

      @Test(expected = WantedException.class)
      public void call2_should_throw_a_WantedException__not_call1() {
          // init tested
          tested.call1(); // may throw a WantedException
      
          // call to be actually tested
          tested.call2(); // the call that is supposed to raise an exception
      }
  3. A ExpectedExceptionregra também é uma tentativa de corrigir as advertências anteriores, mas parece um pouco complicado de usar, pois usa um estilo de expectativa, os usuários do EasyMock conhecem muito bem esse estilo. Pode ser conveniente para alguns, mas se você seguir os princípios do Behavior Driven Development (BDD) ou do Organize Act Assert (AAA), a ExpectedExceptionregra não se ajustará ao estilo de escrita. Além disso, ele pode sofrer do mesmo problema que o @Testcaminho, dependendo de onde você coloca a expectativa.

    @Rule ExpectedException thrown = ExpectedException.none()
    
    @Test
    public void call2_should_throw_a_WantedException__not_call1() {
        // expectations
        thrown.expect(WantedException.class);
        thrown.expectMessage("boom");
    
        // init tested
        tested.call1(); // may throw a WantedException
    
        // call to be actually tested
        tested.call2(); // the call that is supposed to raise an exception
    }

    Mesmo a exceção esperada é colocada antes da instrução de teste, ele interrompe o fluxo de leitura se os testes seguirem BDD ou AAA.

    Veja também este comentário edição de no JUnit do autor de ExpectedException. O JUnit 4.13-beta-2 até deprecia este mecanismo:

    Puxar pedido # 1519 : descontinuar ExpectedException

    O método Assert.assertThrows fornece uma maneira melhor de verificar exceções. Além disso, o uso de ExpectedException é suscetível a erros quando usado com outras regras como TestWatcher, porque a ordem das regras é importante nesse caso.

Portanto, essas opções acima têm toda sua carga de advertências e claramente não são imunes a erros de codificação.

  1. Existe um projeto que eu tomei conhecimento depois de criar esta resposta que parece promissora, é uma exceção .

    Como a descrição do projeto diz, ele permite que um codificador escreva em uma linha fluente de código capturando a exceção e ofereça essa exceção para a última afirmação. E você pode usar qualquer biblioteca de asserções como Hamcrest ou AssertJ .

    Um exemplo rápido retirado da página inicial:

    // given: an empty list
    List myList = new ArrayList();
    
    // when: we try to get the first element of the list
    when(myList).get(1);
    
    // then: we expect an IndexOutOfBoundsException
    then(caughtException())
            .isInstanceOf(IndexOutOfBoundsException.class)
            .hasMessage("Index: 1, Size: 0") 
            .hasNoCause();

    Como você pode ver que o código é realmente simples, você captura a exceção em uma linha específica, a thenAPI é um alias que usará as APIs do AssertJ (semelhante ao uso assertThat(ex).hasNoCause()...). Em algum momento, o projeto contou com o FEST-Assert, o ancestral do AssertJ . EDIT: Parece que o projeto está distribuindo um suporte ao Java 8 Lambdas.

    Atualmente, esta biblioteca possui duas deficiências:

    • No momento da redação deste artigo, é digno de nota que essa biblioteca é baseada no Mockito 1.x, pois cria uma simulação do objeto testado nos bastidores. Como o Mockito ainda não está atualizado, esta biblioteca não pode funcionar com classes ou métodos finais . E mesmo que fosse baseado no Mockito 2 na versão atual, isso exigiria declarar um criador de simulação global ( inline-mock-maker), algo que pode não ser o que você deseja, pois esse criador de simulação tem desvantagens diferentes das do criador de simulação comum.

    • Requer ainda outra dependência de teste.

    Esses problemas não se aplicarão quando a biblioteca suportar lambdas. No entanto, a funcionalidade será duplicada pelo conjunto de ferramentas AssertJ.

    Levando tudo em conta, se você não quiser usar a ferramenta catch-exception, vou recomendar a boa maneira antiga do bloco try- catch, pelo menos até o JDK7. E para usuários do JDK 8, você pode preferir usar o AssertJ, pois oferece mais do que apenas declarar exceções.

  2. Com o JDK8, as lambdas entram em cena e provaram ser uma maneira interessante de afirmar um comportamento excepcional. O AssertJ foi atualizado para fornecer uma API fluente e agradável para afirmar um comportamento excepcional.

    E um teste de amostra com AssertJ :

    @Test
    public void test_exception_approach_1() {
        ...
        assertThatExceptionOfType(IOException.class)
                .isThrownBy(() -> someBadIOOperation())
                .withMessage("boom!"); 
    }
    
    @Test
    public void test_exception_approach_2() {
        ...
        assertThatThrownBy(() -> someBadIOOperation())
                .isInstanceOf(Exception.class)
                .hasMessageContaining("boom");
    }
    
    @Test
    public void test_exception_approach_3() {
        ...
        // when
        Throwable thrown = catchThrowable(() -> someBadIOOperation());
    
        // then
        assertThat(thrown).isInstanceOf(Exception.class)
                          .hasMessageContaining("boom");
    }
  3. Com uma reescrita quase completa do JUnit 5, as asserções foram aprimoradas um pouco, elas podem ser interessantes como uma maneira pronta para afirmar uma exceção adequada. Mas realmente a API de afirmação ainda é um pouco ruim, não há nada lá fora assertThrows.

    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
        Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek());
    
        Assertions.assertEquals("...", t.getMessage());
    }

    Como você notou assertEquals ainda está voltandovoid e, como tal, não permite asserções de encadeamento como o AssertJ.

    Além disso, se você se lembrar de um conflito de nome com Matcherou Assert, esteja preparado para enfrentar o mesmo conflito com Assertions.

Gostaria de concluir que hoje (03-03-2017) a facilidade de uso do AssertJ , a API detectável, o ritmo acelerado de desenvolvimento e como dependência de teste de fato são a melhor solução com o JDK8, independentemente da estrutura de teste (JUnit ou não), os JDKs anteriores deveriam confiar em try-catch blocos mesmo que se sintam desajeitado.

Esta resposta foi copiada de outra pergunta que não tem a mesma visibilidade, eu sou o mesmo autor.


1
A adição da dependência org.junit.jupiter: junit-jupiter-engine: 5.0.0-RC2 (além da junit já existente: junit: 4.12) para poder usar assertThrows talvez não seja a solução preferida, mas não causou nenhuma problemas para mim.
ANRE

Sou fã do uso da regra ExpectedException, mas sempre me incomodou o fato de quebrar com o AAA. Você escreveu um excelente artigo para descrever todas as abordagens diferentes e definitivamente me incentivou a experimentar o AssertJ :-) Obrigado!
Pim Hazebroek,

@PimHazebroek thanks. A API do AssertJ é bastante rica. Melhor na minha opinião, o que o JUnit propõe fora da caixa.
Brice

64

Agora que o JUnit 5 e o JUnit 4.13 foram lançados, a melhor opção seria usar Assertions.assertThrows() (para o JUnit 5) e Assert.assertThrows()(para o JUnit 4.13). Consulte o Guia do Usuário do Junit 5 .

Aqui está um exemplo que verifica se uma exceção foi lançada e usa a Verdade para fazer afirmações na mensagem de exceção:

public class FooTest {
  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    IndexOutOfBoundsException e = assertThrows(
        IndexOutOfBoundsException.class, foo::doStuff);

    assertThat(e).hasMessageThat().contains("woops!");
  }
}

As vantagens sobre as abordagens nas outras respostas são:

  1. Incorporado ao JUnit
  2. Você recebe uma mensagem de exceção útil se o código no lambda não lança uma exceção e um rastreamento de pilha se lança uma exceção diferente
  3. Conciso
  4. Permite que seus testes sigam Arrange-Act-Assert
  5. Você pode indicar com precisão qual código espera lançar a exceção
  6. Você não precisa listar a exceção esperada na throwscláusula
  7. Você pode usar a estrutura de asserção de sua escolha para fazer asserções sobre a exceção capturada

Um método semelhante será adicionado org.junit Assertna JUnit 4.13.


Essa abordagem é clara, mas não vejo como isso permite que o nosso teste siga "Organizar-agir-afirmar", pois precisamos agrupar a parte "Agir" em um "assertThrow", que é uma afirmação.
Clockwork

@ Clockwork O lambda é o "ato". O objetivo do Organizar-Agir-Assertar é tornar o código limpo e simples (e, portanto, fácil de entender e manter). Como você afirmou, essa abordagem é limpa.
NamshubWriter 11/03/19

Eu ainda esperava poder afirmar o lance e a exceção no final do teste, na parte "afirmar". Nessa abordagem, você precisa envolver o ato em uma primeira declaração para capturá-lo primeiro.
Clockwork

Isso exigiria mais código em todos os testes para fazer a afirmação. Isso é mais código e seria propenso a erros.
NamshubWriter 11/03/19

43

Que tal isso: Capture uma exceção muito geral, verifique se ela sai do bloco de captura e afirme que a classe da exceção é o que você espera que seja. Essa afirmação falhará se a) a exceção for do tipo errado (por exemplo, se você tiver um ponteiro nulo) eb) a exceção nunca foi lançada.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

  try {
    foo.doStuff();
  } catch (Throwable ex) {
    e = ex;
  }

  assertTrue(e instanceof IndexOutOfBoundsException);
}

3
Além disso, você não verá que tipo de exceção ex está nos resultados do teste quando chegar o dia em que o teste falhará.
jontejj

Isso pode ser melhorado um pouco, alterando a forma como você afirma no final. assertEquals(ExpectedException.class, e.getClass())mostrará os valores reais e esperados quando o teste falhar.
Cypher

37

Solução estilo BDD : JUnit 4 + Exceção de captura + AssertJ

import static com.googlecode.catchexception.apis.BDDCatchException.*;

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(() -> foo.doStuff());

    then(caughtException()).isInstanceOf(IndexOutOfBoundsException.class);

}

Dependências

eu.codearte.catch-exception:catch-exception:2.0

36

Usando uma assertJ AssertJ , que pode ser usada ao lado do JUnit:

import static org.assertj.core.api.Assertions.*;

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  Foo foo = new Foo();

  assertThatThrownBy(() -> foo.doStuff())
        .isInstanceOf(IndexOutOfBoundsException.class);
}

É melhor do que @Test(expected=IndexOutOfBoundsException.class)porque garante que a linha esperada no teste lançou a exceção e permite que você verifique mais detalhes sobre a exceção, como mensagem, mais fácil:

assertThatThrownBy(() ->
       {
         throw new Exception("boom!");
       })
    .isInstanceOf(Exception.class)
    .hasMessageContaining("boom");

Instruções Maven / Gradle aqui.


maneira mais concisa e ninguém a aprecia, estranho. Só tenho um problema com a biblioteca assertJ, assertThat entra em conflito com o junit. mais sobre assertJ throwby: JUnit: testando exceções com Java 8 e AssertJ 3.0.0 ~ Codeleak.pl
ycomp 21/03

@ycomp Bem, é uma nova resposta para uma pergunta muito antiga, então a diferença de pontuação é enganosa.
weston 24/03

Essa é provavelmente a melhor solução se for possível usar o Java 8 e o AssertJ!
Pierre Henry

@ycomp Eu suspeito que esse conflito de nomes possa ter sido planejado: a biblioteca AssertJ, portanto, incentiva você a nunca usar o JUnit assertThat, sempre o AssertJ. Além disso, o método JUnit retorna apenas um tipo "regular", enquanto o método AssertJ retorna uma AbstractAssertsubclasse ... permitindo a seqüência de métodos como acima (ou quaisquer que sejam os termos técnicos para isso ...).
mike roedor

@weston Na verdade, acabei de usar sua técnica no AssertJ 2.0.0. Não há desculpa para não atualizar, sem dúvida, mas você gostaria de saber.
mike roedor

33

Para resolver o mesmo problema, montei um pequeno projeto: http://code.google.com/p/catch-exception/

Usando este pequeno ajudante, você escreveria

verifyException(foo, IndexOutOfBoundsException.class).doStuff();

Isso é menos detalhado que a regra ExpectedException da JUnit 4.7. Em comparação com a solução fornecida pelo skaffman, você pode especificar em qual linha de código espera a exceção. Eu espero que isso ajude.


Pensei em fazer algo assim também, mas finalmente descobri que o verdadeiro poder de ExpectedException é que não apenas você pode especificar a exceção esperada, mas também pode especificar certas propriedades da exceção, como a causa esperada ou a mensagem esperada.
21814 Jason Thompson

Meu palpite é que esta solução tem algumas das mesmas desvantagens que as zombarias? Por exemplo, se fooé finalque irá falhar porque você não pode fazer proxy foo?
Tom

Tom, se doStuff () fizer parte de uma interface, a abordagem de proxy funcionará. Caso contrário, essa abordagem falhará, você está certo.
Rwitzel

31

Atualização: O JUnit5 possui uma melhoria para o teste de exceções:assertThrows .

O exemplo a seguir é de: Guia do Usuário do Junit 5

 @Test
void exceptionTesting() {
    Throwable exception = assertThrows(IllegalArgumentException.class, () -> 
    {
        throw new IllegalArgumentException("a message");
    });
    assertEquals("a message", exception.getMessage());
}

Resposta original usando o JUnit 4.

Existem várias maneiras de testar se uma exceção é lançada. Eu também discuti as opções abaixo no meu post Como escrever ótimos testes de unidade com o JUnit

Defina o expectedparâmetro @Test(expected = FileNotFoundException.class).

@Test(expected = FileNotFoundException.class) 
public void testReadFile() { 
    myClass.readFile("test.txt");
}

Usando try catch

public void testReadFile() { 
    try {
        myClass.readFile("test.txt");
        fail("Expected a FileNotFoundException to be thrown");
    } catch (FileNotFoundException e) {
        assertThat(e.getMessage(), is("The file test.txt does not exist!"));
    }

}

Testando com ExpectedExceptionregra.

@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void testReadFile() throws FileNotFoundException {

    thrown.expect(FileNotFoundException.class);
    thrown.expectMessage(startsWith("The file test.txt"));
    myClass.readFile("test.txt");
}

Você pode ler mais sobre o teste de exceções no wiki do JUnit4 para Teste de exceção e bad.robot - Regra de JUnit de exceção de exceção .


22

Você também pode fazer isso:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
    try {
        foo.doStuff();
        assert false;
    } catch (IndexOutOfBoundsException e) {
        assert true;
    }
}

12
Nos testes JUnit, é melhor usar Assert.fail(), não assert, apenas no caso de seus testes serem executados em um ambiente onde as asserções não estão ativadas.
NamshubWriter

14

IMHO, a melhor maneira de verificar exceções no JUnit é o padrão try / catch / fail / assert:

// this try block should be as small as possible,
// as you want to make sure you only catch exceptions from your code
try {
    sut.doThing();
    fail(); // fail if this does not throw any exception
} catch(MyException e) { // only catch the exception you expect,
                         // otherwise you may catch an exception for a dependency unexpectedly
    // a strong assertion on the message, 
    // in case the exception comes from anywhere an unexpected line of code,
    // especially important if your checking IllegalArgumentExceptions
    assertEquals("the message I get", e.getMessage()); 
}

O assertTruepode ser um pouco forte para algumas pessoas, por isso, assertThat(e.getMessage(), containsString("the message");pode ser preferível.



13

A resposta mais flexível e elegante para o 4 de junho que encontrei no blog Mkyong . Tem a flexibilidade de try/catchusar a @Ruleanotação. Eu gosto dessa abordagem porque você pode ler atributos específicos de uma exceção personalizada.

package com.mkyong;

import com.mkyong.examples.CustomerService;
import com.mkyong.examples.exception.NameNotFoundException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.hasProperty;

public class Exception3Test {

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void testNameNotFoundException() throws NameNotFoundException {

        //test specific type of exception
        thrown.expect(NameNotFoundException.class);

        //test message
        thrown.expectMessage(is("Name is empty!"));

        //test detail
        thrown.expect(hasProperty("errCode"));  //make sure getters n setters are defined.
        thrown.expect(hasProperty("errCode", is(666)));

        CustomerService cust = new CustomerService();
        cust.findByName("");

    }

}

12

Tentei muitos dos métodos aqui, mas eles eram complicados ou não atendiam totalmente aos meus requisitos. De fato, pode-se escrever um método auxiliar de maneira bastante simples:

public class ExceptionAssertions {
    public static void assertException(BlastContainer blastContainer ) {
        boolean caughtException = false;
        try {
            blastContainer.test();
        } catch( Exception e ) {
            caughtException = true;
        }
        if( !caughtException ) {
            throw new AssertionFailedError("exception expected to be thrown, but was not");
        }
    }
    public static interface BlastContainer {
        public void test() throws Exception;
    }
}

Use-o assim:

assertException(new BlastContainer() {
    @Override
    public void test() throws Exception {
        doSomethingThatShouldExceptHere();
    }
});

Zero dependências: sem necessidade de mockito, sem necessidade de powermock; e funciona muito bem com as aulas finais.


Interessante, mas não se encaixa no AAA (Organizar Atuação de Atuação), onde você deseja realizar a Atuação e a Atuação em etapas realmente diferentes.
bi-tom

1
@ bln-tom Tecnicamente, são duas etapas diferentes, elas simplesmente não estão nessa ordem. ; p
Trejkaz 02/02

10

Solução Java 8

Se você deseja uma solução que:

  • Utiliza lambdas Java 8
  • Será que não depende de nenhuma mágica JUnit
  • Permite verificar várias exceções em um único método de teste
  • Verifica se uma exceção está sendo lançada por um conjunto específico de linhas no seu método de teste, em vez de qualquer linha desconhecida no método de teste inteiro
  • Rende o objeto de exceção real que foi lançado para que você possa examiná-lo mais

Aqui está uma função utilitária que eu escrevi:

public final <T extends Throwable> T expectException( Class<T> exceptionClass, Runnable runnable )
{
    try
    {
        runnable.run();
    }
    catch( Throwable throwable )
    {
        if( throwable instanceof AssertionError && throwable.getCause() != null )
            throwable = throwable.getCause(); //allows "assert x != null : new IllegalArgumentException();"
        assert exceptionClass.isInstance( throwable ) : throwable; //exception of the wrong kind was thrown.
        assert throwable.getClass() == exceptionClass : throwable; //exception thrown was a subclass, but not the exact class, expected.
        @SuppressWarnings( "unchecked" )
        T result = (T)throwable;
        return result;
    }
    assert false; //expected exception was not thrown.
    return null; //to keep the compiler happy.
}

( retirado do meu blog )

Use-o da seguinte maneira:

@Test
public void testThrows()
{
    RuntimeException e = expectException( RuntimeException.class, () -> 
        {
            throw new RuntimeException( "fail!" );
        } );
    assert e.getMessage().equals( "fail!" );
}


8

No meu caso, eu sempre recebo RuntimeException do db, mas as mensagens são diferentes. E a exceção precisa ser tratada respectivamente. Aqui está como eu testei:

@Test
public void testThrowsExceptionWhenWrongSku() {

    // Given
    String articleSimpleSku = "999-999";
    int amountOfTransactions = 1;
    Exception exception = null;

    // When
    try {
        createNInboundTransactionsForSku(amountOfTransactions, articleSimpleSku);
    } catch (RuntimeException e) {
        exception = e;
    }

    // Then
    shouldValidateThrowsExceptionWithMessage(exception, MESSAGE_NON_EXISTENT_SKU);
}

private void shouldValidateThrowsExceptionWithMessage(final Exception e, final String message) {
    assertNotNull(e);
    assertTrue(e.getMessage().contains(message));
}

1
antes da linha com } catch (, você deve inserir #fail("no exception thrown");
Daniel Alder

6

Basta criar um Matcher que possa ser ligado e desligado, desta forma:

public class ExceptionMatcher extends BaseMatcher<Throwable> {
    private boolean active = true;
    private Class<? extends Throwable> throwable;

    public ExceptionMatcher(Class<? extends Throwable> throwable) {
        this.throwable = throwable;
    }

    public void on() {
        this.active = true;
    }

    public void off() {
        this.active = false;
    }

    @Override
    public boolean matches(Object object) {
        return active && throwable.isAssignableFrom(object.getClass());
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("not the covered exception type");
    }
}

Para usá-lo:

adicione public ExpectedException exception = ExpectedException.none();, então:

ExceptionMatcher exMatch = new ExceptionMatcher(MyException.class);
exception.expect(exMatch);
someObject.somethingThatThrowsMyException();
exMatch.off();

6

No JUnit 4 ou posterior, você pode testar as exceções da seguinte maneira

@Rule
public ExpectedException exceptions = ExpectedException.none();


isso fornece muitos recursos que podem ser usados ​​para melhorar nossos testes JUnit.
Se você vir o exemplo abaixo, estou testando três coisas na exceção.

  1. O tipo de exceção lançada
  2. A exceção Message
  3. A causa da exceção


public class MyTest {

    @Rule
    public ExpectedException exceptions = ExpectedException.none();

    ClassUnderTest classUnderTest;

    @Before
    public void setUp() throws Exception {
        classUnderTest = new ClassUnderTest();
    }

    @Test
    public void testAppleisSweetAndRed() throws Exception {

        exceptions.expect(Exception.class);
        exceptions.expectMessage("this is the exception message");
        exceptions.expectCause(Matchers.<Throwable>equalTo(exceptionCause));

        classUnderTest.methodUnderTest("param1", "param2");
    }

}

6

Podemos usar uma falha de asserção após o método que deve retornar uma exceção:

try{
   methodThatThrowMyException();
   Assert.fail("MyException is not thrown !");
} catch (final Exception exception) {
   // Verify if the thrown exception is instance of MyException, otherwise throws an assert failure
   assertTrue(exception instanceof MyException, "An exception other than MyException is thrown !");
   // In case of verifying the error message
   MyException myException = (MyException) exception;
   assertEquals("EXPECTED ERROR MESSAGE", myException.getMessage());
}

3
A segunda catchseria engolir o rastreamento de pilha se alguma outra exceção é lançada, perdendo informações úteis
NamshubWriter

5

Além do que o NamShubWriter disse, verifique se:

  • A instância ExpectedException é pública ( questão relacionada )
  • A ExpectedException não é instanciada, por exemplo, pelo método @Before. Este post explica claramente todos os meandros da ordem de execução da JUnit.

Você não fazer isso:

@Rule    
public ExpectedException expectedException;

@Before
public void setup()
{
    expectedException = ExpectedException.none();
}

Por fim, esta postagem do blog ilustra claramente como afirmar que uma certa exceção é lançada.


4

Eu recomendo a biblioteca assertj-corepara lidar com exceção no teste junit

No java 8, assim:

//given

//when
Throwable throwable = catchThrowable(() -> anyService.anyMethod(object));

//then
AnyException anyException = (AnyException) throwable;
assertThat(anyException.getMessage()).isEqualTo("........");
assertThat(exception.getCode()).isEqualTo(".......);

2

A solução Junit4 com Java8 é usar esta função:

public Throwable assertThrows(Class<? extends Throwable> expectedException, java.util.concurrent.Callable<?> funky) {
    try {
        funky.call();
    } catch (Throwable e) {
        if (expectedException.isInstance(e)) {
            return e;
        }
        throw new AssertionError(
                String.format("Expected [%s] to be thrown, but was [%s]", expectedException, e));
    }
    throw new AssertionError(
            String.format("Expected [%s] to be thrown, but nothing was thrown.", expectedException));
}

O uso é então:

    assertThrows(ValidationException.class,
            () -> finalObject.checkSomething(null));

Observe que a única limitação é usar uma finalreferência de objeto na expressão lambda. Esta solução permite continuar as asserções de teste, em vez de esperar que sejam jogáveis ​​no nível do método usando a @Test(expected = IndexOutOfBoundsException.class)solução.


1

Por exemplo, você quer escrever Junit para o fragmento de código abaixo mencionado

public int divideByZeroDemo(int a,int b){

    return a/b;
}

public void exceptionWithMessage(String [] arr){

    throw new ArrayIndexOutOfBoundsException("Array is out of bound");
}

O código acima é para testar alguma exceção desconhecida que possa ocorrer e a abaixo é para declarar alguma exceção com mensagem personalizada.

 @Rule
public ExpectedException exception=ExpectedException.none();

private Demo demo;
@Before
public void setup(){

    demo=new Demo();
}
@Test(expected=ArithmeticException.class)
public void testIfItThrowsAnyException() {

    demo.divideByZeroDemo(5, 0);

}

@Test
public void testExceptionWithMessage(){


    exception.expectMessage("Array is out of bound");
    exception.expect(ArrayIndexOutOfBoundsException.class);
    demo.exceptionWithMessage(new String[]{"This","is","a","demo"});
}

1
    @Test(expectedException=IndexOutOfBoundsException.class) 
    public void  testFooThrowsIndexOutOfBoundsException() throws Exception {
         doThrow(IndexOutOfBoundsException.class).when(foo).doStuff();  
         try {
             foo.doStuff(); 
            } catch (IndexOutOfBoundsException e) {
                       assertEquals(IndexOutOfBoundsException .class, ex.getCause().getClass());
                      throw e;

               }

    }

Aqui está outra maneira de verificar o método gerado exceção correta ou não.


1

A estrutura JUnit possui o assertThrows()método:

ArithmeticException exception = assertThrows(ArithmeticException.class, () ->
    calculator.divide(1, 0));
assertEquals("/ by zero", exception.getMessage());

0

Com o Java 8, você pode criar um método usando um código para verificar e a exceção esperada como parâmetros:

private void expectException(Runnable r, Class<?> clazz) { 
    try {
      r.run();
      fail("Expected: " + clazz.getSimpleName() + " but not thrown");
    } catch (Exception e) {
      if (!clazz.isInstance(e)) fail("Expected: " + clazz.getSimpleName() + " but " + e.getClass().getSimpleName() + " found", e);
    }
  }

e depois dentro do seu teste:

expectException(() -> list.sublist(0, 2).get(2), IndexOutOfBoundsException.class);

Benefícios:

  • não confiando em nenhuma biblioteca
  • verificação localizada - mais precisa e permite ter várias asserções como essa em um teste, se necessário
  • fácil de usar
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.