BDDMockito.given() 은 어떻게 사용해야 하나요? (Strict stubbing argument mismatch)

Flash·2023년 2월 4일
0

Spring Boot

목록 보기
4/4
post-thumbnail

모든 내용은 아직 초보인 제가 스스로 학습하며 알게된 정보와 그에 대한 제 견해가 섞여있기 때문에 공식적이거나 정확한 정보가 아닙니다. 부족한 부분이 있다면 날카로운 지적들 주시면 감사하겠습니다 :)

Strict stubbing argument mismatch

Service Layer 의 unit test 를 작성하다가 만난 에러다. 테스트 실패한 코드는 다음과 같다.

@ExtendWith(MockitoExtension.class)
class TodoServiceTest {

    @InjectMocks
    private TodoService todoService;

    @Mock
    private TodoRepository todoRepository;

    private static TodoRequestDto todoRequestDto;

    @BeforeAll
    static void beforeEach() {
        todoRequestDto = new TodoRequestDto();
        todoRequestDto.setContent("TodoService Test");
        todoRequestDto.setIsDone(true);
    }

    @Test
    @DisplayName("Todo 생성 & 저장")
    void testCreateTodo() {

        //given
        TodoEntity todoEntity = todoRequestDto.toEntity();
        given(todoRepository.save(todoEntity)).willReturn(todoEntity); // -> Error 발생
//        given(todoRepository.save(any())).willReturn(todoEntity); -> 적상 작동

        //when
        Long createId = todoService.createTodo(todoRequestDto);

        //then
        assertThat(createId).isEqualTo(todoEntity.getId());
    }
    
		...
    
}

Error 메시지는 다음과 같았다.

Strict stubbing argument mismatch. Please check:
 - this invocation of 'save' method:
    todoRepository.save(
    com.todolist.model.entity.TodoEntity@65aa6596  => 여기!!
);
    -> at com.todolist.service.TodoService.createTodo(TodoService.java:26)
 - has following stubbing(s) with different arguments:
    1. todoRepository.save(
    com.todolist.model.entity.TodoEntity@2c7d121c  => 여기!!
);
      -> at com.todolist.service.TodoServiceTest.testCreateTodo(TodoServiceTest.java:50)
Typically, stubbing argument mismatch indicates user mistake when writing tests.
Mockito fails early so that you can debug potential problem easily.
However, there are legit scenarios when this exception generates false negative signal:
  - stubbing the same method multiple times using 'given().will()' or 'when().then()' API
    Please use 'will().given()' or 'doReturn().when()' API for stubbing.
  - stubbed method is intentionally invoked with different arguments by code under test
    Please use default or 'silent' JUnit Rule (equivalent of Strictness.LENIENT).
For more information see javadoc for PotentialStubbingProblem class.
org.mockito.exceptions.misusing.PotentialStubbingProblem:

//given 에 해당하는 given(todoRepository.save(todoEntity)~~todoEntity
//when 에 해당하는 todoService.createTodo(todoRequestDto) 를 실행함으로써 작동되는 todoRepository.save(todoRequestDto.toEntity())todoRequestDto.toEntity() 가 서로 다른 객체이기 때문에 stubbing argument mismatch 를 뱉는 것이다!

이런 이유로 Stubbing 을 할 때 내가 정의한 객체를 given() 의 매개변수로 넣어줄 것이 아니라 any() 를 사용해야한다는 것을 알게 됐다.

그럼 항상 any()만 써야할까?

그럼 이런 생각이 들었다. 내가 정의한 객체가 아닌 Long type 의 값을 넣을 때는 given(any()) 를 쓸 필요 없이 given(1L) 과 같이 써도 문제가 없을까?

문제는 없었다!
Service Test 의 다른 테스트 메소드에 바로 적용을 해보았다. 내 코드는 다음과 같았다.

@ExtendWith(MockitoExtension.class)
class TodoServiceTest {

    @InjectMocks
    private TodoService todoService;

    @Mock
    private TodoRepository todoRepository;

    private static TodoRequestDto todoRequestDto;

    @BeforeAll
    static void beforeEach() {
        todoRequestDto = new TodoRequestDto();
        todoRequestDto.setContent("TodoService Test");
        todoRequestDto.setIsDone(true);
    }

    	...

    @Test
    @DisplayName("Todo 하나 불러오기")
    void testGetTodo() {

        //given
        TodoEntity todoEntity = todoRequestDto.toEntity();
//      given(todoRepository.findById(any())).willReturn(Optional.of(todoEntity));  -> 위의 문제를 만나고 작성했기에 아무 생각 없이 any() 를 썼다. 
		given(todoRepository.findById(1L)).willReturn(Optional.of(todoEntity));  // -> 이렇게 작성해도 문제 없다!

        //when
        TodoResponseDto findTodoDto = todoService.getTodo(1L);

        //then
        assertThat(findTodoDto).isNotNull();
        assertThat(findTodoDto.getContent()).isEqualTo("TodoService Test");
        assertThat(findTodoDto.getIsDone()).isEqualTo(true);
    }
}

왜 이럴까?
Long 타입은 Primitive type 이 아닌 Wrapper Class 이지만 Long a = 1LLong b = 1L 을 비교할 때는 Longequals 메소드에서 unboxing 을 통해 같은 값인지만 비교하기 때문이다. 실제로 ab는 구분되는 객체지만 두 녀석을 비교할 때는 같은 곳을 가리키기 때문에 Stubbing Argument Mismatch 가 일어나지 않는 것이다!

진짜 그럴까?

궁금해져서 테스트를 돌려봤다. Test code 는 다음과 같다.

@Test
void testLong(){
    Long a = 129L;
    Long b = 129L;
    assertThat(a).isEqualTo(b);
}

결과는 다음과 같았다.

long 타입으로 unboxing 을 한 후에 비교하는 것이라면 다음과 같이 해도 테스트가 성공해야한다고 생각했다.

@Test
void testLong(){
    Long a = 129L;
    long b = 129L;
    assertThat(a).isEqualTo(b);
}

역시 초록불을 만났다. Service Test 를 짜다가 생각지 못했던 더 근본적인 내용을 만나 공부할 수 있게 되어 감사하다.

profile
개발 빼고 다 하는 개발자

0개의 댓글