Test Double

HelloPongยท2025๋…„ 11์›” 17์ผ

๊ณต๋ถ€

๋ชฉ๋ก ๋ณด๊ธฐ
39/39
post-thumbnail

๐Ÿงช 1. Mock / Stub / Mockito / @MockBean ์ •๋ฆฌ


1. ์™œ ํ…Œ์ŠคํŠธ ๋”๋ธ”(Test Double)์ด ํ•„์š”ํ•œ๊ฐ€

์‹ค์ œ ์ฝ”๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉํ•˜๋ฉด ์ด๋Ÿฐ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค.

  • DB์— ์‹ค์ œ๋กœ ์ฟผ๋ฆฌ ๋‚ ๋ฆผ
  • ์™ธ๋ถ€ API ํ˜ธ์ถœ(๊ฒฐ์ œ, ๋ฌธ์ž, ์นด์นด์˜ค, S3 ๋“ฑ)
  • ๋„คํŠธ์›Œํฌ ์˜์กด
  • ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์„ธํŒ…์ด ๋ณต์žกํ•ด์ง
  • ํ…Œ์ŠคํŠธ๊ฐ€ ๋А๋ฆฌ๊ณ , ์‹คํŒจ ์›์ธ์ด ์ฝ”๋“œ์ธ์ง€ ํ™˜๊ฒฝ์ธ์ง€ ์• ๋งคํ•ด์ง

๊ทธ๋ž˜์„œ ํ…Œ์ŠคํŠธ์—์„œ๋Š” โ€œ์ง„์งœ ๊ฐ์ฒด ๋Œ€์‹  ์‚ฌ์šฉํ•˜๋Š” ๊ฐ€์งœ ๊ฐ์ฒดโ€๋ฅผ ์“ฐ๋Š”๋ฐ, ์ด๊ฒƒ๋“ค์„ ํ†ตํ‹€์–ด ํ…Œ์ŠคํŠธ ๋”๋ธ”(Test Double) ์ด๋ผ๊ณ  ํ•œ๋‹ค.

๋Œ€ํ‘œ์ ์ธ ์ข…๋ฅ˜:

  • Stub
  • Mock
  • Fake
  • Spy

์ด ์ค‘์—์„œ Spring + Mockito ํ™˜๊ฒฝ์—์„œ ๊ฐ€์žฅ ์ž์ฃผ ๋ณด๋Š” ๊ฑด Stub, Mock ์ด๋‹ค.


2. Stub: ๊ฐ€์žฅ ๋‹จ์ˆœํ•œ ํ…Œ์ŠคํŠธ ๋”๋ธ”

2.1 Stub์˜ ์ •์˜

โ€œ๋ฏธ๋ฆฌ ์ •ํ•ด๋‘” ๋ฐ˜ํ™˜๊ฐ’๋งŒ ๋Œ๋ ค์ฃผ๋Š” ๋‹จ์ˆœ ๊ฐ€์งœ ๊ฐ์ฒดโ€

ํŠน์ง•:

  • ๋‚ด๋ถ€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์—†์Œ
  • ํ˜ธ์ถœ ํšŸ์ˆ˜, ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ ๋ถˆ๊ฐ€
  • ์˜ค์ง โ€œํŠน์ • ์ž…๋ ฅ โ†’ ํŠน์ • ์ถœ๋ ฅโ€๋งŒ ๋ณด์žฅํ•˜๋Š” ๋‹จ์ˆœ ๊ตฌํ˜„

2.2 ์˜ˆ์‹œ ์ฝ”๋“œ

public interface UserService {
    User findById(Long id);
}

public class StubUserService implements UserService {

    @Override
    public User findById(Long id) {
        // ํ•ญ์ƒ ๊ฐ™์€ ๊ฐ’ ๋ฐ˜ํ™˜ (ํ…Œ์ŠคํŠธ์šฉ)
        return new User("pong", 27);
    }
}

ํ…Œ์ŠคํŠธ์—์„œ:

