반응형

JUnit 4.7 이전 버전에서는 Exception Test를 할 때 @Test(expected=Exception.class) 와 같은 형식으로 테스트를 통과 시킨다.

하지만, 이 방법은 Exception이 발생하는지 여부만 확인이 가능하고, Exception 내부에 들어있는 값들은 검증하지 못한다.


그래서, JUnit 4.7 이후 버전부터 @Rule이라는 기능을 지원을 하는데,

기본적으로 아래와 같이 선언 한 후, 

@Rule

ExpectedException expectedException;



각 Unit Test에서 expect, expectMessage를 이용하여 검증을 한다.

expectedException.expect(Exception.class);

expectedException.expectMessage("exception message");


이 방법도 어떤 Exception인지, Exception안에 들어가 있는 Message만 확인이 가능하기 때문에,

Customizing한 Exception에 들어있는 값들은 확인할 수가 없다.


이런 경우 MethodRule Interface를 상속받아서, 직접 Rule을 작성해주고 ExpectedException 대신에 새롭게 만든 Rule을 넣어주면

원하는대로 체크가 가능하다.


기존에 정의한 Exception이 아래와 같은 형태라고 가정하고, errorCode에 들어있는 값을 체크하고 싶다.


public class GameException extends RuntimeException {

    private static final long serialVersionUID = 7214480405095839046L;

    private int errorCode = -1;
    public GameException(int errorCode) {
        this.errorCode = errorCode;
    }
    
    public int getErrorCode() {
        return this.errorCode;
    }
}


Message보다는 ErrorCode를 체크해야 겠기에, 기존 ExpectedException에 있는 코드를 그대로 가져와서,

expectMessage는 없애고, expectedErrorCode 구문을 추가하였다.


public class GameExceptionRule implements MethodRule {
    /**
     * @return a Rule that expects no exception to be thrown
     * (identical to behavior without this Rule)
     */
    public static GameExceptionRule none() {
        return new GameExceptionRule();
    }

    private Matcher<Object> fMatcher= null;

    private GameExceptionRule() {
        
    }
    
    @Override
    public Statement apply(Statement base, FrameworkMethod method, Object target) {
        return new GameExceptionStatement(base);
    }

    /**
     * Adds {@code matcher} to the list of requirements for any thrown exception.
     */
    // Should be able to remove this suppression in some brave new hamcrest world.
    @SuppressWarnings("unchecked")
    public void expect(Matcher<?> matcher) {
        if (fMatcher == null)
            fMatcher= (Matcher<Object>) matcher;
        else
            fMatcher= both(fMatcher).and((Matcher<? super Object>) matcher);
    }

    /**
     * Adds to the list of requirements for any thrown exception that it
     * should be an instance of {@code type}
     */
    public void expect(Class<? extends Throwable> type) {
        expect(instanceOf(type));
    }
    
    public void expectErrorCode(int errorCode) {
        expect(hasErrorCode(is(errorCode)));
    }

    private class GameExceptionStatement extends Statement {
        private final Statement fNext;

        public GameExceptionStatement(Statement base) {
            fNext= base;
        }

        @Override
        public void evaluate() throws Throwable {
            try {
                fNext.evaluate();
            } catch (Throwable e) {
                if (fMatcher == null)
                    throw e;
                Assert.assertThat(e, fMatcher);
                return;
            }
            if (fMatcher != null)
                throw new AssertionError("Expected test to throw "
                        + StringDescription.toString(fMatcher));
        }
    }

    private Matcher<Throwable> hasErrorCode(final Matcher<Integer> matcher) {
        return new TypeSafeMatcher<Throwable>() {
            public void describeTo(Description description) {
                description.appendText("exception with errorCode ");
                description.appendDescriptionOf(matcher);
            }
        
            @Override
            public boolean matchesSafely(Throwable item) {
                return matcher.matches(((GameException)item).getErrorCode());
            }
        };
    }



유닛 테스트에서의 사용은 아래와 같으며, 아래 작성된 테스트는 gameService의 test메소드에서

GameException이 발생하고, 에러코드가 100이 나와야 성공하는 테스트이다.


@Rule
GameExceptionRule gameExceptionRule = GameExceptionRule.none();

@Test
public void ruleTest() {
        // 반드시 로직 수행 전에 명시해 주어야한다.
        gameExceptionRule.expect(GameException.class);
       gameExceptionRule.expectErrorCode(100);

        // 로직 수행
        gameService.test();
}



이로써, 사용자가 정의한 Exception의 경우에도 Exception 내부의 값을 체크할 수 있게 되었다.

반응형

'개발 > Testing' 카테고리의 다른 글

[Mockito] Spring MVC Mockito  (0) 2016.06.11
[JUnit] Parameterized  (0) 2016.06.11
[PowerMock] private mock  (0) 2016.06.11
[Mockito] Spring RestTemplate Mocking  (0) 2016.06.11
[BDD] JBeHave를 이용한 Behavior Driven Development  (0) 2016.06.11
,