JUnit 4 테스트에서 특정 예외가 발생했다고 어떻게 주장합니까?


1999

JUnit4를 관용적으로 사용하여 일부 코드에서 예외가 발생하는지 테스트하려면 어떻게해야합니까?

나는 확실히 이와 같은 것을 할 수 있지만 :

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

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

  assertTrue(thrown);
}

이러한 종류의 상황에 대해 주석이나 Assert.xyz 또는 JUnit의 훨씬 덜 혼란스럽고 훨씬 더 정신적 인 것이 있다는 것을 기억합니다 .


21
다른 접근 방식의 문제점이지만 예외가 발생하면 항상 테스트를 종료한다는 것입니다. 반면에, org.mockito.Mockito.verify예외가 발생하기 전에 특정 상황이 발생했는지 확인하기 위해 종종 로거 서비스가 올바른 매개 변수 로 호출 되도록 다양한 매개 변수 를 호출하려고합니다 .
ZeroOne


6
@ZeroOne-두 가지 테스트가 있습니다. 하나는 예외에 대한 것이고 다른 하나는 모의와의 상호 작용을 확인하는 것입니다.
tddmonkey

JUnit 5 로이 작업을 수행하는 방법이 있습니다. 아래에서 내 답변을 업데이트했습니다.
Dilini Rajapaksha

답변:


2361

JUnit 버전과 사용하는 어설 션 라이브러리에 따라 다릅니다.

원래 답변 JUnit <= 4.12은 다음과 같습니다.

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

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

}

답변 https://stackoverflow.com/a/31826781/2986984 에는 JUnit <= 4.12에 대한 추가 옵션이 있습니다.

참고 :


66
이 코드는 코드의 어딘가에서만 예외가 예상되고 이와 같은 담요가 아닌 경우 작동하지 않습니다.
Oh Chin Boon

4
@skaffman org.junit.experimental.theories.Theory와이 것없는 작품은 org.junit.experimental.theories.Theories에 의해 runned
아르 템 Oboturov에게

74
Roy Osherove는 예외는 테스트중인 장치 내부뿐만 아니라 테스트 내부에있을 수 있기 때문에 단위 테스트 기술 에서 이러한 종류의 예외 테스트를 권장하지 않습니다.
Kevin Wittek

21
@ Kiview / Roy Osherove에 동의하지 않습니다. 내 생각에 테스트는 구현이 아니라 행동에 대한 것이어야합니다. 특정 메소드가 오류를 발생시킬 수 있는지 테스트함으로써 테스트와 구현에 직접 연결합니다. 위에 표시된 방법으로 테스트하면 더 유용한 테스트가 제공된다고 주장합니다. 내가 추가해야 할주의 사항은이 경우 사용자 정의 예외를 테스트하여 실제로 원하는 예외가 발생한다는 것을 알 수 있다는 것입니다.
nickbdyer

5
둘 다. 수업의 행동을 테스트하고 싶습니다. 중요한 것은 존재하지 않는 것을 검색하려고하면 예외가 발생한다는 것입니다. 데이터 구조가 ArrayList응답 한다는 사실 get()은 관련이 없습니다. 나중에 기본 배열로 이동하기로 선택한 경우이 테스트 구현을 변경해야합니다. 테스트가 클래스 의 동작에 집중할 수 있도록 데이터 구조를 숨겨야합니다 .
nickbdyer

1316

편집 : 이제 JUnit 5 및 JUnit 4.13이 릴리스되었으므로 가장 좋은 옵션은 Assertions.assertThrows() (JUnit 5의 경우) 및 Assert.assertThrows()(JUnit 4.13의 경우)를 사용하는 것입니다. 자세한 내용은 다른 답변 을 참조하십시오.

JUnit 5로 마이그레이션하지 않았지만 JUnit 4.7을 사용할 수있는 경우 ExpectedException규칙을 사용할 수 있습니다 .

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

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

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

전에 던져 @Test(expected=IndexOutOfBoundsException.class)지면 테스트가 실패하기 때문에 이것은 훨씬 낫습니다.IndexOutOfBoundsExceptionfoo.doStuff()

