๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๋ฐฑ์—”๋“œ/spring&springboot

[๋‚ด์ผ๋ฐฐ์›€์บ ํ”„_Spring Boot ์‹ฌํ™”์ฃผ์ฐจ] ํ…Œ์ŠคํŠธ(๋‹จ์œ„ ํ…Œ์ŠคํŠธ, Mockito, ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ)

by alswlfl 2026. 7. 1.

๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ž€ ๋ฌด์—‡์ผ๊นŒ?

  • ๋ฒ„๊ทธ ๋ฐœ๊ฒฌ ์‹œ๊ฐ„์ด ๋Šฆ์–ด์ง์— ๋”ฐ๋ผ ๋น„์šฉ์ด ๊ธฐํ•˜๊ธ‰์ˆ˜์ ์œผ๋กœ ์ปค์ง

  1. Development: ๊ฐœ๋ฐœ
  2. Unit Tests(๋‹จ์œ„ ํ…Œ์ŠคํŠธ): ๊ฐœ๋ฐœ์ž ํ…Œ์ŠคํŠธ
  3. QA Testing(๋ธ”๋ž™๋ฐ•์Šค ํ…Œ์ŠคํŒ…): ์ฃผ๋กœ QA ํŒ€์ด Production ํ™˜๊ฒฝ๊ณผ ์œ ์‚ฌํ•œ ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŒ…
  4. Production: ์‹ค ์„œ๋น„์Šค ์šด์˜ ํ™˜๊ฒฝ
๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ์ž‘์€ ๋‹จ์œ„๋กœ ์ชผ๊ฐœ์„œ ๊ฐ ๋‹จ์œ„๊ฐ€ ์ •ํ™•ํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€๋ฅผ ๊ฒ€์‚ฌํ•˜๋Š” ํ…Œ์ŠคํŠธ ๊ธฐ๋ฒ•
- ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ๋น ๋ฅด๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๊ณ  ๋ฌธ์ œ ๋ฐœ์ƒ ์‹œ ์–ด๋А ๋ถ€๋ถ„์ด ์ž˜๋ชป ๋˜์—ˆ๋Š”์ง€๋ฅผ ๋น ๋ฅด๊ณ  ์ •ํ™•ํ•˜๊ฒŒ ํ™•์ธ ๊ฐ€๋Šฅ

JUnit5 ๊ธฐ๋Šฅ

BeforeAfter ๊ธฐ๋Šฅ

  • @BeforeEach๋Š” ๊ฐ๊ฐ์˜ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ์ˆ˜ํ–‰๋˜๋Š” ๋ฉ”์„œ๋“œ
  • @AfterEach๋Š” ๊ฐ๊ฐ์˜ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋œ ํ›„์— ์ˆ˜ํ–‰๋˜๋Š” ๋ฉ”์„œ๋“œ
  • @BeforeAll์€ ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ์ˆ˜ํ–‰๋˜๋Š” ๋ฉ”์„œ๋“œ
  • @AfterAll์€ ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์ˆ˜ํ–‰๋œ ํ›„ ๋งˆ์ง€๋ง‰์œผ๋กœ ์ˆ˜ํ–‰๋˜๋Š” ๋ฉ”์„œ๋“œ
package com.sparta.junit5practice;

import org.junit.jupiter.api.*;

public class BeforeAfterTest {
    @BeforeEach
    void setUp() {
        System.out.println("๊ฐ๊ฐ์˜ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ์ˆ˜ํ–‰");
    }

    @AfterEach
    void tearDown() {
        System.out.println("๊ฐ๊ฐ์˜ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋œ ํ›„์— ์ˆ˜ํ–‰\n");
    }

    @BeforeAll
    static void beforeAll() {
        System.out.println("๋ชจ๋“  ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ์ตœ์ดˆ๋กœ ์ˆ˜ํ–‰\n");
    }

    @AfterAll
    static void afterAll() {
        System.out.println("๋ชจ๋“  ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์ˆ˜ํ–‰๋œ ํ›„ ๋งˆ์ง€๋ง‰์œผ๋กœ ์ˆ˜ํ–‰\n");
    }
    
    @Test
    void test1() {
        System.out.println("ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ 1");
    }

    @Test
    void test2() {
        System.out.println("ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ 2");
    }
}

ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ

Nested ๊ธฐ๋Šฅ

  • @Nested๋Š” ์ฃผ์ œ ๋ณ„๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ๊ทธ๋ฃน์ง€์–ด์„œ ํŒŒ์•…ํ•˜๊ธฐ ์ข‹๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ
@Test
@DisplayName("ํ…Œ์ŠคํŠธ์˜ ๋‚ด์šฉ์„ ํ•œ๋ˆˆ์— ์•Œ์•„๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ๋„ค์ด๋ฐ")
void test1() {
    System.out.println("ํ…Œ์ŠคํŠธ ๋‚ด์šฉ ๋น ๋ฅด๊ฒŒ ํŒŒ์•…");
}
    
@Nested
@DisplayName("์ฃผ์ œ ๋ณ„๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ๊ทธ๋ฃน์ง€์–ด์„œ ํŒŒ์•…ํ•˜๊ธฐ ์ข‹์Šต๋‹ˆ๋‹ค.")
class Test1 {
    @Test
    @DisplayName("Test1 - test1()")
    void test1() {
        System.out.println("Test1.test1");
    }