UserService userService = new StubUserService();
User user = userService.findById(1L);
// ํ•ญ์ƒ "pong" ๋ฐ˜ํ™˜

2.3 ์–ธ์ œ ์“ฐ๋Š”๊ฐ€

  • โ€œ์ด ๋ฉ”์„œ๋“œ๋Š” ์–ธ์ œ๋‚˜ ์ด ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•˜๊ณ  ์‹ถ๋‹คโ€
  • ํ˜ธ์ถœ ํšŸ์ˆ˜, ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ™์€ ๊ฑด ์‹ ๊ฒฝ ์•ˆ ์“ธ ๋•Œ
  • ์•„์ฃผ ๋‹จ์ˆœํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ, TDD ์—ฐ์Šต์šฉ

์‹ค๋ฌด์—์„œ Stub ํด๋ž˜์Šค๋ฅผ ์ง์ ‘ ๋งŒ๋“œ๋Š” ๊ฒฝ์šฐ๋Š” ์ ์  ์ค„์–ด๋“œ๋Š” ์ถ”์„ธ๋‹ค.
๋Œ€๋ถ€๋ถ„ Mock์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ Stub ๊ธฐ๋Šฅ๊นŒ์ง€ ํ•จ๊ป˜ ์“ฐ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.


3. Mock: Stub + ํ–‰๋™ ๊ฒ€์ฆ๊นŒ์ง€ ๊ฐ€๋Šฅํ•œ ํ…Œ์ŠคํŠธ ๋”๋ธ”

3.1 Mock์˜ ์ •์˜

โ€œ๊ฐ€์งœ ๊ฐ์ฒด์ธ๋ฐ,

  • ์–ด๋–ค ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ๊ณ 
  • ์–ด๋–ป๊ฒŒ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€(ํšŸ์ˆ˜, ํŒŒ๋ผ๋ฏธํ„ฐ)๋ฅผ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒดโ€

Mock์€ Stub๋ณด๋‹ค ํ›จ์”ฌ ๊ฐ•๋ ฅํ•˜๋‹ค.

3.2 Mockito๋ฅผ ์‚ฌ์šฉํ•œ Mock ์˜ˆ์ œ

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;  // ๊ฐ€์งœ Repo

    @InjectMocks
    private UserServiceImpl userService;    // ํ…Œ์ŠคํŠธํ•  ์‹ค์ œ Service

    @Test
    void findById_์ •์ƒ์กฐํšŒ() {
        // given
        User user = new User("pong", 27);
        when(userRepository.findById(1L))
                .thenReturn(Optional.of(user));  // Stub ์—ญํ• 

        // when
        User result = userService.findById(1L);

        // then
        assertThat(result.getName()).isEqualTo("pong");
        verify(userRepository, times(1)).findById(1L); // Mock์˜ ํ–‰๋™ ๊ฒ€์ฆ
    }
}

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ํฌ์ธํŠธ:

  • when(...).thenReturn(...)
    โ†’ Stub ์—ญํ•  (์ž…๋ ฅ โ†’ ์ถœ๋ ฅ ๊ณ ์ •)
  • verify(..., times(1))
    โ†’ Mock ๊ณ ์œ  ๊ธฐ๋Šฅ (ํ˜ธ์ถœ ํšŸ์ˆ˜ ๊ฒ€์ฆ)

3.3 Mock์˜ ์žฅ์ 

  • ๋ฐ˜ํ™˜๊ฐ’์„ ์ž์œ ๋กญ๊ฒŒ ์„ธํŒ… ๊ฐ€๋Šฅ
  • ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋„๋ก ์„ค์ • ๊ฐ€๋Šฅ (thenThrow)
  • ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ํšŸ์ˆ˜ ๊ฒ€์ฆ ๊ฐ€๋Šฅ
  • ์–ด๋–ค ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ํ˜ธ์ถœ๋๋Š”์ง€ ๊ฒ€์ฆ ๊ฐ€๋Šฅ (verify(mock).save(argThat(...)))
  • โ€œ์ด ๋ฉ”์„œ๋“œ๋Š” ํ˜ธ์ถœ๋˜๋ฉด ์•ˆ ๋œ๋‹คโ€๋„ ๊ฒ€์ฆ ๊ฐ€๋Šฅ(never())