자세한 내용은 이 기사 를 참조하십시오


14
@skaffman-이것을 올바르게 이해했다면 예외처럼 보입니다. 예상은 전체 클래스가 아닌 하나의 테스트에만 적용됩니다.
bacar

5
우리가 던질 것으로 예상되는 예외가 확인 된 예외라면, 다른 방법으로 던지기를 추가하거나 시도하거나이 상황을 테스트해야합니까?
Mohammad Jafar Mashhadi

5
@MartinTrummer 예외가 발생하고 메소드가 종료되므로 foo.doStuff () 이후에 코드를 실행하지 않아야합니다. 예상 된 예외 (마지막으로 리소스를 닫는 것을 제외하고) 후에 코드를 갖는 것은 예외가 발생하면 절대 실행되지 않아야하므로 도움이되지 않습니다.
Jason Thompson

9
이것이 가장 좋은 방법입니다. skaffman의 솔루션과 비교하여 여기에는 두 가지 장점이 있습니다. 첫째, ExpectedException클래스에는 예외 메시지와 일치하는 방법이 있거나 예외 클래스에 따라 고유 한 매처를 작성할 수도 있습니다. 둘째, 예외가 발생할 것으로 예상되는 코드 줄 바로 앞에 기대치를 설정할 수 있습니다. 즉, 잘못된 코드 줄이 예외를 throw하면 테스트가 실패합니다. 반면 skaffman의 솔루션으로는 그렇게 할 수 없습니다.
Dawood ibn Kareem

5
@MJafarMash 던질 것으로 예상되는 예외가 확인되면 테스트 메소드의 throws 절에 해당 예외를 추가합니다. 특정 테스트 케이스에서 예외가 트리거되지 않더라도 확인 된 예외를 발생 시키도록 선언 된 메소드를 테스트 할 때마다 동일한 작업을 수행합니다.
NamshubWriter

471

메소드 가 테스트에서 특정 코드 줄이 아니라 해당 예외를 발생 시켰다고 주장하기 때문에 예상되는 예외를 사용하는 데주의하십시오 .

이러한 방법은 일반적으로 매우 간단하지만 더 복잡한 테스트가 더 잘 제공 될 수 있기 때문에 매개 변수 유효성 검사 테스트에 사용하는 경향이 있습니다.

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

판단을 적용하십시오.


95
어쩌면 나는 구식이지만 여전히 이것을 선호합니다. 또한 예외 자체를 테스트 할 수있는 장소를 제공합니다. 때로는 특정 값에 대해 getter가있는 예외가 있거나 단순히 메시지에서 특정 값을 찾을 수 있습니다 (예 : "인식되지 않은 코드 'xyz'메시지에서"xyz ") ").
Rodney Gitzel

3
NamshubWriter의 접근 방식은 두 가지 이점을 모두 제공한다고 생각합니다.
Eddie

4
ExpectedException을 사용하면 메소드별로 N exception.expect를 호출하여이 예외와 같이 테스트 할 수 있습니다 .expect (IndexOutOfBoundsException.class); foo.doStuff1 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff2 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff3 ();
user1154664

10
실제로는 할 수 없습니다. ExpectedException을 사용하면 한 메소드에서 예외가 발생하는지 테스트 할 수 있습니다. 해당 메소드가 호출되면 예상 예외가 발생하여 테스트 실행이 중지되기 때문입니다!
NamshubWriter

2
첫 문장은 사실이 아닙니다. 를 사용할 때 ExpectedException일반적으로해야 할 일은 예외를 던질 것으로 예상되는 줄 바로 앞에 기대치를 설정하는 것입니다. 이렇게하면 이전 줄에서 예외가 발생하면 규칙이 트리거되지 않으며 테스트가 실패합니다.
Dawood ibn Kareem

212

이전에 대답했듯이 JUnit에는 예외를 처리하는 여러 가지 방법이 있습니다. 그러나 Java 8에는 Lambda Expressions 사용이 있습니다. Lambda Expressions를 사용하면 다음과 같은 구문을 얻을 수 있습니다.

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