    @Test
    @DisplayName("Test1 - test2()")
    void test2() {
        System.out.println("Test1.test2");
    }
}

@Nested
@DisplayName("Test2 ๋‹ค๋ฅธ ์ฃผ์ œ")
class Test2 {
    @Test
    @DisplayName("Test2 - test1()")
    void test1() {
        System.out.println("Test2.test1");
    }

    @Test
    @DisplayName("Test2 - test2()")
    void test2() {
        System.out.println("Test2.test2");
    }
}

ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ

Order ๊ธฐ๋Šฅ

@Order๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ๋ฉ”์„œ๋“œ ๋‹จ์œ„๋กœ ์ˆœ์„œ๋ฅผ ๋งค๊ธธ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์œผ๋กœ, ํ•ด๋‹นํ•˜๋Š” ํด๋ž˜์Šค ์œ„์— @TestMethodOrder ๋‹ฌ์•„์ค˜์•ผํ•จ

@Nested
@DisplayName("์ฃผ์ œ ๋ณ„๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ๊ทธ๋ฃน์ง€์–ด์„œ ํŒŒ์•…ํ•˜๊ธฐ ์ข‹์Šต๋‹ˆ๋‹ค.")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class Test1 {

    @Order(1)
    @Test
    @DisplayName("Test1 ํด๋ž˜์Šค")
    void test() {
        System.out.println("\nTest1 ํด๋ž˜์Šค");
    }

    @Order(3)
    @Test
    @DisplayName("Test1 - test1()")
    void test1() {
        System.out.println("Test1.test1");
    }

    @Order(2)
    @Test
    @DisplayName("Test1 - test2()")
    void test2() {
        System.out.println("Test1.test2");
    }
}

ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ

RepeatedTest ๊ธฐ๋Šฅ

@RepeatedTest๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ํ…Œ์ŠคํŠธ ๋Œ๋ฆด ์ˆ˜ ์žˆ์Œ

// value: ํ…Œ์ŠคํŠธ ๋ฐ˜๋ณตํ•  ํšŸ์ˆ˜
// totalRepetitions = value
// currentRepetition = ํ˜„์žฌ ์ˆ˜ํ–‰ํ•œ ํšŸ์ˆ˜
@RepeatedTest(value = 5, name = "๋ฐ˜๋ณต ํ…Œ์ŠคํŠธ {currentRepetition} / {totalRepetitions}")
void repeatTest(RepetitionInfo info) {
    System.out.println("ํ…Œ์ŠคํŠธ ๋ฐ˜๋ณต : " + info.getCurrentRepetition() + " / " + info.getTotalRepetitions());
}

ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ

ParameterizedTest ๊ธฐ๋Šฅ

@ParameterizedTest๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์„ ํ™œ์šฉํ•ด์„œ ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ, @ValueSource ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•ด์„œ ์ „๋‹ฌํ•ด์ฃผ๊ณ  ์‹ถ์€ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์„ ๋„ฃ์–ด์คŒ

@DisplayName("ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’ ํ™œ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ํ•˜๊ธฐ")
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5, 6, 7, 8, 9})
void parameterTest(int num) {
    System.out.println("5 * num = " + 5 * num);
}

ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ


โญ๏ธ Assertions ํŒจํ‚ค์ง€ ๊ธฐ๋Šฅ

  • Assertions ํŒจํ‚ค์ง€๋Š” ๊ฒ€์ฆํ•  ๋•Œ ๋งŽ์ด ์‚ฌ์šฉํ•จ

[Calculator ํด๋ž˜์Šค ์ƒ์„ฑ]

public class Calculator {
    public Double operate(double num1, String op, double num2) {
        switch (op) {
            case "*":
                return num1 * num2;
            case "/":
                if (validateNum(num2)) {
                    return num1 / num2;
                } else {
                    return null;
                }
            case "+":
                return num1 + num2;
            case "-":
                return num1 - num2;
            default:
                throw new IllegalArgumentException("์ž˜๋ชป๋œ ์—ฐ์‚ฐ์ž์ž…๋‹ˆ๋‹ค.");
        }
    }

    public boolean validateNum(double num) {
        if (num == 0) {
            return false;
        } else {
            return true;
        }
    }
}

 

assertEquals ๋ฉ”์„œ๋“œ

Assertions.assertEquals(expected, actual) ๋ฉ”์„œ๋“œ๋Š” expected์™€ actual์ด ๋™์ผํ•œ์ง€ ํ™•์ธ

  • expected: ์˜ˆ์ƒ ๊ฐ’
  • actual: ๊ฒฐ๊ณผ ๊ฐ’
@Test
@DisplayName("assertEquals")
void test1() {
    Double result = calculator.operate(5, "/", 2);
    assertEquals(2.5, result);
}

@Test
@DisplayName("assertEquals - Supplier")
void test1_1() {
    Double result = calculator.operate(5, "/", 0);
    // ํ…Œ์ŠคํŠธ ์‹คํŒจ ์‹œ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ (new Supplier<String>())
    assertEquals(2.5, result, () -> "์—ฐ์‚ฐ์ž ํ˜น์€ ๋ถ„๋ชจ๊ฐ€ 0์ด ์•„๋‹Œ์ง€ ํ™•์ธํ•ด๋ณด์„ธ์š”!");
}

