Spring

[Spring] Mockito의 Strict Stubbing과 lenient의 차이는?

jhkimmm 2022. 5. 12. 00:29

객체를 저장하는 Service의 유닛 테스트 중 발생한 Strict Stubbing 문제와 해결법에 대한 포스팅입니다.

문제가 발생한 상황

...

@InjectMocks private NoteBookService sut;
@Mock private NoteBookRepository noteBookRepository;
@Mock private NoteRepository noteRepository;
...

@Test
void givenNormalNoteBook_whenCreateNoteBook_thenReturnOK(){
    //Given
    NoteBookDto noteBookDto = createNormalNoteBookDto("notebook 1");
    User user = createUser("user1");
    NoteBook noteBook = noteBookDto.toEntity(user);
    given(noteBookRepository.findNoteBookByTitleAndWriter("notebook 1", user)).willReturn(Optional.empty());
    given(noteBookRepository.save(noteBook)).willReturn(noteBook);

    //When
    sut.create(noteBookDto, user);

    //Then
    then(noteBookRepository).should().save(noteBook);
 }

위와 같이 notebookDto객체에서 notebook객체를 생성해 save하는 과정에서 'Strict stubbing argument miasmatch'라는 오류 메시지가 나타나며,

Test Failure가 아니라 아예 Unexpected Exception이 던져지고 테스트 실행 자체가 실패하는 상황이 발생하였습니다.

//에러메세지
...
Strict stubbing argument mismatch. Please check:
 - this invocation of 'save' method:
    noteBookRepository.save(
    com.-.-.-.entity.NoteBook@1eb6e1c
);
    -> at com.-.-.-.service.NoteBookService.create(NoteBookService.java:32)
 - has following stubbing(s) with different arguments:
    1. noteBookRepository.save(
    com.-.-.-.entity.NoteBook@51e0301d
);
 ...

그럼 Strict stubbing이 뭐지?

Strict Stubbing이란 개념이 생소했기에 먼저 Mockito의 소스를 들여다 보니

Strictness.java

Mockito는 LENIENT, WARN, STRICT_STUBS라는 3단계의 Strictness를 가지고 있었습니다.

 

Strictness란 Mockito 2.+ 버전부터 소개된 특징으로 STRICT_STUBS가 기본값이며,

STRICT_STUBS로 설정 되어있을 경우,

SUT에 불필요한(사용되지 않는) Stub 코드가 작성되어 있다면 'UnnecessaryStubbingException'이 발생하게 됩니다.

 

Strict Stubbing의 주요 목적은 아래와 같습니다.

  • 테스트 코드에서 사용되지 않는 stub을 탐지
  • 테스트 코드의 중복과 불필요한 테스트 코드를 줄인다.
  • '죽은' 코드를 제거함으로써 더 깨끗한 테스트로 만든다.
  • 디버그 가능성(Dubuggaility)와 생산성을 향상시킨다.

문제 해결

제 테스트 코드에서는

given(noteBookRepository.save(noteBook)).willReturn(noteBook);

이 부분에서 Exception이 발생했으므로 위 코드가 SUT에서 사용되지 않았다는 의미인데, 저는 에러 메세지에서 힌트를 얻을 수 있었습니다.

//에러메세지
...
Strict stubbing argument mismatch. Please check:
 - this invocation of 'save' method:
    noteBookRepository.save(
    com.-.-.-.entity.NoteBook@1eb6e1c
);
    -> at com.-.-.-.service.NoteBookService.create(NoteBookService.java:32)
 - has following stubbing(s) with different arguments:
    1. noteBookRepository.save(
    com.-.-.-.entity.NoteBook@51e0301d
);
 ...

given에서 Stubbing된 noteBookRepository.save()에 파라미터로 넘긴 Notebook객체와 실제 SUT에서 호출된 noteBookRepository.save()에 파라미터로 넘어간 Notebook객체를 주소값을 기준으로 비교해서, 두 객체가 다르다고 판단 되었습니다. 

 

그렇기 때문에 given으로 만든 테스트 스텁이 사용되지 않은 것으로 판단되어 UnnecessaryStubbingException이 발생한 것입니다.

 

실제로 given에 넘겨준 Notebook 객체와 SUT에서 사용된 Notebook 객체는 필드값을 기준으로 비교되어야 하므로 

NoteBook 엔티티에 롬복의 @EqualsAndHashCode 어노테이션을 추가해주면 테스트가 성공적으로 수행되는 것을 확인할 수 있습니다.

 

[참고]

 

Mockito Strict Stubbing and The UnnecessaryStubbingException | Baeldung

Understand the reasons behind the Mockito UnnecessaryStubbingException and how to avoid it.

www.baeldung.com