Stub์œผ๋กœ๋Š” ํ–‰๋™ ๊ฒ€์ฆ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜์ง€๋งŒ,
Mock์€ ํ–‰๋™๊นŒ์ง€ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ›จ์”ฌ ์œ ์—ฐํ•˜๋‹ค.


4. Mockito: Mock์„ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

4.1 Mockito๋ž€?

โ€œJava์—์„œ Mock ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ ,
๊ทธ ๋™์ž‘์„ ์„ค์ •ํ•˜๊ณ ,
ํ˜ธ์ถœ ์—ฌ๋ถ€๋ฅผ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌโ€

Spring Boot ํ…Œ์ŠคํŠธ์—์„œ ๊ฑฐ์˜ ๊ธฐ๋ณธ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•œ๋‹ค.

์ฃผ์š” ๊ธฐ๋Šฅ:

  • @Mock, @InjectMocks ์ง€์›
  • when(...).thenReturn(...), thenThrow(...) ๋“ฑ Stub ์„ค์ •
  • verify(...) ๋กœ ํ˜ธ์ถœ ์—ฌ๋ถ€ / ํšŸ์ˆ˜ / ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ
  • spy๋กœ ์‹ค์ œ ๊ฐ์ฒด + ์ผ๋ถ€๋งŒ ๋ชจํ‚น๋„ ๊ฐ€๋Šฅ

5. @MockBean: Spring Test + Mockito ์—ฐ๋™

5.1 @MockBean ์ •์˜

โ€œSpring ApplicationContext์— ์žˆ๋Š” Bean์„ Mockito Mock ๊ฐ์ฒด๋กœ ๊ต์ฒดํ•ด์ฃผ๋Š” Spring Boot ํ…Œ์ŠคํŠธ์šฉ ์–ด๋…ธํ…Œ์ด์…˜โ€

์˜ˆ:

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService; // ์—ฌ๊ธฐ!

    @Test
    void getUser() throws Exception {
        when(userService.findById(1L))
                .thenReturn(new User("pong", 27));

        mockMvc.perform(get("/users/1"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("pong"));
    }
}

์ด ์ฝ”๋“œ์—์„œ:

  • ์›๋ž˜๋ผ๋ฉด Spring์ด UserService ๊ตฌํ˜„์ฒด๋ฅผ Bean์œผ๋กœ ๋“ฑ๋กํ•ด์•ผ ํ•จ
  • ํ•˜์ง€๋งŒ @WebMvcTest๋Š” Service๋ฅผ ๋กœ๋“œํ•˜์ง€ ์•Š์Œ
  • @MockBean์ด ํ•ด๋‹น ํƒ€์ž…์˜ ๋นˆ์„ Mockito Mock์œผ๋กœ ๋“ฑ๋ก
  • ์ปจํŠธ๋กค๋Ÿฌ์—๋Š” ์ง„์งœ ์„œ๋น„์Šค ๋Œ€์‹  ์ด Mock์ด ์ฃผ์ž…๋จ

5.2 @MockBean์ด ํ•„์š”ํ•œ ์ด์œ 

  • @WebMvcTest๋Š” Controller + MVC ๊ด€๋ จ Bean๋งŒ ๋กœ๋“œํ•œ๋‹ค.
  • ๊ทธ ์™ธ Service, Repository๋Š” ๋กœ๋”ฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— null์ด ๋จ.
  • ๊ทธ ์ƒํƒœ๋กœ ํ…Œ์ŠคํŠธํ•˜๋ฉด NullPointerException ๋ฐœ์ƒ.
  • ๊ทธ๋ž˜์„œ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์˜์กดํ•˜๋Š” ์„œ๋น„์Šค๋“ค์„ @MockBean์œผ๋กœ ์ฑ„์›Œ ๋„ฃ๋Š” ๊ฒƒ์ด ํ•„์ˆ˜.