assertThrown은 람다 식, 메서드 참조 또는 생성자 참조로 인스턴스를 만들 수있는 기능적 인터페이스를 허용합니다. 해당 인터페이스를 승인하면 assertThrown이 예외를 처리하고 처리 할 준비가됩니다.

이것은 비교적 간단하지만 강력한 기술입니다.

이 기술을 설명하는이 블로그 게시물을 살펴보십시오. http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

소스 코드는 https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8 에서 찾을 수 있습니다.

공개 : 저는 블로그와 프로젝트의 저자입니다.


2
나는이 솔루션을 좋아하지만 maven repo에서 이것을 다운로드 할 수 있습니까?
Selwyn

Maven에서 사용할 수있는이 아이디어의 @Airduster 구현 중 하나는 stefanbirkner.github.io/vallado
NamshubWriter

6
@CristianoFontes이 API의 더 간단한 버전은 JUnit 4.13 용으로 예정되어 있습니다. github.com/junit-team/junit/commit/…
NamshubWriter 2

@RafalBorowiec 기술적, new DummyService()::someMethodA는 MethodHandle하지만,이 방법은 람다 표현식과 동일하게 작동합니다.
Andy

@NamshubWriter, 그것은 그 JUnit을 4.13 JUnit을 5 찬성 버려진 것 같다 stackoverflow.com/questions/156503/...
바드

154

junit에는 예외를 테스트하는 네 가지 방법이 있습니다.

junit5.x

  • junit5.x의 경우 assertThrows다음과 같이 사용할 수 있습니다

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

junit4.x

  • junit4.x의 경우, 선택 테스트 테스트의 '예상'속성을 사용하십시오.

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
  • junit4.x의 경우 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();
        }
    }
  • junit 3 프레임 워크에서 널리 사용되는 고전적인 try / catch 방식을 사용할 수도 있습니다

    @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.
        }
    }
  • 그래서

    • junit 5를 좋아한다면 첫 번째 것을 좋아해야합니다
    • 두 번째 방법은 예외 유형 만 테스트하려는 경우에 사용됩니다.
    • 첫 번째와 마지막 두 개는 테스트 예외 메시지를 추가로 원할 때 사용됩니다.
    • junit 3을 사용하면 네 번째 것이 선호됩니다.
  • 자세한 내용은 이 문서 와 자세한 내용은 junit5 사용 설명서 를 참조하십시오.


6
나에게 이것은 가장 좋은 대답이며, 모든 방법을 매우 명확하게 설명합니다. 감사합니다! 개인적으로 저는 가독성을 위해 Junit4에서도 3 번째 옵션을 계속 사용합니다. 빈 캐치 블록을 피하기 위해 Throwable 및 assert 유형의 e도 잡을 수 있습니다.
Nicolas Cornette

확인 된 예외를 예상하기 위해 ExpectedException을 사용할 수 있습니까?
miuser

모두 상위 3 개의 답변이 누적 된 것입니다. IMO,이 답변은 새로운 것을 추가하지 않으면 게시되지 않아야합니다. 담당자에 대한 답변 (인기있는 질문)입니다. 꽤 쓸모가 없습니다.
Paul Samsotha 1

Trowable메소드 에서 파생 된 모든 유형을 전달할 수 있기 때문 ExpectedException.expect입니다. 서명을 참조하십시오 . @miuser
walsh

116

tl; dr

  • post-JDK8 : AssertJ 또는 커스텀 람다를 사용 하여 뛰어난 동작을 확인하십시오.

  • pre-JDK8 : 오래된 good try- catchblock을 추천합니다 . ( 블록 앞에 어설 션 을 추가하는 것을 잊지 마십시오fail()catch )

Junit 4 또는 JUnit 5에 상관없이

긴 이야기

자신을 작성할 수 있습니다 그것을 스스로 할 try - catch블록 또는 JUnit을 도구 (사용 @Test(expected = ...)또는 @Rule ExpectedExceptionJUnit을 규칙 기능).