@Test
@DisplayName("assertNotEquals")
void test1_2() {
    Double result = calculator.operate(5, "/", 0);
    assertNotEquals(2.5, result);
}

 

assertTrue ๋ฉ”์„œ๋“œ

@Assertions.assertTrue(boolean) ๋ฉ”์„œ๋“œ๋Š” ๊ฒ€์ฆํ•˜๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ์‹ค์ œ๋กœ ์ž˜ ์ˆ˜ํ–‰๋˜๋Š”์ง€ ํ™•์ธ

@Test
@DisplayName("assertTrue ์™€ assertFalse")
void test2() {
    assertTrue(calculator.validateNum(9));
    assertFalse(calculator.validateNum(0));
}

 

assertNotNull ๋ฉ”์„œ๋“œ

@Assertions.assertNotNull(actual) ๋ฉ”์„œ๋“œ๋Š” null์ธ์ง€ ํ™•์ธ

@Test
@DisplayName("assertNotNull ๊ณผ assertNull")
void test3() {
    Double result1 = calculator.operate(5, "/", 2);
    assertNotNull(result1);
    Double result2 = calculator.operate(5, "/", 0);
    assertNull(result2);
}

 

assertThrows ๋ฉ”์„œ๋“œ

@Assertions.assertThrows(expectedType, executable) ๋ฉ”์„œ๋“œ๋Š” ์˜ˆ์™ธ(Exception) ํ™•์ธ

  • expectedType: ์˜ˆ์ƒํ•˜๋Š” ํƒ€์ž…
  • executable: ์ˆ˜ํ–‰ ๋ฉ”์„œ๋“œ
@Test
@DisplayName("assertThrows")
void test4() {
    IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> calculator.operate(5, "?", 2));
    assertEquals("์ž˜๋ชป๋œ ์—ฐ์‚ฐ์ž์ž…๋‹ˆ๋‹ค.", exception.getMessage());
}

โญ๏ธ Given-When-Then ํŒจํ„ด

Given-When-Then ํŒจํ„ด์€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์Šคํƒ€์ผ์„ ํ‘œํ˜„ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ,

  • Given: ํ…Œ์ŠคํŠธ ํ•˜๊ณ ์ž ํ•˜๋Š” ๋Œ€์ƒ์„ ์‹ค์ œ๋กœ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ๊ทธ ํ…Œ์ŠคํŠธ์— ํ•„์š”ํ•œ ๊ฐ’๋“ค์„ ๋ฏธ๋ฆฌ ์„ ์–ธ
  • When: ํ…Œ์ŠคํŠธ ํ•˜๊ณ ์ž ํ•˜๋Š” ๋Œ€์ƒ์„ ์‹ค์ œ๋กœ ์‹คํ–‰
  • Then: ์–ด๋–ค ํŠน์ •ํ•œ ํ–‰๋™ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•  ๊ฑฐ๋ผ๊ณ  ์˜ˆ์ƒ๋˜๋Š” ๊ฒฐ๊ณผ์— ๋Œ€ํ•ด ์˜ˆ์ธกํ•˜๊ณ  ๋งž๋Š”์ง€ ํ™•์ธ(Assertions ํŒจํ‚ค์ง€ ์ด์šฉ)
class CalculatorTest {

    Calculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }

    @Test
    @DisplayName("๊ณ„์‚ฐ๊ธฐ ์—ฐ์‚ฐ ์„ฑ๊ณต ํ…Œ์ŠคํŠธ")
    void test1() {
        // given
        int num1 = 5;
        String op = "/";
        int num2 = 2;

        // when
        Double result = calculator.operate(num1, op, num2);

        // then
        assertNotNull(result);
        assertEquals(2.5, result);
    }

    @Test
    @DisplayName("๊ณ„์‚ฐ๊ธฐ ์—ฐ์‚ฐ ์‹คํŒจ ํ…Œ์ŠคํŠธ : ๋ถ„๋ชจ๊ฐ€ 0์ผ ๊ฒฝ์šฐ")
    void test1_1() {
        // given
        int num1 = 5;
        String op = "/";
        int num2 = 0;

        // when
        Double result = calculator.operate(num1, op, num2);

        // then
        assertNull(result);
    }

    @Test
    @DisplayName("๊ณ„์‚ฐ๊ธฐ ์—ฐ์‚ฐ ์‹คํŒจ ํ…Œ์ŠคํŠธ : ์—ฐ์‚ฐ์ž๊ฐ€ ์ž˜๋ชป๋์„ ๊ฒฝ์šฐ")
    void test1_2() {
        // given
        int num1 = 5;
        String op = "?";
        int num2 = 2;

        // when - then
        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> calculator.operate(5, "?", 2));
        assertEquals("์ž˜๋ชป๋œ ์—ฐ์‚ฐ์ž์ž…๋‹ˆ๋‹ค.", exception.getMessage());
    }
}

