Home doReturn과 thenReturn
Post
Cancel

doReturn과 thenReturn

스프링 doReturn과 thenReturn에 대해 학습하며 작성된 글입니다. 학습 과정에서 작성된 글이기 때문에 잘못된 내용이 있을 수 있으며, 이에 대한 지적이나 피드백은 언제든 환영입니다.

image







1. doReturn과 thenReturn


두 메서드 모두 Mockito 프레임워크가 제공하는 메서드로, Stub으로 테스트 대역을 지정하고 Mock과 Spy의 특정 메서드를 호출하면 설정한 값을 반환 합니다. 이 둘은 유사하지만, 타입 세이프(Type Safety), 사이드 이펙트(Side-Effect)에 대해 차이점이 있는데 이에 대해 살펴보겠습니다. 우선 이를 위해 사용자의 이름을 받아 저장하는 간단한 예제를 만들겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequiredArgsConstructor
public class UserCommandController {

    private final UserCommandService userCommandService;

    @PostMapping
    public UserResponse save(@RequestBody UserSignupRequest request) {
        User newUser = new User(request.getName());
        User savedUser = userCommandService.save(newUser);
        return new UserResponse(savedUser);
    }
}
1
2
3
4
5
6
7
8
9
10
11
@Service
@RequiredArgsConstructor
public class UserCommandService {

    private final UserJpaRepository userJpaRepository;

    @Transactional
    public User save(User user){
        return userJpaRepository.save(user);
    }
}
1
2
public interface UserJpaRepository extends JpaRepository<User, Long> {
}
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long userId;

    private String name;

    protected User() {
    }

    public User(String name) {
        this.name = name;
    }

    public User(Long userId, String name) {
        this.userId = userId;
        this.name = name;
    }

    public Long getUserId() {
        return userId;
    }

    public String getName() {
        return name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User user)) return false;
        return getUserId().equals(user.getUserId());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getUserId());
    }

    @Override
    public String toString() {
        return userId.toString();
    }
}







1-1. 타입 세이프(Type Safety)

doReturn 메서드는 컴파일 시점에 타입 체크를 하지 않습니다. 아래 코드를 보면 UserCommandService의 반환 타입은 User인데 doReturn의 기댓값으로 1L을 넣어도 오류가 발생하지 않습니다.

1
2
3
4
5
6
7
8
9
10
11
@Service
@RequiredArgsConstructor
public class UserCommandService {

    private final UserJpaRepository userJpaRepository;

    @Transactional
    public User save(User user){
        return userJpaRepository.save(user);
    }
}

image







하지만 테스트를 실행시켜보면 아래와 같이 오류가 발생하게 됩니다.

image







반면 thenReturn의 경우 컴파일 시점에 타입 체크를 하며 타입 세이프한 테스트를 진행할 수 있습니다. 이를 정리해보면 thenReturn은 컴파일 시점에 오류를 알 수 있지만 doReturn은 알 수 없다는 차이가 있습니다.

image







1-2. 사이드 이펙트(Side-Effect)

thenReturn은 실제 메서드를 호출하며 doReturn은 실제 메서드를 호출하지 않고 최종 결과만 반환합니다. 따라서 thenReturn은 사이드 이펙트가 존재하며, doReturn은 존재하지 않는 차이가 있습니다. 이를 확인하기 위해 UserCommandService에 아래와 같이 로그를 볼 수 있도록 어노테이션을 추가했습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
@Slf4j
@Service
@RequiredArgsConstructor
public class UserCommandService {

    private final UserJpaRepository userJpaRepository;

    @Transactional
    public User save(User user) {
        log.info("UserCommandService save method call");
        return userJpaRepository.save(user);
    }
}








thenReturn 테스트를 실행시키면 로그가 남는 것을 확인할 수 있는데, 즉 실제 메서드를 호출한 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class SpyMethodCallTest extends AbstractTestConfiguration {

    @SpyBean
    private UserCommandService userCommandService;

    @SpyBean
    private UserCommandController userCommandController;

    @Test
    @DisplayName("스파이는 실제 메서드를 호출하기 때문에 메서드 호출 횟수가 1이다.")
    void thenReturn_실제_메서드_호출_테스트() {
        // given
        UserCommandService spy = spy(userCommandService);
        User newUser = new User("devjun10");
        User expected = new User(1L, "devjun10");
        when(spy.save(newUser)).thenReturn(expected);

        // when
        spy.save(newUser);

        // then
        verify(spy, times(1)).save(newUser);
    }
}

image








반면 doReturn의 경우 로그가 남지 않는 것을 볼 수 있습니다. 즉 thenReturn은 실제 메서드를 호출하기 때문에 그 과정에서 상태를 바꾸는 사이드 이펙트를 발생시키지만, doReturn의 경우 설정한 응답 값만 반환하며 실제 메서드를 호출하지 않기 때문에 사이드 이펙트가 없습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class SpyMethodCallTest extends AbstractTestConfiguration {

    @SpyBean
    private UserCommandService userCommandService;

    @SpyBean
    private UserCommandController userCommandController;

    @Test
    @DisplayName("스파이는 실제 메서드를 호출하기 때문에 메서드 호출 횟수가 1이다.")
    void thenReturn_실제_메서드_호출_테스트() {
        // given
        UserCommandService spy = spy(userCommandService);
        User newUser = new User("devjun10");
        User expected = new User(1L, "devjun10");
        doReturn(expected).when(spy.save(newUser));

        // when
        spy.save(newUser);

        // then
        verify(spy, times(1)).save(newUser);
    }
}

image








2. 정리


doReturn와 thenReturn의 차이점은 타입 세이프와 사이드 이펙트의 유무입니다. 단순히 결과적으로만 볼 땐 doReturn이 안 좋아 보일 수 있지만 사이드 이펙트가 없으므로 메서드 호출 시간이 길거나 외부 API를 호출하는 테스트 혹은 문서 작업 등에서는 유용하게 사용될 수 있습니다.




This post is licensed under CC BY 4.0 by the author.

Blocking/Non-Blocking, Sync/Async의 특징과 차이점은 무엇일까?

Timeout 테스트