그러나 이러한 방법은 그다지 우아하지 않으며 다른 도구와 잘 가독성 이 좋지 않습니다 . 또한 JUnit 툴링에는 몇 가지 함정이 있습니다.

  1. try- catch당신이 테스트 문제를 해결 블록을 작성하고 catch 블록의 주장을 작성해야 블록, 즉 미세하지만 많은 발견 할 수있다이 스타일은 인터럽트 테스트의 읽기 흐름을 그. 또한, 당신은 쓸 필요 Assert.fail의 끝 부분에 try블록. 그렇지 않으면 테스트에서 어설 션의 한쪽이 누락 될 수 있습니다. PMD , findbugs 또는 Sonar 는 이러한 문제를 발견합니다.

  2. @Test(expected = ...)적은 코드를 작성하고이 테스트를 작성하는 것은 오류를 코딩 가정으로 적은 경향 수있는 기능은 흥미 롭다. 그러나 일부 지역에서는 이러한 접근 방식이 부족합니다.

    • 테스트에서 원인 또는 메시지와 같은 예외에 대한 추가 사항을 확인해야하는 경우 (예외 메시지가 중요하면 정확한 예외 유형이 충분하지 않을 수 있음)
    • 또한 테스트 코드 작성 방법에 따라 테스트 코드의 잘못된 부분에서 예외가 발생하여 거짓 양성 테스트가 발생할 수 있으며 PMD , findbugs 또는 Sonar 확신하지 못합니다 그러한 코드에 대한 힌트를 줄 것입니다.

      @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. ExpectedException규칙은 또한 이전 경고를 해결하려는 시도이지만, 예상 스타일을 사용하기 때문에 사용하기가 약간 어색하다고 생각합니다. EasyMock 사용자는이 스타일을 매우 잘 알고 있습니다. 일부에게는 편리 할 수 ​​있지만 BDD ( behavior Driven Development ) 또는 AAA ( Action Actassert ) 원칙을 따르는 경우 ExpectedException규칙이 해당 작성 스타일에 맞지 않습니다. 그 외에도 @Test예상 위치에 따라 방법 과 동일한 문제가 발생할 수 있습니다 .

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

    예상되는 예외도 테스트 문 앞에 놓여지며 테스트가 BDD 또는 AAA를 따르는 경우 읽기 흐름이 중단됩니다.

    또한 의 작성자 JUnit에 대한 이 주석 문제를 참조하십시오 ExpectedException. JUnit 4.13-beta-2 는이 메커니즘을 더 이상 사용하지 않습니다.

    풀 요청 # 1519 : Deprecate ExpectedException

    Assert.assertThrows 메소드는 예외를 확인하는 더 좋은 방법을 제공합니다. 또한 규칙의 순서가 중요하기 때문에 TestWatcher와 같은 다른 규칙과 함께 사용하면 ExpectedException을 사용하는 것이 오류가 발생하기 쉽습니다.