Mockito๋ž€ ๋ฌด์—‡์ผ๊นŒ?

  • ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•  ๋•Œ ์•„๋ž˜์™€ ๊ฐ™์ด ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์ฃผ์ž…๋ฐ›๋Š” ๊ฒƒ(3๊ฐœ์˜ Repository)๋“ค์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋‚˜ updateProduct ๋ฉ”์„œ๋“œ์ฒ˜๋Ÿผ ๋‚ด๋ถ€์—์„œ findById์™€ ๊ฐ™์€ ๊ฒƒ๋“ค์ด ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ, ์ด๋Ÿฌํ•œ ๋ฌธ์ œ์ ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Mock์„ ์ œ๊ณตํ•จ
    • Service์™€ Repository๋ฅผ ๋”ฐ๋กœ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•ด
class ProductServiceTest {
    @Test
    @DisplayName("๊ด€์‹ฌ ์ƒํ’ˆ ํฌ๋ง๊ฐ€ - ์ตœ์ €๊ฐ€ ์ด์ƒ์œผ๋กœ ๋ณ€๊ฒฝ")
    void test1() {
        // given
        Long productId = 100L;
        int myprice = ProductService.MIN_MY_PRICE + 3_000_000;

        ProductMypriceRequestDto requestMyPriceDto = new ProductMypriceRequestDto();
        requestMyPriceDto.setMyprice(myprice);

        ProductService productService = new ProductService();

        // when
        ProductResponseDto result = productService.updateProduct(productId, requestMyPriceDto);

        // then
        assertEquals(myprice, result.getMyprice());
    }

    @Test
    @DisplayName("๊ด€์‹ฌ ์ƒํ’ˆ ํฌ๋ง๊ฐ€ - ์ตœ์ €๊ฐ€ ๋ฏธ๋งŒ์œผ๋กœ ๋ณ€๊ฒฝ")
    void test2() {
        // given
        Long productId = 200L;
        int myprice = ProductService.MIN_MY_PRICE - 50;

        ProductMypriceRequestDto requestMyPriceDto = new ProductMypriceRequestDto();
        requestMyPriceDto.setMyprice(myprice);

        ProductService productService = new ProductService();

        // when
        Exception exception = assertThrows(IllegalArgumentException.class, () -> {
            productService.updateProduct(productId, requestMyPriceDto);
        });

        // then
        assertEquals(
                "์œ ํšจํ•˜์ง€ ์•Š์€ ๊ด€์‹ฌ ๊ฐ€๊ฒฉ์ž…๋‹ˆ๋‹ค. ์ตœ์†Œ " +ProductService.MIN_MY_PRICE + " ์› ์ด์ƒ์œผ๋กœ ์„ค์ •ํ•ด ์ฃผ์„ธ์š”.",
                exception.getMessage()
        );
    }
}

Mockito ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

Mockito ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” Mock Object(๊ฐ€์งœ ๊ฐ์ฒด)๋ฅผ ์ƒ์„ฑํ•ด์„œ ์ œ๊ณต

  • Mockito๋Š” ๋”ฐ๋กœ dependencies์—์„œ ๊ฐ€์ ธ์˜ฌ ํ•„์š” ์—†์Œ
package com.sparta.myselectshop.service;

import com.sparta.myselectshop.dto.ProductMypriceRequestDto;
import com.sparta.myselectshop.dto.ProductRequestDto;
import com.sparta.myselectshop.dto.ProductResponseDto;
import com.sparta.myselectshop.entity.Product;
import com.sparta.myselectshop.entity.User;
import com.sparta.myselectshop.repository.FolderRepository;
import com.sparta.myselectshop.repository.ProductFolderRepository;
import com.sparta.myselectshop.repository.ProductRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.BDDMockito.given;

@ExtendWith(MockitoExtension.class) // @Mock ์‚ฌ์šฉ์„ ์œ„ํ•ด ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
class ProductServiceTest {

    @Mock
    ProductRepository productRepository;

    @Mock
    FolderRepository folderRepository;

    @Mock
    ProductFolderRepository productFolderRepository;

    @Test
    @DisplayName("๊ด€์‹ฌ ์ƒํ’ˆ ํฌ๋ง๊ฐ€ - ์ตœ์ €๊ฐ€ ์ด์ƒ์œผ๋กœ ๋ณ€๊ฒฝ")
    void test1() {
        // given
        Long productId = 100L;
        int myprice = ProductService.MIN_MY_PRICE + 3_000_000;

        ProductMypriceRequestDto requestMyPriceDto = new ProductMypriceRequestDto();
        requestMyPriceDto.setMyprice(myprice);

        User user = new User();
        ProductRequestDto requestProductDto = new ProductRequestDto(
                "Apple <b>๋งฅ๋ถ</b> <b>ํ”„๋กœ</b> 16ํ˜• 2021๋…„ <b>M1</b> Max 10์ฝ”์–ด ์‹ค๋ฒ„ (MK1H3KH/A) ",
                "https://shopping-phinf.pstatic.net/main_2941337/29413376619.20220705152340.jpg",
                "https://search.shopping.naver.com/gate.nhn?id=29413376619",
                3515000
        );

        Product product = new Product(requestProductDto, user);

        ProductService productService = new ProductService(productRepository, folderRepository, productFolderRepository);

        given(productRepository.findById(productId)).willReturn(Optional.of(product));

        // when
        ProductResponseDto result = productService.updateProduct(productId, requestMyPriceDto);

        // then
        assertEquals(myprice, result.getMyprice());
    }

