이번 포스트에서는 스프링에서 테스트 코드를 작성하는 예시를 설명한다.
1. API 테스트
API 테스트는 서버가 API 요청에 대해 예상된 응답을 반환하는지 확인하기 위해 작성된다.
이를 위해 MockMvc를 사용하여 가상의 API 요청을 보내고 응답을 검증하는 방법을 주로 사용한다.
- MockMvc는 post(), get(), put(), delete() 등의 HTTP 메소드를 사용하여 요청을 전송하고, andExpect()를 통해 응답을 검증한다.
- 예시 코드는 MockMvc를 사용하여 API 요청을 보내고 응답 상태를 검증하는 테스트이다. 여기서 mockMvc.perform() 부분은 API 요청을 보내는 부분이고, andExpect(status().isNotFound()) 부분은 응답 상태를 검증하는 부분이다.
- 다만, 테스트 내용에 따라 달라질 수 있다. 예를 들어, 특정 API 호출이 성공하고 예상된 데이터를 반환하는지 검증하려면 andExpect(status().isOk())와 같이 응답 상태를 검증하고, andExpect(content().json(expectedJson)) 등의 메소드를 사용하여 응답 본문을 검증할 수 있다.
- 또한, @WebMvcTest 어노테이션을 사용하면 웹 계층만 로드하여 MockMvc 테스트를 더 빠르고 효율적으로 수행할 수 있다.
아래는 이에 대한 예시이다.
mockMvc.perform(post("/api/v1/users/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsBytes(new UserLoginRequest(username, password)))
).andDo(print())
.andExpect(status().isOk())
.andExpect(content().json(expectedJson));
2. Mockito의 when 메서드 사용예시
2-1. 로그인 테스트 코드 예시
@Test
void 로그인() throws Exception {
String username = "userName";
String password = "password";
when(userService.login(username, password)).thenReturn("test_token");
//성공하는 경우
mockMvc.perform(post("/api/v1/users/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsBytes(new UserLoginRequest(username, password)))
).andDo(print())
.andExpect(status().isOk());
}
- when() 메소드는 테스트 케이스를 설정하는 단계로, 실제 테스트를 수행하는 것은 아니다. when() 메소드를 사용하여 특정 메소드가 호출될 때 어떤 동작을 수행해야 하는지를 정의한다. 이렇게 설정한 동작은 이후에 실제 테스트를 수행하는 과정에서 사용된다.
- 위 코드에서 when(userService.login(username, password)).thenReturn("test_token"); 라인은 userService의 login() 메소드가 username과 password 인자로 호출될 때 "test_token"을 반환하도록 설정한다. 이 설정은 이후의 mockMvc.perform() 테스트에서 사용된다.
2-2. 작성한 when()은 실제로 어떻게 사용될까?
- mockMvc.perform() 메소드를 사용하여 실제로 /api/v1/users/login 엔드포인트에 POST 요청을 보내고, 이 요청이 잘 처리되는지 테스트한다. 요청 본문에는 UserLoginRequest 객체가 JSON 형태로 담겨 있다.
- 이 요청이 처리되면서 userService.login() 메소드가 호출되는데, 이때 when()으로 설정해둔 대로 "test_token"을 반환하게 된다. 이로 인해 실제 userService의 구현에 의존하지 않고도 테스트를 수행할 수 있다. 이는 외부 시스템에 의존하지 않고 독립적으로 테스트를 수행하기 위한 중요한 단계이다.
- 실제 컨트롤러의 login() 메소드 안에서 userService.login(request.getUserName(), request.getPassword()); 이 메서드가 호출되면, when()으로 설정해둔 대로 "test_token" 값을 반환한다.
- 즉, 실제 userService의 login() 메소드의 구현이 어떻든 간에, 이 테스트 케이스에서는 "test_token"이 반환되는 것이 보장된다. 이렇게 함으로써 테스트의 의존성이 줄어들고, 테스트의 격리가 보장되며, 이에 따라 테스트의 안정성이 향상된다.
- 실제 컨트롤러의 login() 메소드 안에서 userService.login(request.getUserName(), request.getPassword()); 이 메서드가 호출되면, when()으로 설정해둔 대로 "test_token" 값을 반환한다.
3. Mockito의 when 메서드의 여러 가지 매처(matcher) 설명
- Mockito 테스트 프레임워크에서는 여러 가지 매처(matcher)를 제공한다. 매처는 when() 메소드의 인자에 사용되며, 특정 메소드 호출을 어떤 인자에 대해 모킹할지 결정하는데 사용된다.
3-1. 아래는 몇 가지 대표적인 매처들이다.
- any()
- 어떤 종류의 값이든 매치된다. 예를 들어, when(myService.processData(any())).thenReturn("result"); 코드는 myService.processData() 메소드가 어떤 인자로 호출되든 간에 "result"를 반환하도록 설정한다.
- 어떤 종류의 값이든 매치된다. 예를 들어, when(myService.processData(any())).thenReturn("result"); 코드는 myService.processData() 메소드가 어떤 인자로 호출되든 간에 "result"를 반환하도록 설정한다.
- any(Class<T> type)
- 주어진 클래스의 어떤 인스턴스라도 매치된다. 예를 들어, any(String.class)는 모든 문자열에 매치되며, any(MyClass.class)는 MyClass의 모든 인스턴스에 매치된다.
- 주어진 클래스의 어떤 인스턴스라도 매치된다. 예를 들어, any(String.class)는 모든 문자열에 매치되며, any(MyClass.class)는 MyClass의 모든 인스턴스에 매치된다.
- anyInt(), anyLong(), anyDouble()
- 모든 정수, 실수 등에 매치된다. 이들은 특정 유형의 기본 데이터 타입에 대해 매치되는 값들을 설정하는데 사용된다.
- 모든 정수, 실수 등에 매치된다. 이들은 특정 유형의 기본 데이터 타입에 대해 매치되는 값들을 설정하는데 사용된다.
- eq()
- 특정 값에만 매치된다. 예를 들어, when(myService.processData(eq("input"))).thenReturn("result"); 코드는 myService.processData() 메소드가 "input" 문자열로만 호출될 때 "result"를 반환하도록 설정한다.
- 특정 값에만 매치된다. 예를 들어, when(myService.processData(eq("input"))).thenReturn("result"); 코드는 myService.processData() 메소드가 "input" 문자열로만 호출될 때 "result"를 반환하도록 설정한다.
- isNull(), isNotNull()
- null 또는 non-null 값에 매치된다.
- null 또는 non-null 값에 매치된다.
- matches(String regex)
- 정규 표현식에 매치되는 문자열에 매치된다.
- 정규 표현식에 매치되는 문자열에 매치된다.
- startsWith(String prefix), endsWith(String suffix), contains(String substring)
- 특정 접두사로 시작하거나, 특정 접미사로 끝나거나, 특정 부분 문자열을 포함하는 문자열에 매치된다.
- 특정 접두사로 시작하거나, 특정 접미사로 끝나거나, 특정 부분 문자열을 포함하는 문자열에 매치된다.
3-2. 위의 모든 매처들을 코드로 설명하겠다.
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.mockito.InjectMocks;
import org.mockito.Mock;
@SpringBootTest
public class MyServiceTest {
@Mock
private MyService myService;
@InjectMocks
private MyClient myClient;
@Test
public void testMatchers() {
// any() 매처: 어떤 값이든 매치
when(myService.processData(any())).thenReturn("result");
// any(Class<T> type) 매처: 주어진 클래스의 어떤 인스턴스라도 매치
when(myService.processData(any(String.class))).thenReturn("result");
when(myService.processData(any(MyClass.class))).thenReturn("result");
// anyInt(), anyLong(), anyDouble() 매처: 모든 정수, 실수 등에 매치
when(myService.processData(anyInt())).thenReturn("result");
when(myService.processData(anyLong())).thenReturn("result");
when(myService.processData(anyDouble())).thenReturn("result");
// eq() 매처: 특정 값에만 매치
when(myService.processData(eq("input"))).thenReturn("result");
// isNull(), isNotNull() 매처: null 또는 non-null 값에 매치
when(myService.processData(isNull())).thenReturn("result");
when(myService.processData(isNotNull())).thenReturn("result");
// matches(String regex) 매처: 정규 표현식에 매치
when(myService.processData(matches("\\d+"))).thenReturn("result");
// startsWith(String prefix), endsWith(String suffix), contains(String substring) 매처
when(myService.processData(startsWith("prefix"))).thenReturn("result");
when(myService.processData(endsWith("suffix"))).thenReturn("result");
when(myService.processData(contains("substring"))).thenReturn("result");
}
}
- 이 외에도 Mockito는 사용자가 직접 매처를 정의하거나 커스텀 매처를 사용할 수 있도록 지원하므로, 더 복잡한 조건에 맞게 메소드 호출을 모킹할 수 있다. 이는 org.mockito.ArgumentMatchers 클래스를 확인하면 더 많은 정보를 얻을 수 있다.
2023.08.09 - [Spring 테스트코드] - Spring(스프링) - 테스트 코드의 기초(2)
2023.08.09 - [Spring 테스트코드] - Spring(스프링) - 테스트 코드의 기초(4)
반응형
'Spring > 테스트 코드' 카테고리의 다른 글
[스프링, 스프링 부트] Spring test - service 테스트에서 만난 오류 (0) | 2023.08.16 |
---|---|
[스프링, 스프링 부트] Spring test - Mock에 대한 이해 (0) | 2023.08.09 |
[스프링, 스프링부트] Spring test - 테스트 코드의 기초(5) [CRUD 테스트] (0) | 2023.08.09 |
[스프링, 스프링 부트] Spring test - 테스트 코드의 기초(4) [mock 테스트] (0) | 2023.08.09 |
[스프링, 스프링 부트] Spring test - 테스트 코드의 기초(2) [MockMvc, MockBean] (0) | 2023.08.09 |