따라서 위의 옵션에는 모든주의 사항이 있으며 코더 오류에 영향을 미치지 않습니다.

  1. 유망한 것으로 보이는이 답변을 만든 후에 내가 알게 된 프로젝트가 있습니다. 그것은 예외 입니다.

    프로젝트에 대한 설명에서 알 수 있듯이 코더는 유창한 코드 줄을 작성하여 예외를 포착하고 후자의 주장에 대해이 예외를 제공 할 수 있습니다. 그리고 Hamcrest 또는 AssertJ 와 같은 어설 션 라이브러리를 사용할 수 있습니다 .

    홈페이지에서 가져온 빠른 예 :

    // 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();

    코드가 정말 간단 then하다는 것을 알 수 있듯이 특정 라인에서 예외를 포착하면 API는 AssertJ API를 사용하는 별칭입니다 (과 유사 assertThat(ex).hasNoCause()...). 어느 시점에서 프로젝트는 AssertJ의 조상 FEST-Assert에 의존했습니다 . 편집 : 프로젝트가 Java 8 Lambdas 지원을 양조하고있는 것 같습니다.

    현재이 라이브러리에는 두 가지 단점이 있습니다.

    • 이 글을 쓰는 시점에서이 라이브러리는 Mockito 1.x를 기반으로하며, 테스트 된 객체 뒤에 장면을 모방 한 것입니다. Mockito가 여전히 업데이트되지 않았으므로이 라이브러리는 최종 클래스 또는 최종 메소드와 함께 작동 할 수 없습니다 . 그리고 현재 버전의 Mockito 2를 기반으로 했더라도 글로벌 모의 메이커 ( inline-mock-maker) 를 선언해야합니다. 이 모의 메이커는 일반 모의 메이커와는 다른 단점이 있기 때문에 원치 않을 수도 있습니다.

    • 또 다른 테스트 종속성이 필요합니다.

    라이브러리가 람다를 지원하면 이러한 문제는 적용되지 않습니다. 그러나이 기능은 AssertJ 툴셋에 의해 복제됩니다.

    catch-exception 도구를 사용하지 않으려면 모든 것을 고려 하여 적어도 JDK7까지 try- catch블록 의 오래된 좋은 방법을 권장합니다 . JDK 8 사용자의 경우 AssertJ를 사용하는 것이 좋습니다. 예외를 주장하는 것 이상을 제공 할 수도 있습니다.

  2. JDK8을 사용하면 람다는 테스트 현장에 들어가고 뛰어난 행동을 나타내는 흥미로운 방법으로 판명되었습니다. AssertJ는 유창한 유창한 API를 제공하여 예외적 인 행동을하도록 업데이트되었습니다.

    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. JUnit 5를 거의 완전히 다시 작성하면 어설 션이 약간 개선 되어 제대로 예외를 주장하는 즉시 사용 가능한 방법으로 흥미로울 수 있습니다. 그러나 실제로 어설 션 API는 여전히 약간 나쁘지만 외부에는 아무것도 없습니다 assertThrows.

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

    알다시피 assertEquals여전히을 반환 void하고 AssertJ와 같은 체인 어설 션을 허용하지 않습니다.

    당신이 이름 충돌을 기억한다면 또한 Matcher또는 Assert와 같은 충돌을 만날 준비 Assertions.

오늘 (2017-03-03) AssertJ 의 사용 편의성, 검색 가능한 API, 빠른 개발 속도 및 사실상 테스트 종속성은 테스트 프레임 워크 (JUnit)에 관계없이 JDK8을 사용하는 가장 좋은 솔루션이라고 결론 내리고 싶습니다 여부), 이전의 JDK 대신에 의존한다 try-catch 그들이 투박한 느낌이 경우에도 블록.

이 답변은 동일한 가시성을 가지고 있지 않은 다른 질문 에서 복사되었습니다 . 저는 같은 저자입니다.


1
assertThrows를 사용할 수 있도록 org.junit.jupiter : junit-jupiter-engine : 5.0.0-RC2 종속성 (기존의 junit : junit : 4.12에 추가)을 추가하는 것이 선호되는 솔루션은 아니지만, 나를 위해 문제.
anre

나는 ExpectedException 규칙을 사용하는 팬이지만 항상 AAA와 충돌한다고 귀찮게했습니다. 다양한 접근 방식을 모두 설명하는 훌륭한 기사를 작성했으며 AssertJ :-)를 사용해 보라고 확실히 권장했습니다. 감사합니다!
Pim Hazebroek

@PimHazebroek 감사합니다. AssertJ API는 매우 풍부합니다. JUnit이 즉시 제안하는 것이 더 좋습니다.
Brice

64

JUnit 5 및 JUnit 4.13이 릴리스되었으므로 가장 좋은 옵션은 Assertions.assertThrows() (JUnit 5의 경우) 및 Assert.assertThrows()(JUnit 4.13의 경우)를 사용하는 것입니다. Junit 5 사용자 안내서를 참조하십시오 .

다음은 예외가 발생했는지 확인하고 Truth 를 사용 하여 예외 메시지에 대한 어설 션을 만드는 예입니다 .

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

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

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