    @Test
    @DisplayName("๊ด€์‹ฌ ์ƒํ’ˆ ํฌ๋ง๊ฐ€ - ์ตœ์ €๊ฐ€ ๋ฏธ๋งŒ์œผ๋กœ ๋ณ€๊ฒฝ")
    void test2() {
        // given
        Long productId = 200L;
        int myprice = ProductService.MIN_MY_PRICE - 50;

        ProductMypriceRequestDto requestMyPriceDto = new ProductMypriceRequestDto();
        requestMyPriceDto.setMyprice(myprice);

        ProductService productService = new ProductService(productRepository, folderRepository, productFolderRepository);

        // when
        Exception exception = assertThrows(IllegalArgumentException.class, () -> {
            productService.updateProduct(productId, requestMyPriceDto);
        });

        // then
        assertEquals(
                "์œ ํšจํ•˜์ง€ ์•Š์€ ๊ด€์‹ฌ ๊ฐ€๊ฒฉ์ž…๋‹ˆ๋‹ค. ์ตœ์†Œ " +ProductService.MIN_MY_PRICE + " ์› ์ด์ƒ์œผ๋กœ ์„ค์ •ํ•ด ์ฃผ์„ธ์š”.",
                exception.getMessage()
        );
    }
}
  • ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค ์ƒ๋‹จ์— @ExtendWith(MockitoExtension.class) ์ถ”๊ฐ€
  • ๊ฐ€์งœ ๊ฐ์ฒด๋กœ ๋งŒ๋“ค๊ณ  ์‹ถ์€ ํด๋ž˜์Šค๋‚˜ ์ธํ„ฐํŽ˜์ด์Šค ์„ ์–ธ ํ›„ @Mock ์–ด๋…ธํ…Œ์ด์…˜ ๋‹ฌ์•„์คŒ
  • Service์™€ Repository๋ฅผ ๋ถ„๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด Mock ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋„ฃ์–ด์คฌ์œผ๋ฉด, ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„๊นŒ์ง€๋„ ์šฐ๋ฆฌ๊ฐ€ ๋„ฃ์–ด์ค˜์•ผํ•จ
  • Mock ๊ฐ์ฒด๋Š” ๊ฐ€์งœ ๊ฐ์ฒด๋งŒ ๋งŒ๋“ค์–ด์ฃผ๊ณ , ์‹ค์ œ๋กœ ์ˆ˜ํ–‰ํ•œ ๊ฒฐ๊ณผ๋ฌผ์€ ์ง์ ‘ ๋„ฃ์–ด์ค˜์•ผํ•จgiven ๋ฉ”์„œ๋“œ ์ด์šฉํ•ด์„œ ๋„ฃ์–ด์คŒ

 


ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ž€ ๋ฌด์—‡์ผ๊นŒ?

๋‹จ์œ„ ํ…Œ์ŠคํŠธ(Unit Test)

  • ํ•˜๋‚˜์˜ ๋ชจ๋“ˆ์ด๋‚˜ ํด๋ž˜์Šค์— ๋Œ€ํ•ด ์„ธ๋ฐ€ํ•œ ๋ถ€๋ถ„๊นŒ์ง€ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
  • ํ•˜์ง€๋งŒ, ๋ชจ๋“ˆ ๊ฐ„์— ์ƒํ˜ธ ์ž‘์šฉ ๊ฒ€์ฆ ์ˆ˜ํ–‰ X
  • ex) ์ด์ „์— ProductService์˜ updateProduct ๋ฉ”์„œ๋“œ๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ repository์ชฝ์€ ์ปจํŠธ๋กคํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์—,
    Mock Object๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋„ฃ์–ด์ฃผ๊ณ , given ๋ฉ”์„œ๋“œ ์‚ฌ์šฉํ•ด์„œ repository๊ฐ€ ์ˆ˜ํ–‰๋˜๋Š” findById ๋ถ€๋ถ„๋„ ์ง์ ‘ ๋งŒ๋“ค์–ด์ค€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ฒ˜๋ฆฌํ–ˆ์Œ

ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ(Integration Test)

  • ๋‘ ๊ฐœ ์ด์ƒ์˜ ๋ชจ๋“ˆ์ด ์—ฐ๊ฒฐ๋œ ์ƒํƒœ๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Œ
  • ๋ชจ๋“ˆ ๊ฐ„์˜ ์—ฐ๊ฒฐ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ ๊ฒ€์ฆ ๊ฐ€๋Šฅ

Spring Boot๋ฅผ ์ด์šฉํ•œ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ

  • ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์‹œ Spring์€ ๋™์ž‘๋˜์ง€ ์•Š์Œ
  • ์—ฌ๋Ÿฌ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋‚˜์˜ ํ†ตํ•ฉ๋œ ํ…Œ์ŠคํŠธ๋กœ ์ˆ˜ํ–‰
  • @SpringBootTest ์–ด๋…ธํ…Œ์ด์…˜์€ ์Šคํ”„๋ง์ด ๋™์ž‘๋˜๋„๋ก ํ•ด์ฃผ๋Š” ๊ฒƒ์œผ๋กœ, ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰ ์‹œ ์Šคํ”„๋ง์ด ๋™์ž‘ํ•จ
    • Spring IoC/DI ๊ธฐ๋Šฅ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
    • Repository๋ฅผ ์‚ฌ์šฉํ•ด DB CRUD ๊ฐ€๋Šฅ