5.3 @Mock vs @MockBean ์ฐจ์ด

์–ด๋…ธํ…Œ์ด์…˜์–ด๋””์— ์“ฐ๋Š”๊ฐ€์—ญํ• 
@Mock์ˆœ์ˆ˜ JUnit + Mockito ํ…Œ์ŠคํŠธ (Spring context ์—†์Œ)๋‹จ์ˆœํ•œ Mockito mock ์ƒ์„ฑ
@MockBeanSpring Boot ํ…Œ์ŠคํŠธ (Spring context ์‚ฌ์šฉ)ํ•ด๋‹น ํƒ€์ž…์˜ Spring Bean์„ Mockito mock์œผ๋กœ ๊ต์ฒด

6. Mock์ด Stub๋ณด๋‹ค โ€œ๋” ์ข‹๋‹ค?โ€์— ๋Œ€ํ•œ ์ •๋ฆฌ

  • Stub์™€ Mock์€ ์šฐ์—ด ๊ด€๊ณ„๊ฐ€ ์•„๋‹ˆ๋ผ ์—ญํ• ์ด ๋‹ค๋ฅด๋‹ค.
  • ์‹ค์ œ๋กœ๋Š” Mock์ด Stub ๊ธฐ๋Šฅ์„ ํฌํ•จํ•œ๋‹ค.

์ •๋ฆฌํ•˜๋ฉด:

  • Stub

    • ์ •ํ•ด์ง„ ๊ฐ’๋งŒ ๋ฐ˜ํ™˜
    • ํ–‰๋™ ๊ฒ€์ฆ ๋ถˆ๊ฐ€
    • ์•„์ฃผ ๋‹จ์ˆœํ•œ ํ…Œ์ŠคํŠธ์— ์ ํ•ฉ
  • Mock

    • Stub ๊ธฐ๋Šฅ ํฌํ•จ + ํ–‰๋™ ๊ฒ€์ฆ
    • ๋ณต์žกํ•œ ์ผ€์ด์Šค, ์˜ˆ์™ธ, ํ˜ธ์ถœ ํšŸ์ˆ˜ ๊ฒ€์ฆ ๋“ฑ ์‹ค๋ฌด ํ…Œ์ŠคํŠธ์— ์ ํ•ฉ
    • Spring + Mockito ํ™˜๊ฒฝ์—์„œ๋Š” Mock์ด ์‚ฌ์‹ค์ƒ ํ‘œ์ค€

ํ˜„๋Œ€ Spring ํ™˜๊ฒฝ์—์„œ๋Š” ์ง์ ‘ Stub ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ๋ณด๋‹ค, Mockito Mock์„ ์จ์„œ Stub+Mock ๊ธฐ๋Šฅ์„ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋Š” ํŒจํ„ด์ด ์••๋„์ ์œผ๋กœ ๋งŽ๋‹ค.


๐Ÿงต ์š”์•ฝ

  • ํ…Œ์ŠคํŠธ ๋”๋ธ”์€ โ€œ์‹ค์ œ ๊ฐ์ฒด ๋Œ€์‹  ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉํ•˜๋Š” ๊ฐ€์งœ ๊ฐ์ฒดโ€
  • Stub: ๊ฒฐ๊ณผ๋งŒ ๊ณ ์ •, ํ–‰๋™ ๊ฒ€์ฆ ๋ถˆ๊ฐ€
  • Mock: Stub + ํ–‰๋™ ๊ฒ€์ฆ๊นŒ์ง€ ๊ฐ€๋Šฅ
  • Mockito: Mock์„ ๋งŒ๋“ค์–ด์ฃผ๊ณ  ๊ฒ€์ฆ๊นŒ์ง€ ๋„์™€์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • @MockBean: Spring Bean์„ Mockito Mock์œผ๋กœ ๋ฐ”๊ฟ”์น˜๊ธฐํ•˜๋Š” Spring Boot ์ „์šฉ ์–ด๋…ธํ…Œ์ด์…˜
  • ์‹ค๋ฌด์—์„œ๋Š” ๋Œ€๋ถ€๋ถ„ Stub ๋Œ€์‹  Mock(@MockBean ํฌํ•จ)์„ ์‚ฌ์šฉํ•œ๋‹ค.