다른 답변의 접근법보다 장점은 다음과 같습니다.

  1. JUnit에 내장
  2. 람다의 코드에서 예외가 발생하지 않으면 유용한 예외 메시지가 표시되고 다른 예외가 발생하면 스택 추적이 표시됩니다.
  3. 간결한
  4. 테스트가 Arrange-Act-Assert를 따르도록 허용
  5. 예외를 던질 것으로 예상되는 코드를 정확하게 나타낼 수 있습니다
  6. throws절에 예상되는 예외를 나열 할 필요는 없습니다.
  7. 선택한 어설 션 프레임 워크를 사용하여 포착 된 예외에 대한 어설 션을 만들 수 있습니다.

org.junit AssertJUnit 4.13 에도 비슷한 방법이 추가 될 것 입니다.


이 접근 방식은 깔끔하지만 "AssertThrow"에서 "Act"부분을 래핑해야하기 때문에이 테스트가 "Arrange-Act-Assert"를 따르는 방법을 알 수 없습니다.
Clockwork

@Clockwork 람다는 "행위"입니다. Arrange-Act-Assert의 목표는 코드를 깨끗하고 간단하게 (그리고 이해하고 유지하기 쉽게) 만드는 것입니다. 언급 했듯이이 접근법은 깨끗합니다.
NamshubWriter

그래도 "어설 션"부분에서 테스트가 끝날 때 던지기와 예외를 선언 할 수 있기를 바랐습니다. 이 방법에서는 먼저 행동을 먼저 잡기 위해 행동을 포장해야합니다.
Clockwork

어설 션을 수행하려면 모든 테스트에서 더 많은 코드가 필요합니다. 더 많은 코드이며 오류가 발생하기 쉽습니다.
NamshubWriter

43

어떻습니까 : 매우 일반적인 예외를 잡아서 catch 블록에서 벗어나게 한 다음 예외 클래스가 예상 한 것임을 주장하십시오. a) 예외 유형이 잘못된 경우 (예 : 대신 널 포인터가있는 경우) b) 예외가 발생하지 않은 경우이 어설 션이 실패합니다.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}

3
또한 테스트가 실패한 날이 오면 테스트 결과에 어떤 종류의 예외가 있는지 볼 수 없습니다.
jontejj

마지막에 어설 션하는 방식을 변경하여 조금 개선 할 수 있습니다. assertEquals(ExpectedException.class, e.getClass())테스트에 실패하면 예상 및 실제 값이 표시됩니다.
Cypher

37

BDD 스타일 솔루션 : JUnit 4 + Catch Exception + AssertJ

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

@Test
public void testFooThrowsIndexOutOfBoundsException() {

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

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

}

의존성

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

36

JUnit과 함께 사용할 수 있는 AssertJ 어설 션 사용

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

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

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

@Test(expected=IndexOutOfBoundsException.class)테스트에서 예상되는 행이 예외를 발생 시켰음을 보장하고 메시지와 같은 예외에 대한 세부 사항을 더 쉽게 확인할 수있게하는 것보다 낫습니다 .

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

Maven / Gradle 지침은 여기입니다.


가장 간결한 방법으로 아무도 그것을 감사하지 않습니다. 이상합니다. assertJ 라이브러리에는 한 가지 문제가 있습니다. assertThat은 이름 단위로 junit과 충돌합니다. assertJ throwby에 대한 추가 정보 : JUnit : Java 8 및 AssertJ 3.0.0을 사용한 예외 테스트 ~ Codeleak.pl
ycomp

@ycomp 글은 아주 오래된 질문에 대한 새로운 답변이므로 점수 차이는 기만적입니다.
weston

Java 8과 AssertJ를 사용할 수 있다면 아마도 최고의 솔루션 일 것입니다!
Pierre Henry

@ycomp이 이름 충돌은 의도적으로 설계된 것 같습니다. AssertJ 라이브러리는 항상 AssertJ 라이브러리 인 JUnit을 사용하지 말 것을 강력히 권장합니다 assertThat. 또한 JUnit 메소드는 "일반"유형 만 리턴하는 반면 AssertJ 메소드는 AbstractAssert서브 클래스를 리턴합니다. 위와 같이 메소드를 문자열 화할 수 있습니다 (또는 기술적 용어는 무엇이든).
마이크 설치류

