๋จ์ ํ ์คํธ๋ ๋ฌด์์ผ๊น?
- ๋ฒ๊ทธ ๋ฐ๊ฒฌ ์๊ฐ์ด ๋ฆ์ด์ง์ ๋ฐ๋ผ ๋น์ฉ์ด ๊ธฐํ๊ธ์์ ์ผ๋ก ์ปค์ง

- Development: ๊ฐ๋ฐ
- Unit Tests(๋จ์ ํ ์คํธ): ๊ฐ๋ฐ์ ํ ์คํธ
- QA Testing(๋ธ๋๋ฐ์ค ํ ์คํ ): ์ฃผ๋ก QA ํ์ด Production ํ๊ฒฝ๊ณผ ์ ์ฌํ ํ๊ฒฝ์์ ํ ์คํ
- 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: ์ ์ธํ ๊ฒ ์ง์