๐Ÿ›ฐ๏ธ 2. MockMvc / @WebMvcTest๋กœ ์ปจํŠธ๋กค๋Ÿฌ ํ…Œ์ŠคํŠธ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ธฐ


1. ์ปจํŠธ๋กค๋Ÿฌ ํ…Œ์ŠคํŠธ ์ „๋žต ์ „์ฒด ๊ทธ๋ฆผ

Spring์—์„œ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ 3๊ฐ€์ง€ ๋ ˆ๋ฒจ์ด ์žˆ๋‹ค.

  1. ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ˆ˜์ค€

    • ์ปจํŠธ๋กค๋Ÿฌ ๊ฐ์ฒด๋ฅผ new ํ•ด์„œ ์ง์ ‘ ํ˜ธ์ถœ
    • Spring MVC, ํ•„ํ„ฐ, Validation, Security ๋“ฑ์€ ์•ˆ ํƒ
  2. ์›น ๋ ˆ์ด์–ด ํ…Œ์ŠคํŠธ

    • MockMvc + @WebMvcTest
    • DispatcherServlet, ํ•„ํ„ฐ, ์ธํ„ฐ์…‰ํ„ฐ, ArgumentResolver, Validation, Security ๋“ฑ MVC ๋ ˆ์ด์–ด ์ „์ฒด ํ๋ฆ„์„ ์‹ค์ œ์ฒ˜๋Ÿผ ์‹คํ–‰
    • ์„œ๋ฒ„(WAS)๋Š” ๋„์šฐ์ง€ ์•Š์Œ
  3. ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ

    • @SpringBootTest + TestRestTemplate ๋˜๋Š” WebTestClient
    • ์‹ค์ œ ์„œ๋ฒ„ ํฌํŠธ๊นŒ์ง€ ๋„์›Œ์„œ ํ˜ธ์ถœ
    • DB ํฌํ•จ ์ „์ฒด ์Šคํƒ ํ…Œ์ŠคํŠธ

์—ฌ๊ธฐ์„œ ์šฐ๋ฆฌ๊ฐ€ ๋ณด๊ณ  ์žˆ๋Š” ๊ฑด 2๋ฒˆ: MockMvc + @WebMvcTest ์กฐํ•ฉ์ด๋‹ค.


2. MockMvc: WAS ์—†๋Š” Spring MVC ํ…Œ์ŠคํŠธ ๋„๊ตฌ

2.1 ์ •์˜

โ€œSpring MVC์˜ DispatcherServlet์„ ์‹ค์ œ๋กœ ๋™์ž‘์‹œํ‚ค๋ฉด์„œ,
HTTP ์š”์ฒญ/์‘๋‹ต ํ๋ฆ„์„ WAS ์—†์ด ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋„๊ตฌโ€

์ฆ‰:

  • ์‹ค์ œ๋กœ get("/path"), post("/path") ๊ฐ™์€ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๊ณ 
  • ํ•„ํ„ฐ, ์ธํ„ฐ์…‰ํ„ฐ, ์ปจํŠธ๋กค๋Ÿฌ, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ, ์‘๋‹ต ๋ณ€ํ™˜๊นŒ์ง€ ๋ชจ๋‘ ํ†ต๊ณผํ•œ๋‹ค.
  • ๋‹จ์ง€ Tomcat ๊ฐ™์€ ์„œ๋ฒ„๋Š” ๋„์šฐ์ง€ ์•Š๋Š”๋‹ค.

2.2 ๋Œ€ํ‘œ ์‚ฌ์šฉ ์˜ˆ์‹œ

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void ์œ ์ €_๋‹จ๊ฑด_์กฐํšŒ_API() throws Exception {
        // given
        when(userService.findById(1L))
                .thenReturn(new User("pong", 27));

        // when & then
        mockMvc.perform(get("/api/users/{id}", 1L)
                        .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("pong"))
                .andExpect(jsonPath("$.age").value(27));
    }
}