@weston 실제로 AssertJ 2.0.0에서 기술을 사용했습니다. 업그레이드하지 않은 것에 대한 변명의 여지가 없지만 의심의 여지가 없지만 알고 싶습니다.
마이크 설치류

33

같은 문제를 해결하기 위해 작은 프로젝트를 설정했습니다 : http://code.google.com/p/catch-exception/

이 작은 도우미를 사용하면

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

이것은 JUnit 4.7의 ExpectedException 규칙보다 덜 장황합니다. skaffman이 제공하는 솔루션과 비교하여 예외를 예상 할 코드를 지정할 수 있습니다. 이게 도움이 되길 바란다.


나는 이와 같은 일을하는 것에 대해서도 생각했지만 궁극적으로 ExpectedException의 진정한 힘은 예상 예외를 지정할 수있을뿐만 아니라 예상 원인 또는 예상 메시지와 같은 예외의 특정 속성을 지정할 수도 있다는 것을 발견했습니다.
Jason Thompson

내 생각 에이 솔루션에는 모의와 같은 단점이 있습니까? 예를 들어, foofinal이 실패하면 프록시 수 없기 때문에 foo?
Tom

Tom, doStuff ()가 인터페이스의 일부인 경우 프록시 접근 방식이 작동합니다. 그렇지 않으면이 방법이 실패 할 것입니다.
rwitzel

31

업데이트 : JUnit5의 예외 테스트 기능이 개선되었습니다.assertThrows .

다음 예제는 다음과 같습니다. Junit 5 사용자 안내서

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

JUnit 4를 사용한 원래 답변

예외가 발생했는지 테스트하는 방법에는 여러 가지가 있습니다. 나는 또한 내 게시물의 옵션 아래 논의했다 JUnit을 가진 좋은 단위 테스트를 작성하는 방법

expected파라미터를 설정합니다 @Test(expected = FileNotFoundException.class).

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

사용 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!"));
    }

}

ExpectedException규칙을 사용한 테스트 .

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

예외 테스트bad.robot-예상 예외 JUnit 규칙대해서는 JUnit4 위키 에서 예외 테스트에 대해 자세히 읽을 수 있습니다.


22

당신은 또한 이것을 할 수 있습니다 :

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

12
JUnit 테스트에서 더 나은 사용에있어 Assert.fail(),하지 assert, 당신의 검사 결과는 환경에서 실행되는 경우에 대비해 주장은 사용할 수 없습니다 곳.
NamshubWriter

14

JUnit에서 예외를 확인하는 가장 좋은 방법은 IMHO, 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()); 
}