๊ด€์‹ฌ์ƒํ’ˆ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์„ค๊ณ„

  • ์‹ ๊ทœ ๊ด€์‹ฌ์ƒํ’ˆ ๋“ฑ๋ก
  • ์‹ ๊ทœ ๋“ฑ๋ก๋œ ๊ด€์‹ฌ์ƒํ’ˆ์˜ ํฌ๋ง ์ตœ์ €๊ฐ€ ๋ณ€๊ฒฝ
  • ํšŒ์› Id๋กœ ๋“ฑ๋ก๋œ ๋ชจ๋“  ๊ด€์‹ฌ์ƒํ’ˆ ์กฐํšŒ
package com.sparta.myselectshop.service;

import com.sparta.myselectshop.dto.ProductMypriceRequestDto;
import com.sparta.myselectshop.dto.ProductRequestDto;
import com.sparta.myselectshop.dto.ProductResponseDto;
import com.sparta.myselectshop.entity.User;
import com.sparta.myselectshop.repository.UserRepository;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // ์„œ๋ฒ„์˜ PORT ๋ฅผ ๋žœ๋ค์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
@TestInstance(TestInstance.Lifecycle.PER_CLASS) // ํ…Œ์ŠคํŠธ ์ธ์Šคํ„ด์Šค์˜ ์ƒ์„ฑ ๋‹จ์œ„๋ฅผ ํด๋ž˜์Šค๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class ProductServiceIntegrationTest {

    @Autowired
    ProductService productService;
    @Autowired
    UserRepository userRepository;

    User user;
    ProductResponseDto createdProduct = null;
    int updatedMyPrice = -1;

    @Test
    @Order(1)
    @DisplayName("์‹ ๊ทœ ๊ด€์‹ฌ์ƒํ’ˆ ๋“ฑ๋ก")
    void test1() {
        // given
        String title = "Apple <b>์—์–ดํŒŸ</b> 2์„ธ๋Œ€ ์œ ์„ ์ถฉ์ „ ๋ชจ๋ธ (MV7N2KH/A)";
        String imageUrl = "https://shopping-phinf.pstatic.net/main_1862208/18622086330.20200831140839.jpg";
        String linkUrl = "https://search.shopping.naver.com/gate.nhn?id=18622086330";
        int lPrice = 173900;
        ProductRequestDto requestDto = new ProductRequestDto(
                title,
                imageUrl,
                linkUrl,
                lPrice
        );
        user = userRepository.findById(1L).orElse(null);

        // when
        ProductResponseDto product = productService.createProduct(requestDto, user);

        // then
        assertNotNull(product.getId());
        assertEquals(title, product.getTitle());
        assertEquals(imageUrl, product.getImage());
        assertEquals(linkUrl, product.getLink());
        assertEquals(lPrice, product.getLprice());
        assertEquals(0, product.getMyprice());
        createdProduct = product;
    }

    @Test
    @Order(2)
    @DisplayName("์‹ ๊ทœ ๋“ฑ๋ก๋œ ๊ด€์‹ฌ์ƒํ’ˆ์˜ ํฌ๋ง ์ตœ์ €๊ฐ€ ๋ณ€๊ฒฝ")
    void test2() {
        // given
        Long productId = this.createdProduct.getId();
        int myPrice = 173000;
        ProductMypriceRequestDto requestDto = new ProductMypriceRequestDto();
        requestDto.setMyprice(myPrice);

        // when
        ProductResponseDto product = productService.updateProduct(productId, requestDto);

        // then
        assertNotNull(product.getId());
        assertEquals(this.createdProduct.getTitle(), product.getTitle());
        assertEquals(this.createdProduct.getImage(), product.getImage());
        assertEquals(this.createdProduct.getLink(), product.getLink());
        assertEquals(this.createdProduct.getLprice(), product.getLprice());
        assertEquals(myPrice, product.getMyprice());
        this.updatedMyPrice = myPrice;
    }

    @Test
    @Order(3)
    @DisplayName("ํšŒ์›์ด ๋“ฑ๋กํ•œ ๋ชจ๋“  ๊ด€์‹ฌ์ƒํ’ˆ ์กฐํšŒ")
    void test3() {
        // given
        // when
        Page<ProductResponseDto> productList = productService.getProducts(user,
                0, 10, "id", false);

        // then
        // 1. ์ „์ฒด ์ƒํ’ˆ์—์„œ ํ…Œ์ŠคํŠธ์— ์˜ํ•ด ์ƒ์„ฑ๋œ ์ƒํ’ˆ ์ฐพ์•„์˜ค๊ธฐ (์ƒํ’ˆ์˜ id ๋กœ ์ฐพ์Œ)
        Long createdProductId = this.createdProduct.getId();
        ProductResponseDto foundProduct = productList.stream()
                .filter(product -> product.getId().equals(createdProductId))
                .findFirst()
                .orElse(null);

        // 2. Order(1) ํ…Œ์ŠคํŠธ์— ์˜ํ•ด ์ƒ์„ฑ๋œ ์ƒํ’ˆ๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆ
        assertNotNull(foundProduct);
        assertEquals(this.createdProduct.getId(), foundProduct.getId());
        assertEquals(this.createdProduct.getTitle(), foundProduct.getTitle());
        assertEquals(this.createdProduct.getImage(), foundProduct.getImage());
        assertEquals(this.createdProduct.getLink(), foundProduct.getLink());
        assertEquals(this.createdProduct.getLprice(), foundProduct.getLprice());

        // 3. Order(2) ํ…Œ์ŠคํŠธ์— ์˜ํ•ด myPrice ๊ฐ€๊ฒฉ์ด ์ •์ƒ์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜์—ˆ๋Š”์ง€ ๊ฒ€์ฆ
        assertEquals(this.updatedMyPrice, foundProduct.getMyprice());
    }
}

ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ


Controller ํ…Œ์ŠคํŠธ

controller๋Š” Security ๋“ฑ Filter๊ฐ€ ์ ์šฉ๋˜์–ด ์žˆ์–ด์„œ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ๊นŒ๋‹ค๋กœ์›€

  • Filter๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ตฌํ˜„์ฒด ์ƒ์„ฑ
    - ์ฆ‰, ๊ฐ€์งœ ์ธ์ฆ์„ ํ•˜๋Š” ํ–‰์œ„(๊ฐ€์งœ Filter)
package com.sparta.myselectshop.mvc;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

import java.io.IOException;

public class MockSpringSecurityFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) {}

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        SecurityContextHolder.getContext()
                .setAuthentication((Authentication) ((HttpServletRequest) req).getUserPrincipal());
        chain.doFilter(req, res);
    }

    @Override
    public void destroy() {
        SecurityContextHolder.clearContext();
    }
}

Controller ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ

package com.sparta.myselectshop.mvc;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.myselectshop.config.WebSecurityConfig;
import com.sparta.myselectshop.controller.ProductController;
import com.sparta.myselectshop.controller.UserController;
import com.sparta.myselectshop.dto.ProductRequestDto;
import com.sparta.myselectshop.entity.User;package com.sparta.myselectshop.mvc;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.myselectshop.config.WebSecurityConfig;
import com.sparta.myselectshop.controller.ProductController;
import com.sparta.myselectshop.controller.UserController;
import com.sparta.myselectshop.dto.ProductRequestDto;
import com.sparta.myselectshop.entity.User;
import com.sparta.myselectshop.entity.UserRoleEnum;
import com.sparta.myselectshop.security.UserDetailsImpl;
import com.sparta.myselectshop.service.FolderService;
import com.sparta.myselectshop.service.KakaoService;
import com.sparta.myselectshop.service.ProductService;
import com.sparta.myselectshop.service.UserService;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;

import java.security.Principal;

import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

@WebMvcTest(
        controllers = {UserController.class, ProductController.class},
        excludeFilters = {
                @ComponentScan.Filter(
                        type = FilterType.ASSIGNABLE_TYPE,
                        classes = WebSecurityConfig.class
                )
        }
)
class UserProductMvcTest {
    private MockMvc mvc;

    private Principal mockPrincipal;

    @Autowired
    private WebApplicationContext context;

    @Autowired
    private ObjectMapper objectMapper;

    // @MockBean: Bean ์ฃผ์ž…(๊ฐ€์งœ Bean)
    @MockBean
    UserService userService;

    @MockBean
    KakaoService kakaoService;

    @MockBean
    ProductService productService;

    @MockBean
    FolderService folderService;

    @BeforeEach
    public void setup() {
        // ๊ธฐ๋ณธ Security Filter ์ œ์™ธํ•˜๊ณ , ๋งŒ๋“  Mock Filter ๋„ฃ์–ด์ฃผ๊ธฐ
        mvc = MockMvcBuilders.webAppContextSetup(context)
                .apply(springSecurity(new MockSpringSecurityFilter()))
                .build();
    }

    private void mockUserSetup() {
        // Mock ํ…Œ์ŠคํŠธ ์œ ์ ธ ์ƒ์„ฑ
        String username = "sollertia4351";
        String password = "robbie1234";
        String email = "sollertia@sparta.com";
        UserRoleEnum role = UserRoleEnum.USER;
        User testUser = new User(username, password, email, role);
        UserDetailsImpl testUserDetails = new UserDetailsImpl(testUser);
        mockPrincipal = new UsernamePasswordAuthenticationToken(testUserDetails, "", testUserDetails.getAuthorities());
    }

    @Test
    @DisplayName("๋กœ๊ทธ์ธ Page")
    void test1() throws Exception {
        // when - then
        mvc.perform(get("/api/user/login-page"))
                .andExpect(status().isOk())
                .andExpect(view().name("login"))
                .andDo(print());
    }

    @Test
    @DisplayName("ํšŒ์› ๊ฐ€์ž… ์š”์ฒญ ์ฒ˜๋ฆฌ")
    void test2() throws Exception {
        // given
        MultiValueMap<String, String> signupRequestForm = new LinkedMultiValueMap<>();
        signupRequestForm.add("username", "sollertia4351");
        signupRequestForm.add("password", "robbie1234");
        signupRequestForm.add("email", "sollertia@sparta.com");
        signupRequestForm.add("admin", "false");

        // when - then
        mvc.perform(post("/api/user/signup")
                        .params(signupRequestForm)
                )
                .andExpect(status().is3xxRedirection())
                .andExpect(view().name("redirect:/api/user/login-page"))
                .andDo(print());
    }