์—ฌ๊ธฐ์„œ MockMvc๋Š” ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค:

  1. GET /api/users/1 ์š”์ฒญ ์ƒ์„ฑ
  2. DispatcherServlet ์‹คํ–‰
  3. HandlerMapping โ†’ UserController ๋งคํ•‘
  4. HandlerAdapter โ†’ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ, ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ
  5. Service ํ˜ธ์ถœ (์—ฌ๊ธฐ์„  @MockBean์œผ๋กœ ๋Œ€์ฒด๋œ mockService)
  6. ๋ฐ˜ํ™˜ ๊ฐ์ฒด๋ฅผ JSON์œผ๋กœ ๋ณ€ํ™˜
  7. HTTP ์‘๋‹ต์œผ๋กœ ํฌ์žฅ
  8. status, jsonPath๋กœ ์‘๋‹ต ๊ฒ€์ฆ

3. @WebMvcTest: โ€œ์›น ๋ ˆ์ด์–ด๋งŒ ๋กœ๋“œํ•˜๋Š”โ€ ํ…Œ์ŠคํŠธ ์„ค์ •

3.1 ์ •์˜

โ€œController์™€ MVC ๊ด€๋ จ ๊ตฌ์„ฑ ์š”์†Œ๋งŒ ๋กœ๋”ฉํ•ด์„œ ์›น ๋ ˆ์ด์–ด๋ฅผ ์–‡๊ฒŒ ํ…Œ์ŠคํŠธํ•˜๋„๋ก ๋งŒ๋“  Spring Boot ํ…Œ์ŠคํŠธ์šฉ ์• ๋…ธํ…Œ์ด์…˜โ€

์˜ˆ:

@WebMvcTest(UserController.class)
class UserControllerTest {
    ...
}

3.2 ๋กœ๋“œ๋˜๋Š” Bean / ๋กœ๋“œ๋˜์ง€ ์•Š๋Š” Bean

๋กœ๋“œ๋จ:

  • @Controller, @RestController
  • @ControllerAdvice (์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋“ฑ)
  • Spring MVC ์„ค์ • (WebMvcConfigurer ๋“ฑ ์ผ๋ถ€)
  • ObjectMapper (JSON ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™”)
  • MessageConverter๋“ค
  • Spring Security ๊ด€๋ จ ํ•„ํ„ฐ/์„ค์ • (Security ์‚ฌ์šฉ ์‹œ)

๋กœ๋“œ๋˜์ง€ ์•Š์Œ:

  • @Service
  • @Repository
  • ๋Œ€๋ถ€๋ถ„์˜ @Component
  • DB ๊ด€๋ จ ์„ค์ •, DataSource, JPA, MyBatis ๋“ฑ

๊ทธ๋ž˜์„œ @WebMvcTest๋Š” ์˜ค์ง โ€œ์›น ๋ ˆ์ด์–ด ํ…Œ์ŠคํŠธโ€ ์šฉ์ด๋ผ๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.


4. @WebMvcTest + @MockBean + MockMvc ์กฐํ•ฉ ํ๋ฆ„

์ „์ฒด ๊ทธ๋ฆผ์€ ์ด๋ ‡๊ฒŒ ๋œ๋‹ค:

@WebMvcTest(UserController.class)
   โ†“
Spring์ด UserController, MVC ๊ด€๋ จ ๋นˆ๋งŒ ๋กœ๋“œ
   โ†“
@MockBean UserService
   โ†“
์‹ค์ œ UserService ๋นˆ ๋Œ€์‹  Mockito Mock ๊ฐ์ฒด๋ฅผ Spring Bean์œผ๋กœ ๋“ฑ๋ก
   โ†“
@Autowired MockMvc
   โ†“
MockMvc๋กœ HTTP ์š”์ฒญ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
   โ†“
