개요
Spring Boot, JPA를 사용한 프로젝트의 단위 테스트 중 BDDMockito.willReturn()을 사용하게 됐다.
BDDMockito 클래스를 이용해서 모의 객체에 스텁을 구성할 때,
given()을 사용해 스텁을 정의할 모의 객체의 메서드 호출을 전달하고
willReturn()을 사용해 스텁을 정의한 메서드가 리턴할 값을 지정했다.
willReturn()을 사용하는 경우, 모의 객체의 메서드가 여러 번 호출되면 동일한 인스턴스를 반환하는지 궁금했다.
의문이 생긴 테스트 코드는?
아래는 회원 서비스의 updateMember를 테스트하기 위한 단위 테스트 코드의 일부이다.
@DisplayName("비밀번호는 수정, 닉네임은 그대로")
@Test
public void 회원정보_수정_1() {
//given
MemberForm memberForm = MemberBuilder.builder()
.password("12345678")
.nickname("그대로")
.build();
BDDMockito.given(mockMemberRepo.findOne(any()))
.willReturn(memberForm.toEntity("Before"));
BDDMockito.given(mockBCrypt.encode(any()))
.willReturn("After");
//비밀번호 새로운 값, 기존 값 일치하지 않는다.
BDDMockito.given(mockBCrypt.matches(any(), any()))
.willReturn(false);
//when
memberService.updateMember(1L,"87654321","그대로");
//then
Member member = mockMemberRepo.findOne(1L);
assertThat(member.getNickname()).isEqualTo("그대로");
assertThat(member.getPassword()).isEqualTo("After");
//then 구현 검증
BDDMockito.then(mockMemberRepo).should(never()).findByNickName(any());
}
의문이 생긴 코드는 다음과 같다.
BDDMockito.given(mockMemberRepo.findOne(any()))
.willReturn(memberForm.toEntity("Before"));
모의 객체인 회원 저장소(mockMemberRepo)에서 회원의 정보를 검색하는 findOne() 메서드 호출을 지정한다.
회원 정보를 검색하는 findOne()이 호출되면 memberForm.toEntity를 사용하여
memberForm(폼 객체) -> member(엔티티)로 변환하여 반환하도록 했다.
이 스텁에서는 어떤 값이 매개변수로 들어와도(any()) 비밀번호를 "Before"로 저장하도록 지정했다.
BDDMockito.given(mockBCrypt.encode(any()))
.willReturn("After");
updateMember() 실행 시 비밀번호가 변경되면 BCryptPasswordEncoder.encode를 통해 비밀번호를 암호화한다.
즉, 비밀번호를 변경하는 경우 비밀번호는 "After"로 변경되어 저장되어야 한다.
어떤 문제가 발생했을까?
이 스텁은 테스트 코드에서 두 번 실행된다.
첫 번째, updateMember 메서드 실행 시 회원 정보를 검색하기 위해 사용된다.
@Transactional
public void updateMember(Long memberId, String password, String nickname) {
Member member = memberRepository.findOne(memberId);
...
}
두 번째로, 테스트 코드의 then절에서 회원 정보를 검색하기 위해 사용된다.
Member member = mockMemberRepo.findOne(1L);
1. updateMember에서 첫 번째로 findOne()이 실행되어 비밀번호가 "Before"인 회원 객체가 생성된다.
2. 여기서 회원의 비밀번호는 "After"로 변경된다.
3. 다음 mockMemberRepo.findOne()이 실행되면 비밀번호가 "Before"인 새로운 인스턴스의 회원 객체가 생성되는 거 아닌가?
하지만
내가 예상하 대로 두 번의 findOne() 호출 시 다른 회원 객체가 생성되었다면, 다음 단언으로 인해 테스트는 실패해야 한다.
assertThat(member.getPassword()).isEqualTo("After");
하지만, 테스트 결과는 성공이었다.
테스트가 성공했다는 건, willReturn()으로 반환된 회원 객체가 동일한 인스턴스를 갖는다는 걸 의미한다.
문제의 원인은?
결론부터 말하면 BDDMockito.willReturn()은 동일한 인스턴스를 반환한다.
스택오버플로우의 다음 게시글을 확인해 보면 Mockito thenReturn returns same instance
Mockito.thenReturn() 사용 시 동일한 인스턴스를 반환한다고 한다.
BDDMockito는 BDD(행동 주도 개발)를 사용하여 테스트코드를 작성할 때,
BDD패턴에 맞게 Mockito와 기능은 같지만 이름만 다른 클래스이다.
BDDMockito.willReturn()은 Mockito.thenReturn()과 기능이 동일하다.
willReturn()의 메서드 정의를 확인해 보면 내부적으로 thenReturn을 호출하여 동일한 인스턴스를 반환한다.
다른 인스턴스를 반환하려면 Mockito.thenAnswer(), BDDMockito.willAnswer()를 사용해야 한다.
실제로 willReturn()을 willAnswer()로 수정하고 테스트하면 테스트는 실패한다.
디버깅으로 두 member객체의 인스턴스를 확인해 보아도 두 객체의 인스턴스는 다르다.
결론
스텁을 정의한 메서드가 리턴할 값을 지정할 때,
동일한 인스턴스를 반환해야 한다. -> willReturn()을 사용
다른 인스턴스를 반환해야 한다. -> willAnswer()를 사용한다.
참조
'Java' 카테고리의 다른 글
[Java] Java에서 배열과 컬렉션의 크기 확인( length() vs size() ) (0) | 2024.11.24 |
---|---|
[Java] Comparator의 사용 (1) | 2024.11.21 |
[Spring] Java의 람다식(Lambda)이란? (1) | 2023.08.10 |