Mockito를 이용한 테스트 코드(@RunWith, @ExtendWith)
● 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