DispatcherServlet โ†’ Filter โ†’ Interceptor โ†’ UserController โ†’ Mock(UserService)
   โ†“
์‘๋‹ต(JSON) ๊ฒ€์ฆ

์ด ๊ตฌ์กฐ ๋•๋ถ„์—:

  • ์„œ๋ฒ„๋ฅผ ๋„์šฐ์ง€ ์•Š๊ณ ๋„ (WAS ์—†์Œ)
  • ์ปจํŠธ๋กค๋Ÿฌ + ํ•„ํ„ฐ + ์‹œํ๋ฆฌํ‹ฐ + Validation๊นŒ์ง€
  • ์‹ค์ œ API์ฒ˜๋Ÿผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.

5. ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ ๋ฐฉ์‹๋“ค๊ณผ ๋น„๊ต

5.1 @WebMvcTest + MockMvc (์›น ๋ ˆ์ด์–ด ํ…Œ์ŠคํŠธ)

  • ๋ชฉ์ : ์ปจํŠธ๋กค๋Ÿฌ + ์›น ๋ ˆ์ด์–ด ๊ฒ€์ฆ

  • ์žฅ์ :

    • ๋งค์šฐ ๋น ๋ฆ„
    • MVC ํ๋ฆ„, ์‹œํ๋ฆฌํ‹ฐ, Validation, ์‘๋‹ต ํ˜•์‹๊นŒ์ง€ ํ™•์ธ ๊ฐ€๋Šฅ
  • ๋‹จ์ :

    • Service/Repository๋Š” Mock์œผ๋กœ ๋Œ€์ฒดํ•ด์•ผ ํ•จ
    • DB์— ๋Œ€ํ•œ ํ†ตํ•ฉ ๊ฒ€์ฆ์€ ๋ถˆ๊ฐ€๋Šฅ

5.2 @SpringBootTest + MockMvc (์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด + MockMvc)

@SpringBootTest
@AutoConfigureMockMvc
class UserControllerIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    // ์ง„์งœ UserService, UserRepository, DB๊นŒ์ง€ ์ „๋ถ€ ๋กœ๋“œ
}
  • ๋ชฉ์ : ์กฐ๊ธˆ ๋” ๋ฌด๊ฑฐ์šด ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ

  • ์žฅ์ :

    • ์ „์ฒด Bean ๋กœ๋“œ โ†’ ์‹ค์ œ Service, Repository, DB๊นŒ์ง€ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • ๋‹จ์ :

    • ๋А๋ฆฌ๋‹ค
    • ํ…Œ์ŠคํŠธ ์ปจํ…์ŠคํŠธ๊ฐ€ ๋ฌด๊ฑฐ์›€

5.3 @SpringBootTest + TestRestTemplate / WebTestClient (์™„์ „ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ)

  • ์‹ค์ œ ์„œ๋ฒ„ ํฌํŠธ์— HTTP๋กœ ์š”์ฒญ
  • ์ธํ”„๋ผ ํ™˜๊ฒฝ๊นŒ์ง€ ์ตœ๋Œ€ํ•œ ์‹ค์ œ์™€ ๊ฐ€๊น๊ฒŒ ๊ฒ€์ฆ
  • ๊ฐ€์žฅ ๋А๋ฆฌ๊ณ , ์…‹์—…์ด ๋ฌด๊ฒ์ง€๋งŒ, ์‹ ๋ขฐ๋„๋Š” ๊ฐ€์žฅ ๋†’์Œ

6. ์–ธ์ œ ์–ด๋–ค ๋ฐฉ์‹์„ ์„ ํƒํ•ด์•ผ ํ•˜๋Š”๊ฐ€

