Spring/Spring Test
[Spring][Test] 1. Unit Testing
noahkim_
2025. 4. 19. 22:34
1. 단위 테스트
- 테스트 시에는 코드를 독립적으로 테스트할 수 있어야 해요. (Spring 없이도 테스트가 가능해야 함)
- Spring의 Dependency Injection 덕분에, J2EE 전통 방식보다 컨테이너에 덜 의존적인 코드 작성이 가능
- POJO(Plain Old Java Object) 중심의 구조를 만들 수 있음
- 순수한 자바 코드로 작성된 객체로 설계 가능 (특별한 인터페이스나 어노테이션 등에 의존 ❌)
- 객체를 직접 생성하여 테스트할 수 있음을 의미함
장점
항목 | 설명 |
Spring IoC와의 연관성
|
⚡ 빠른 실행 속도 | DB나 서버 등 외부 시스템 없이 실행 가능 |
IoC로 객체를 외부에서 주입하므로, 테스트 시 Mock/Stubs로 대체 가능
|
🛠️ 테스트 주도 개발(TDD)에 유리 | 빠르고 반복적인 테스트 작성 가능 |
POJO 중심 구조로 인해 객체 생성과 테스트가 간편함
|
🚀 생산성 향상 | 빠른 테스트 주기로 리팩토링과 개발 속도 향상 |
의존성 주입(DI)으로 컴포넌트 간 결합도 낮음
→ 리팩토링 용이 |
🧱 레이어 분리 & 컴포넌트화 | Controller → Service → Repository 계층 분리 용이 |
Spring의 IoC 설계 원칙을 따르면 자연스럽게 분리 구조 유도
|
🔁 Mock / Stub 활용 가능 | 테스트 시 진짜 객체 대신 가짜(mock/stub) 객체로 동작 검증 가능 |
Service에서 DB 접근하는 Repository를 Mock으로 대체 가능
|
🎯 비즈니스 로직 집중 테스트 가능 | 외부 시스템 영향 없이 핵심 로직만 검증 가능 |
순수 POJO로 로직을 구성하면 DI만으로도 테스트가 충분함
|
2. Spring에서 제공하는 도구들
Mock
분류 | 주요 클래스/객체 | 사용 목적 | 특징 및 확장 도구 |
Environment 관련 Mock | MockEnvironment MockPropertySource |
환경 속성에 의존하는 코드 테스트 |
@Value, Environment.getProperty() 등을 사용하는 코드 테스트에 적합
|
Servlet API 관련 Mock | MockHttpServletRequest MockHttpServletResponse MockFilterChain MockHttpSession 등 |
Spring MVC 테스트 |
실제 서버 없이 서블릿 동작을 모방한 객체 제공
📌 확장 도구: MockMvc |
WebFlux 관련 Mock | MockServerHttpRequest MockServerHttpResponse MockServerWebExchange |
Spring WebFlux 기반 테스트 |
비동기 환경 테스트 지원
Mono<Void>, Flux 흐름 검증 📌 확장 도구: WebTestClient |
예제) Environment 관련 Mock
더보기
public class AppProperties {
@Value("${app.mode}")
private String mode;
@Value("${app.name}")
private String name;
public String getMode() {
return mode;
}
public String getName() {
return name;
}
}
public class MockEnvironmentTest {
private static MockEnvironment mockEnvironment;
private static AppProperties appProperties;
@BeforeAll
static void setUp() {
mockEnvironment = new MockEnvironment();
mockEnvironment.setProperty("app.mode", "test");
mockEnvironment.setProperty("app.name", "testApp");
appProperties = new AppProperties();
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setEnvironment(mockEnvironment);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean(AppProperties.class, () -> appProperties);
context.addBeanFactoryPostProcessor(configurer);
context.refresh();
}
@Test
void testMockEnvironment() {
assertEquals("test", mockEnvironment.getProperty("app.mode"));
assertEquals("testApp", mockEnvironment.getProperty("app.name"));
}
@Test
void testAppProperties() {
assertEquals("test", appProperties.getMode());
assertEquals("testApp", appProperties.getName());
}
}
예제) Servlet API 관련 Mock
더보기
@RestController
public class MyController {
@GetMapping("/test/hello")
public String hello(@RequestParam String name) {
return "Hello, " + name;
}
}
public class MyControllerTest {
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(new MyController()).build();
}
@Test
void helloTest() throws Exception {
mockMvc.perform(get("/test/hello?name=Spring"))
.andExpect(status().isOk())
.andExpect(content().string("Hello, Spring"));
}
}
Utils
클래스 | 설명 | 사용 예시 |
AopTestUtils | 프록시로 감싸진 객체의 실제 대상(Target) 객체에 접근 |
Mockito 등으로 mock 객체를 감싸놓았을 때 내부 접근
|
ReflectionTestUtils | private/protected 필드나 메서드에 접근 (값 설정/호출) |
JPA 엔티티나 @Autowired된 필드 수동 설정 등
|
TestSocketUtils | 사용 가능한 랜덤 포트 조회 (테스트용) - 포트의 지속적인 사용 가능성은 보장하지 않음 (운영 환경에서는 주의 필요) |
외부 서버와 통신하는 통합 테스트 등에서 유용
|
예제) AopTestUtils
더보기
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* MyService.hello(..))")
public void logBefore() {
System.out.println("Before method");
}
}
@Service
public class MyService {
public String hello(String name) {
return "Hello, " + name;
}
}
public class AopTest {
@Test
void testGetTargetObject() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AopConfig.class);
ctx.refresh();
MyService proxy = ctx.getBean(MyService.class);
MyService target = AopTestUtils.getTargetObject(proxy);
assertEquals("Hello, spring", target.hello("spring"));
}
}
예제) ReflectionTestUtils
더보기
public class SecretService {
private String secret = "initial";
private String getSecretMessage(String name) {
return secret + " message for " + name;
}
}
public class SecretServiceTest {
@Test
void testReflectionAccess() {
SecretService secretService = new SecretService();
ReflectionTestUtils.setField(secretService, "secret", "updated");
String result = ReflectionTestUtils.invokeMethod(secretService, "getSecretMessage", "Spring");
assertEquals("updated message for Spring", result);
}
}
Spring MVC
도구 | 설명 | 사용 방법 |
ModelAndViewAssert | Spring MVC 컨트롤러가 반환한 ModelAndView 객체를 검증 |
단위 테스트에서 모델, 뷰 검증 시 사용
|
MockHttpServletRequest MockHttpServletResponse MockHttpSession |
컨트롤러 단위 테스트에서 Spring Context 없이 동작을 검증 DispatcherServlet, Filter, ViewResolver 등 생략 가능 |
POJO 방식 컨트롤러 테스트에 사용
|
예제) ModelAndViewAssert
더보기
@Controller
public class SampleController {
@RequestMapping("/test/greet")
public ModelAndView greet() {
ModelAndView mav = new ModelAndView("greetingView");
mav.addObject("message", "Hello, Spring!");
return mav;
}
}
public class ModelAndViewTest {
@Test
void testMav() {
SampleController controller = new SampleController();
ModelAndView mav = controller.greet();
assertViewName(mav, "greetingView");
assertModelAttributeAvailable(mav, "message");
assertModelAttributeValue(mav, "message", "Hello, Spring!");
}
}
예제) MockHttp*
더보기
@Controller
public class LoginController {
public ModelAndView login(@RequestParam String userId, HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession();
session.setAttribute("user", userId);
response.addHeader("Set-Custom-Header", "logged-in");
ModelAndView mav = new ModelAndView("home");
mav.addObject("user", userId);
return mav;
}
}
public class MockServletTest {
@Test
void testLoginController() {
LoginController controller = new LoginController();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
MockHttpSession session = new MockHttpSession();
request.setSession(session);
request.setParameter("userId", "Spring");
ModelAndView mav = controller.login("Spring", request, response);
assertEquals("home", mav.getViewName());
assertEquals("Spring", mav.getModel().get("user"));
assertEquals("Spring", session.getAttribute("user"));
assertEquals("logged-in", response.getHeader("Set-Custom-Header"));
}
}
출처