테스트 코드/JUnit

Mockito를 이용한 테스트 코드(@RunWith, @ExtendWith)

Wings of Freedom 2022. 7. 11. 16:26

● Mockito로 테스트코드를 작성하는 이유

@SpringBootTest 로 테스트코드를 작성하다보며 시간이 지날수록 프로젝트의 덩치가 커지면서 테스트코드를 실행하는데 굉장히 많은 시간이 걸리며 빌드시에도 많은 시간이 소요된다. 따라서 단위 테스트단위로 코드 작성시에는 mockito를 이용하여 테스트코드를 짜는것이 유용하다고 판단된다.

 

@RunWith(MockitoJUnitRunner.class)

@ExtendWith(MockitoExtension.class)

모두 Mockito Mock 객체를 사용하기 위한 Annotation이다

@RunWith는  junit4

@ExtendWith 는 junit5 사용시 정의하면 된다.

 

● Mockito를 이용한 예제

import java.util.Optional;

import org.springframework.stereotype.Service;

import net.suby.testcode.dto.UserDto;
import net.suby.testcode.entity.User;
import net.suby.testcode.enums.Role;
import net.suby.testcode.repository.UserRepository;

import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    private final ObjectMapper objectMapper;

    private PasswordEncoder passwordEncoder;

    public UserDto.Response createUser(UserDto.Request userRequest) {
        User user = objectMapper.convertValue(userRequest, User.class);
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        if(userRequest.getRole() == Role.ADMIN){
            log.info("admin 계정을 생성합니다.");
        } else if(userRequest.getRole() == Role.TEAM_LEADER){
            log.info("팀장 계정을 생성합니다.");
        } else {
            log.info("팀원 계정을 생성합니다.");
        }

        User saveUser = userRepository.save(user);
        return objectMapper.convertValue(saveUser, UserDto.Response.class);
    }

    public UserDto.Response getUser(int userId) {
        Optional<User> optionalUser = userRepository.findById(userId);
        if(optionalUser.isPresent()){
            return objectMapper.convertValue(optionalUser.get(), UserDto.Response.class);
        }
        return UserDto.Response.builder().build();
    }
}
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;

import net.suby.testcode.dto.UserDto;
import net.suby.testcode.enums.Role;
import net.suby.testcode.repository.UserRepository;

import com.fasterxml.jackson.databind.ObjectMapper;

@ExtendWith(MockitoExtension.class)
class TestApplication {

    @InjectMocks
    UserService userService;

    @Mock
    private UserRepository userRepository;

    @Spy
    private ObjectMapper objectMapper;

    @Mock
    private PasswordEncoder passwordEncoder;

    @BeforeEach
    public void setUp() {
        ReflectionTestUtils.setField(userService, "passwordEncoder", passwordEncoder);
    }

    @Test
    void createUser(){
        given(passwordEncoder.encode(anyString())).willReturn("encoderPasswoed");
        UserDto.Request request = UserDto.Request.builder()
                                                 .name("name")
                                                 .password("password@")
                                                 .role(Role.ADMIN)
                                                 .build();
        userService.createUser(request);
    }
}

- @InjectMocks

test class를 정의한다. 만약 해당 클래스의 주입되는 객체가 있다면 @Mock, @Spy에 정의된 mock이 자동으로 주입된다. 위의 예제를 본다면 userService(userRepository, objectMapper)가 주입된 것이다. (passwordEncoder는 주입객체가 아닙니다)

 

- @Mock

mock 객체를 생성한다.

 

- @Spy

mock은 given을 통해 원하는 임의의 값을 넘기지만 mock이 아닌 실제 구현체로 테스트를 진행하고 싶다면 @Spy를 활용하면 된다. 위의 예제에서 objectMapper는 실제 기능을 사용하려고 합니다.

 

- ReflectionTestUtils

만약 주입객체가 아닌 private 한 형태의 클래스나 변수일 경우 ReflectionTestUtils을 통해 객체를 넣을 수 있습니다.

 

출처 : https://myborn.tistory.com/16?category=1030862