    @Test
    @DisplayName("์‹ ๊ทœ ๊ด€์‹ฌ์ƒํ’ˆ ๋“ฑ๋ก")
    void test3() throws Exception {
        // given
        this.mockUserSetup();
        String title = "Apple <b>์•„์ดํฐ</b> 14 ํ”„๋กœ 256GB [์ž๊ธ‰์ œ]";
        String imageUrl = "https://shopping-phinf.pstatic.net/main_3456175/34561756621.20220929142551.jpg";
        String linkUrl = "https://search.shopping.naver.com/gate.nhn?id=34561756621";
        int lPrice = 959000;
        ProductRequestDto requestDto = new ProductRequestDto(
                title,
                imageUrl,
                linkUrl,
                lPrice
        );

        String postInfo = objectMapper.writeValueAsString(requestDto);

        // when - then
        mvc.perform(post("/api/products")
                        .content(postInfo)
                        .contentType(MediaType.APPLICATION_JSON)
                        .accept(MediaType.APPLICATION_JSON)
                        .principal(mockPrincipal)
                )
                .andExpect(status().isOk())
                .andDo(print());
    }
}
import com.sparta.myselectshop.entity.UserRoleEnum;
import com.sparta.myselectshop.security.UserDetailsImpl;
import com.sparta.myselectshop.service.FolderService;
import com.sparta.myselectshop.service.KakaoService;
import com.sparta.myselectshop.service.ProductService;
import com.sparta.myselectshop.service.UserService;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;

import java.security.Principal;

import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

@WebMvcTest(
        controllers = {UserController.class, ProductController.class},
        excludeFilters = {
                @ComponentScan.Filter(
                        type = FilterType.ASSIGNABLE_TYPE,
                        classes = WebSecurityConfig.class
                )
        }
)
class UserProductMvcTest {
    private MockMvc mvc;

    private Principal mockPrincipal;

    @Autowired
    private WebApplicationContext context;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    UserService userService;

    @MockBean
    KakaoService kakaoService;

    @MockBean
    ProductService productService;

    @MockBean
    FolderService folderService;

    @BeforeEach
    public void setup() {
        mvc = MockMvcBuilders.webAppContextSetup(context)
                .apply(springSecurity(new MockSpringSecurityFilter()))
                .build();
    }

    private void mockUserSetup() {
        // Mock ํ…Œ์ŠคํŠธ ์œ ์ ธ ์ƒ์„ฑ
        String username = "sollertia4351";
        String password = "robbie1234";
        String email = "sollertia@sparta.com";
        UserRoleEnum role = UserRoleEnum.USER;
        User testUser = new User(username, password, email, role);
        UserDetailsImpl testUserDetails = new UserDetailsImpl(testUser);
        mockPrincipal = new UsernamePasswordAuthenticationToken(testUserDetails, "", testUserDetails.getAuthorities());
    }

    @Test
    @DisplayName("๋กœ๊ทธ์ธ Page")
    void test1() throws Exception {
        // when - then
        mvc.perform(get("/api/user/login-page"))
                .andExpect(status().isOk())
                .andExpect(view().name("login"))
                .andDo(print());
    }

    @Test
    @DisplayName("ํšŒ์› ๊ฐ€์ž… ์š”์ฒญ ์ฒ˜๋ฆฌ")
    void test2() throws Exception {
        // given
        MultiValueMap<String, String> signupRequestForm = new LinkedMultiValueMap<>();
        signupRequestForm.add("username", "sollertia4351");
        signupRequestForm.add("password", "robbie1234");
        signupRequestForm.add("email", "sollertia@sparta.com");
        signupRequestForm.add("admin", "false");

        // when - then
        mvc.perform(post("/api/user/signup")
                        .params(signupRequestForm)
                )
                .andExpect(status().is3xxRedirection())
                .andExpect(view().name("redirect:/api/user/login-page"))
                .andDo(print());
    }

    @Test
    @DisplayName("์‹ ๊ทœ ๊ด€์‹ฌ์ƒํ’ˆ ๋“ฑ๋ก")
    void test3() throws Exception {
        // given
        this.mockUserSetup();
        String title = "Apple <b>์•„์ดํฐ</b> 14 ํ”„๋กœ 256GB [์ž๊ธ‰์ œ]";
        String imageUrl = "https://shopping-phinf.pstatic.net/main_3456175/34561756621.20220929142551.jpg";
        String linkUrl = "https://search.shopping.naver.com/gate.nhn?id=34561756621";
        int lPrice = 959000;
        ProductRequestDto requestDto = new ProductRequestDto(
                title,
                imageUrl,
                linkUrl,
                lPrice
        );

        String postInfo = objectMapper.writeValueAsString(requestDto);

        // when - then
        mvc.perform(post("/api/products")
                        .content(postInfo)
                        .contentType(MediaType.APPLICATION_JSON)
                        .accept(MediaType.APPLICATION_JSON)
                        .principal(mockPrincipal)
                )
                .andExpect(status().isOk())
                .andDo(print());
    }
}
  • @WebMvcTest ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด Controller ์ชฝ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
    • controllers: ํ…Œ์ŠคํŠธํ•  Controller ํด๋ž˜์Šค ์ง€์ •
    • excludeFilters: ์ œ์™ธํ•  ๊ฒƒ ์ง€์ •