assertTrue어떤 사람들을 위해 조금 강할 수 있으므로 assertThat(e.getMessage(), containsString("the message");바람직 할 수 있습니다.



13

Mkyong 블로그 에서 찾은 Junit 4에 대한 가장 유연하고 우아한 답변입니다 . 주석을 try/catch사용 하는 유연성이 @Rule있습니다. 사용자 정의 예외의 특정 속성을 읽을 수 있기 때문에이 방법이 마음에 듭니다.

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

여기에서 많은 방법을 시도했지만 복잡하거나 내 요구 사항을 충족하지 못했습니다. 실제로 도우미 메서드를 매우 간단하게 작성할 수 있습니다.

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

다음과 같이 사용하십시오.

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

종속성 없음 : mockito 필요 없음, powermock 필요 없음; 최종 수업에서 잘 작동합니다.


흥미롭지 만 실제로 다른 단계에서 Act 및 Assert 단계를 수행하려는 AAA (Act Assert 배열)에는 맞지 않습니다.
bln-tom

1
@ bln-tom 기술적으로 두 가지 단계가 있습니다. 순서가 다릅니다. ; p
트레 카자

10

자바 8 솔루션

다음과 같은 솔루션을 원할 경우 :

  • Java 8 람다 활용
  • 않습니다 하지 어떤 JUnit을 마법에 따라 달라집니다
  • 단일 테스트 방법 내에서 여러 예외를 확인할 수 있습니다
  • 전체 테스트 방법에서 알 수없는 라인 대신 테스트 방법 내의 특정 라인 세트에 의해 예외가 발생하는지 확인
  • 실제 예외 개체를 생성하여 추가로 검사 할 수 있습니다.

내가 작성한 유틸리티 함수는 다음과 같습니다.

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

( 내 블로그에서 가져온 )

다음과 같이 사용하십시오.

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


8

필자의 경우 항상 db에서 RuntimeException이 발생하지만 메시지는 다릅니다. 예외는 각각 처리해야합니다. 테스트 한 방법은 다음과 같습니다.

@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
와 함께 라인에 } catch (삽입해야합니다fail("no exception thrown");
Daniel Alder

6

다음과 같이 켜거나 끌 수있는 Matcher를 만드십시오.

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

그것을 사용하려면 :

public ExpectedException exception = ExpectedException.none();다음을 추가하십시오 .

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

6

JUnit 4 이상에서 다음과 같이 예외를 테스트 할 수 있습니다.

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


이는 JUnit 테스트를 개선하는 데 사용할 수있는 많은 기능을 제공합니다.
아래 예제를 보면 예외에 대해 3 가지를 테스트하고 있습니다.

  1. 발생 된 예외 유형
  2. 예외 메시지
  3. 예외의 원인


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

예외를 반환해야하는 메소드 후에 어설 션 실패를 사용할 수 있습니다.

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
두 번째는 catch다른 예외가 발생하면 유용한 정보를 잃고, 스택 추적을 삼킬 것
NamshubWriter

5

NamShubWriter 가 말한 것 외에도 다음 을 확인하십시오.

  • ExpectedException 인스턴스가 공개입니다 ( 관련 질문 )
  • @Before 메소드 ExpectedException을 인스턴스화 하지 않습니다 . 이 포스트 는 JUnit의 실행 순서에 대한 모든 복잡성을 명확하게 설명합니다.

이 작업 수행 하지 마십시오 :

@Rule    
public ExpectedException expectedException;

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

마지막으로, 블로그 게시물은 특정 예외가 발생했다고 주장하는 방법을 명확하게 보여줍니다.


4

assertj-corejunit 테스트에서 예외를 처리하기 위해 라이브러리 를 권장합니다.

Java 8에서는 다음과 같습니다.

//given

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

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

2

Java8이 포함 된 Junit4 솔루션은 다음 기능을 사용합니다.

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

그러면 사용법은 다음과 같습니다.

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

유일한 제한은 final람다 식에서 객체 참조 를 사용하는 것 입니다. 이 솔루션을 사용하면 솔루션을 사용하여 분석법 수준에서 당황 할 것으로 예상하는 대신 테스트 어설 션을 계속할 수 있습니다 @Test(expected = IndexOutOfBoundsException.class).


1

예를 들어 아래 언급 된 코드 조각에 대해 Junit을 작성하려고합니다.

public int divideByZeroDemo(int a,int b){

    return a/b;
}

public void exceptionWithMessage(String [] arr){

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

위의 코드는 발생할 수있는 알려지지 않은 예외를 테스트하기위한 것이며, 아래의 코드는 사용자 정의 메시지로 일부 예외를 발생시키는 것입니다.

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

               }

    }

올바른 예외가 발생했는지 여부를 확인하는 다른 방법이 있습니다.


1

JUnit 프레임 워크에는 다음과 같은 assertThrows()메소드가 있습니다.

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

0

Java 8을 사용하면 코드를 사용하여 예외를 매개 변수로 확인하는 메소드를 작성할 수 있습니다.

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

그런 다음 테스트 내부에서

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

혜택:

  • 어떤 라이브러리에도 의존하지 않습니다
  • 현지화 된 검사-보다 정확하고 필요한 경우 하나의 테스트 내에서 이와 같은 여러 가지 주장을 할 수 있습니다
  • 사용하기 쉬운
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.