JUnit Mock Object Equality

controller JUnit 테스트를 위해 service를 mocking 하여 테스트시 equals 관련 문제를 겪은 적이 있다. 우선 아래와 같은 코드가 있다고 할때,
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// RestController
@GetMapping(value = "/test/{id}/info")
public TestInfo getTestInfo(@Valid RequestCommand command) {
    return TestInfoService.getTestInfo(command);
}

// Service
public TestInfo getTestInfo(RequestCommand command) {
    // ... command에 따른 로직

    return new TestInfo("test");
}

// Command class
public class RequestCommand {
    private String id;

    // getters & setters
}

// Domain class
public class TestInfo {
    private String str;

    public TestInfo(String str) {
        this.str = str;
    }

    // getters & setters
}
설명)
  1. controller는 path variable로 id를 RequestCommand 객체로 받는다.
  2. 이 command를 DTO로 service로 넘겨 연산을 하고 entity를 받는다.
  3. 받은 entity를 json으로 응답한다.

이 controller의 service를 mocking 하여 Unit 테스트를 하려면 아래와 같이 될텐데,
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Test
public void getTestInfo_200() throws Exception {
    RequestCommand request = new RequestCommand();
    request.setId("1");

    TestInfo info = new TestInfo("test~");

    doReturn(info)
        .when(TestInfoService) // (1) service mock
        .getTestInfo(request)); // (2) 문제 부분

    // then
    mvc.perform(get("/test/{id}/info", "1")
        .contentType(APPLICATION_JSON))
        .andDo(print())
        .andExpect(jsonPath("$.str").value("test~"));
}

이때 (2) 부분에서 equals 비교가 일어 나는듯. controller가 request 받았을때 만든 command 객체는 mock에 넣어준 request command 객체와 다르기(referenece 비교) 때문에 테스트가 실패한다.

간단하게는 RequestCommand에 equals/hashCode를 override 하는 것이지만, reference 비교가 유효한 곳에는 그렇게 할 수 없다. 이 때는 아래처럼 mokito의 argThat()을 사용해 보자.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Test
public void getTestInfo_200() throws Exception {
    RequestCommand request = new RequestCommand();
    request.setId("1");

    TestInfo info = new TestInfo("test~");

    doReturn(info)
        .when(TestInfoService) // mocking된 서비스
        .getTestInfo(argThat(new ArgumentEqualityMatcher(request))); // object equals가 필요한 부분

    // then
    mvc.perform(get("/test/{id}/info", "1")
        .contentType(APPLICATION_JSON))
        .andDo(print())
        .andExpect(jsonPath("$.str").value("test~"));
}

// "equals" for mock arguments.
private class ArgumentEqualityMatcher implements ArgumentMatcher<RequestCommand> {
    private final FlagshipStoreCommand expected;

    ArgumentEqualityMatcher(FlagshipStoreCommand expected) {
        this.expected = expected;
    }

    @Override
    public boolean matches(FlagshipStoreCommand command) {
        return expected.getId() == command.getId()
    }
}

위 처럼 ArgumentMatcher를 구현해주면 argThat()에서 이를 이용해 매칭을 하니 equals/hashCode를 override 하지 않아도 된다.

update) argThat()을 사용하면 Matcher를 사용하지 않아도 될듯~

참고: https://automationrhapsody.com/assert-mockito-mock-method-arguments-no-equals-method-implemented/

댓글

이 블로그의 인기 게시물

[Protocol] WIEGAND 통신

Orange for Oracle에서 한글 깨짐 해결책

[UI] GNB·LNB·SNB·FNB 용어 설명