์ƒํ™ฉ์ถ”์ฒœ ๋ฐฉ์‹
์ปจํŠธ๋กค๋Ÿฌ์˜ Request/Response, Validation, Security, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋งŒ ๊ฒ€์ฆํ•˜๊ณ  ์‹ถ๋‹ค@WebMvcTest + MockMvc + @MockBean
์„œ๋น„์Šค ๋กœ์ง + DB๊นŒ์ง€ ์‹ค์ œ๋กœ ๋™์ž‘์‹œํ‚ค๋ฉฐ API๋ฅผ ๊ฒ€์ฆํ•˜๊ณ  ์‹ถ๋‹ค@SpringBootTest + MockMvc
์‹ค์ œ ์šด์˜๊ณผ ๊ฑฐ์˜ ๋™์ผํ•œ ํ™˜๊ฒฝ์—์„œ E2E ๋ ˆ๋ฒจ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๊ณ  ์‹ถ๋‹ค@SpringBootTest + TestRestTemplate/WebTestClient

ํ˜„์‹ค์ ์ธ ํŒจํ„ด:

  • ์ปจํŠธ๋กค๋Ÿฌ ํ…Œ์ŠคํŠธ: @WebMvcTest + MockMvc + @MockBean
  • ์„œ๋น„์Šค/๋ฆฌํฌ์ง€ํ† ๋ฆฌ + DB ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ: @SpringBootTest (๋˜๋Š” slice ํ…Œ์ŠคํŠธ)
  • ์ตœ์ข…์ ์œผ๋กœ ๋ช‡ ๊ฐœ์˜ ํ•ต์‹ฌ ์‹œ๋‚˜๋ฆฌ์˜ค๋งŒ E2E ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€

7. ๊ฐ„๋‹จํ•œ ์‹ค์ „ ํ…œํ”Œ๋ฆฟ

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    MockMvc mockMvc;

    @MockBean
    UserService userService;

    @Test
    void getUser_์ •์ƒ_์‘๋‹ต() throws Exception {
        // given
        when(userService.findById(1L))
                .thenReturn(new User("pong", 27));

        // when & then
        mockMvc.perform(get("/api/users/{id}", 1L)
                        .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("pong"))
                .andExpect(jsonPath("$.age").value(27));

        verify(userService, times(1)).findById(1L);
    }
}

์ด ํ•œ ํŒŒ์ผ์— ์ง€๊ธˆ๊นŒ์ง€ ์ •๋ฆฌํ•œ ๊ฐœ๋…์ด ์ „๋ถ€ ๋“ค์–ด ์žˆ๋‹ค:

  • @WebMvcTest โ†’ ์›น ๋ ˆ์ด์–ด๋งŒ ๋กœ๋“œ
  • MockMvc โ†’ HTTP ์š”์ฒญ/์‘๋‹ต ํ…Œ์ŠคํŠธ
  • @MockBean โ†’ Service๋ฅผ Mockito Mock์œผ๋กœ ๊ต์ฒด
  • when(...).thenReturn(...) โ†’ Stub ๊ธฐ๋Šฅ
  • verify(...) โ†’ Mock ํ–‰๋™ ๊ฒ€์ฆ

๐Ÿงต ์š”์•ฝ

  • MockMvc: DispatcherServlet์„ ์‹ค์ œ๋กœ ๋Œ๋ฆฌ๋ฉด์„œ, WAS ์—†์ด MVC ํ๋ฆ„์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋„๊ตฌ
  • @WebMvcTest: Controller + MVC ๊ด€๋ จ Bean๋งŒ ๋กœ๋“œํ•˜๋Š” โ€œ์›น ๋ ˆ์ด์–ด ์Šฌ๋ผ์ด์Šค ํ…Œ์ŠคํŠธโ€
  • @MockBean: Controller๊ฐ€ ์˜์กดํ•˜๋Š” Service/Repository๋ฅผ Mock์œผ๋กœ ๊ต์ฒด
  • @WebMvcTest + MockMvc + @MockBean ์กฐํ•ฉ์œผ๋กœ
    โ€œ๊ฐ€๋ณ๊ณ  ๋น ๋ฅด๊ฒŒ, ํ•˜์ง€๋งŒ ์‹ค์ œ MVC ํ๋ฆ„์— ๊ฐ€๊น๊ฒŒโ€ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€