Spring Boot Test์ ์ฌ๋ผ์ด์ค ํ ์คํธ
by JiwonDev2022.03.06 SpringBootTest ๋ด์ฉ ์ถ๊ฐ(์ถํ ์ ๋ฆฌ ์์ )
System.out ๊ฐ์ ๊ฑธ ์ฌ์ฉํ๋ฉด ํ
์คํธ ๋ชปํ์์์?
-> ์ธํฐํ์ด์ค๋ก ์ถ์ํํด์ ์ฌ์ฉํ๊ณ , ์์กด์ฑ์ ๋ถ๋ฆฌ์ํค๋ฉด ๊ฐ๋ฅํฉ๋๋ค.
๐จ ์ธ๋ถ์์กด์ฑ์ด ์๋ ๊ฐ์ฒด๋ Mockito ๋ฑ์ ์ด์ฉํด์ ์กฐ์(Mocking)ํฉ๋๋ค.
- ๋ฌผ๋ก ์ด๋ฅผ ์กฐ์ํ๋ ค๋ฉด, ์ธ๋ถ์์ ๋ด๊ฐ ์ํ๋ ์์กด์ฑ์ ์์ฑ์๋ก ์ฃผ์
ํ ์ ์์ด์ผํฉ๋๋ค.
- ํน์ ๋ฉ์๋ ex) console.todayAsString() ๊ฐ ์คํ๋๋ฉด, ๋ด๊ฐ ์ํ๋ ๊ฒฐ๊ณผ๊ฐ์ ๋ฐํํ๋๋ก ์กฐ์ํ ์ ์์ต๋๋ค.
- ํน์ ๋ฉ์๋๊ฐ ์คํ๋๋ฉด exception์ด ํฐ์ง๋๋ก ๋ง๋ค ์๋ ์์ต๋๋ค.
@Mock Console console; var myConsolePrinter = new MyConsolePrinter(console); given(console.todayAsString()) // ์ํ๋ฅผ ์กฐ์, ์ด๋ฅผ Mocking ์ด๋ผ ํฉ๋๋ค. .willReturn("01/04/2014", "02/04/2014", "10/04/2014"); var myService = new MyService(myConsolePrinter )
๐จ ์ค์ ์ฌ์ฉ์ ์
์ฅ์ ์๊ตฌ์ฌํญ ํ์ธ์, ์ธ์ ํ
์คํธ(Acceptance Test)๋ผ๊ณ ํฉ๋๋ค.
- ์ด๋ main ํจ์๋, ์ค์ ์ฑ์ ์คํ์์ผ์ ํ
์คํธํ๋ ๊ฒฝ์ฐ๋ ํฌํจํฉ๋๋ค. ์์ฃผํด๋ดค์ฃ .
- ๋ฌผ๋ก Acceptance Test ๋ ํ
์คํธ ์ฝ๋๋ก ๋ง๋ค ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด ์๋์ ๊ฐ์ mainํจ์๊ฐ ์์ ๋
public class BankApplication { public static void main(String[] args) { Clock clock = new Clock(); TransactionRepository transactionRepository = new TransactionRepository(clock); Console console = new Console(); StatementPrinter statementPrinter = new StatementPrinter(console); Account account = new Account(transactionRepository, statementPrinter); // ๋ด๊ฐ ํ์ธํ๊ณ ์ถ์ ๊ฒฐ๊ณผ๋ค, ์ฌ๊ธฐ์ ์ค์ DB๋ ์ฝ์์ ๋ค์ ธ๊ฐ๋ฉฐ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํด์ผํฉ๋๋ค. account.deposit(1000); account.withdraw(100); account.deposit(500); account.printStatement(); } }
- ์ด๋ฅผ ATDD (์ธ์ TDD, AcceptanceTDD) ๋ก ํ๋ฉด ์ด๋ ๊ฒ ํ ์ ์์ต๋๋ค. ๋ฐฉ๊ธ ์ค๋ช
ํ Mocking ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํ๊ฑฐ์ฃ .
@ExtendWith(MockitoExtension.class) // @Mock ์ฌ์ฉ์ ์ํ ์ค์ public class PrintStatementFeature { @Mock Console console; @Mock Clock clock; private Account account; @BeforeEach void setUp() { TransactionRepository transactionRepository = new TransactionRepository(clock); StatementPrinter statementPrinter = new StatementPrinter(console); account = new Account(transactionRepository, statementPrinter); } @Test void print_statement_containing_all_transactions() { given(clock.todayAsString()).willReturn("01/04/2014", "02/04/2014", "10/04/2014"); account.deposit(1000); account.withdraw(100); account.deposit(500); account.printStatement(); // ๋ฏธ๋ฆฌ ๋ง๋ค์ด๋ ๊ฐ์ง๊ฐ์ฒด(Mock)์ ์ด์ฉํด์ ํ
์คํธ๋ฅผ ๊ฒ์ฆํฉ๋๋ค. InOrder inOrder = inOrder(console); inOrder.verify(console).printLine("DATE | AMOUNT | BALANCE"); inOrder.verify(console).printLine("10/04/2014 | 500.00 | 1400.00"); inOrder.verify(console).printLine("02/04/2014 | -100.00 | 900.00"); inOrder.verify(console).printLine("01/04/2014 | 1000.00 | 1000.00"); } }
๐จ ๋ค๋ง ์กฐ์ํ๊ธฐ ์ด๋ ค์ด ์ธ๋ถ ์์กด์ฑ(์ฝ์, DB๋ฑ)์ด ์๋ค๋ฉด, ๊ตณ์ด Mockito๋ฅผ ์ฌ์ฉํ์ง ์์๋๋ฉ๋๋ค.
๊ทธ๋ฅ ์ง์ SpyRepository RepositoryStub์ด๋ฐ ์ด๋ฆ์ ๊ฐ์ง๊ฐ์ฒด๋ฅผ ๋ง๋ค๋ฉด ๋๊ฑฐ๋ ์
๐จ ์ค์ ๋น์ฆ๋์ค ์ฑ, ์คํ๋ง์์๋ ๋ค ๊ฐ๋ฅํฉ๋๋ค.
์ธ๋ถ API, ๋ฐ์ดํฐ๋ฒ ์ด์ค, ๋ณต์กํ ์์กด์ฑ.. ๋ค ํ
์คํธ ๊ฐ๋ฅํฉ๋๋ค.
ํ
์คํธ์ ๋์์ด (์ธ๋ถ ์์กด์ฑ์ ์ฌ์ฉํ๋) ๋ด๊ฐ ๋ง๋ ๊ฐ์ฒด์ฌ์ผ ์๋ฏธ๊ฐ ์์ต๋๋ค.
์ธ๋ถ์์กด์ฑ ์์ฒด๋ฅผ ํ
์คํธํ๋๊ฑด ์๋ฌด๋ฐ ์๋ฏธ๊ฐ ์์ต๋๋ค.
KakaoPay API ๊ฐ ์ ๋์ํ๋์ง, MySQL DB ์๋ฒ๊ฐ ์ ๋์ํ๋์ง ์คํ๋ง์์ ํ
์คํธ ํ๋ ๊ฑด ์๋ฌด๋ฐ ์๋ฏธ๊ฐ ์์ฃ .
๐จ ์๋ฅผ ๋ค์ด @SpringBootTest(webEnviroment=...) ๋ ์๋์ ๊ฐ์ ์ต์ ์ด ์์ต๋๋ค.
MOCK (์๋ต์ ๊ธฐ๋ณธ๊ฐ) = MockMvc ์ฌ์ฉ
RANDOM_PORT, DEFINED_PORT = ํฌํธ๋ฅผ ์ง์ ํด์ ์ค์ ํฐ์บฃ์ ๋์๋ด
// @SpringBootTest(webEnviroment=...) = MOCK (์๋ต์ ๊ธฐ๋ณธ๊ฐ) = MockMvc ์ฌ์ฉ = RANDOM_PORT, DEFINED_PORT = ํฌํธ๋ฅผ ์ง์ ํด์ ์ค์ ํฐ์บฃ์ ๋์๋ด
Random์ ์ฌ์ฉํด์ ์ค์ ์ปจํ ์ด๋์ ์ํธ์์ฉํด๋ณด๋ ค๋ฉด RestTemplate ๋ WebClient๋ฅผ ์จ์ผํ๋๋ฐ, ํ ์คํธ ์ฉ๋๋ก TestRestTemaplte WebTestClient ๊ฐ์๊ฑฐ๋ ์ ๊ณตํด์ค๋๋ค.
- TestRestTemplate์ ์ค์ ํด๋ผ์ด์ธํธ๊ฐ HTTP ํต์ ์ผ๋ก ์์ธํ์ด์ง๊ฐ ๋ ๋๋ง ๋์๋์ง ํ์ธ ๊ฐ๋ฅํ๊ณ
- MockMvc๋ ๋จ์ํ ๋์คํจ์ณ ์๋ธ๋ฆฟ์ด ์์ธ๋ฅผ ์ throwํ๊ณ ์ ๋ฌํ๋์ง, ๋ฐํ๊ฐ์ ์ ์ ๋ฌํ๋์ง ๋ก์ง๋ง์ ํ์ธํ ์ ์์ต๋๋ค.
๊ทผ๋ฐ ์ฌ์ค ๊ทธ๋ฐ ๊ฒฝ์ฐ๋ผ๋ฉด ๋ชจ๋ ๋น์ ์ปจํ ์ด๋(AppContext)์ ๋์ธ ํ์๋ ์์ผ๋๊น,
SpringBootTest ๋ง๊ณ ํ ์คํธ์ ํ์ํ ๋น๋ง ๋์ธ ์ ์๋ @WebMvcTest ๊ฐ์ ์ฌ๋ผ์ด์ค ํ ์คํธ๋ฅผ ์ฐ๋๊ฒ ์ ์ผ ํ ์คํธ ์๋๊ฐ ๋นจ๋ผ์ ์ข์ฃ .
# MockMvc ์ฌ์ฉ๋ฒ

// ๐จ MockMvcRequestBuilders๋ RequestBuilder์ธํฐํ์ด์ค์ ๊ตฌํ ๊ฐ์ฒด์ด๋ค. // RequestBuilder reqBuilder = MockMvcRequestBuilders.get("/"); MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/"); request.param("name", "์ด๋ฆ") .content("content") .sessionAttr("session", "์ธ์
");
์คํ ๊ฒฐ๊ณผ๋ ์๋์ ๊ฐ์ด ResultActions๋ก ๋ฐ์ ๊ฒ์ฆํ ์ ์๋ค.
RequestBuilder request = MockMvcRequestBuilders.get("/admin/login"); var actions = mockMvc.perform(request); // ์คํ๊ฒฐ๊ณผ ๋ฐ๊ธฐ, ResultActions ๊ฐ์ฒด๋ก ๋ฐํ actions.andDo(print()) .andExpect(status().isOk()) .andExpect(model().attributeExists("signUpForm")) .andExpect(view().name("account/sign-up"));
ResultActions ๊ฐ์ฒด์ andExpect() ๋ฉ์๋๊ฐ ์๋๋ฐ, ์ด ๋ฉ์๋๋ ์ธ์ ๊ฐ์ผ๋ก org.springframework.test.web.servlet.ResultMatcher ๋ฅผ ๋ฐ๋๋ค
spring-boot-test์์ ์ ๊ณตํ๋ ํฉํ ๋ฆฌ ๋ฉ์๋์ธ MockMvcResultMatchers ํด๋์ค๋ฅผ ์ด์ฉํ๋ฉด ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
์ฐธ๊ณ ๋ก ResultActions์๋ ๊ฒ์ฆ๋ง๊ณ ํน์ ๋ฉ์๋๋ฅผ ์คํํ ์๋ ์๋ค. ๋ก๊ทธ๋ฅผ ๋จ๊ธด๋ค๊ฑฐ๋, ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ํ๋ฉด์ ์ฐ๋๋ค๊ฑฐ๋
ResultActions ์ ๋ฉ์๋
public static ResultHandler log()
: ์คํ ๊ฒฐ๊ณผ๋ฅผ ๋๋ฒ๊น
๋ ๋ฒจ์์ ๋ก๊ทธ๋ก ์ถ๋ ฅํ๋ค. ๋ก๊ทธ๋ฅผ ์ถ๋ ฅํ ๋ ์ฌ์ฉ๋๋ ๋ก๊ฑฐ ์ด๋ฆ์ org.springframework.test.web.servlet.result๋ค
public static ResultHandler print()
: ์คํ ๊ฒฐ๊ณผ๋ฅผ ์์์ ์ถ๋ ฅ ๋์์ ์ถ๋ ฅํ๋ค. ์ถ๋ ฅ๋์์ ์ง์ ํ์ง ์์ผ๋ฉด ๊ธฐ๋ณธ์ ์ผ๋ก ํ์ค ์ถ๋ ฅ(System.out)์ด ์ถ๋ ฅ ๋์์ด ๋๋ค
์์ฒญ๋ง๋ค๊ธฐ - MockHttpServletRequestBuilder
public MockHttpServletRequestBuilder param(String name, String... values)
public MockHttpServletRequestBuilder params(MultiValueMap<String,String> params)
: ์์ฒญํ๋ผ๋ฏธํฐ๋ฅผ ์ค์ ํ๋ค
public MockHttpServletRequestBuilder header(String name, Object... values)
public MockHttpServletRequestBuilder headers(HttpHeaders httpHeaders)
: ์์ฒญ ํค๋๋ฅผ ์ค์ ํ๋ค. contentType์ด๋ accept์ ๊ฐ์ ํน์ ํค๋๋ฅผ ์ค์ ํ ์ ์๋ ๋ฉ์๋๋ ์ ๊ณตํ๋ค.
public MockHttpServletRequestBuilder cookie(Cookie... cookies)
: ์ฟ ํค๋ฅผ ์ค์ ํ๋ค
public MockHttpServletRequestBuilder content(String content)
: ์์ฒญ ๋ณธ๋ฌธ์ ์ค์ ํ๋ค
public MockHttpServletRequestBuilder requestAttr(String name, Object value)
: ์์ฒญ์ค์ฝํ์ ๊ฐ์ฒด๋ฅผ ์ค์ ํ๋ค
public MockHttpServletRequestBuilder sessionAttr(String name, Object value)
public MockHttpServletRequestBuilder sessionAttrs(Map<String,Object> sessionAttributes)
: ์ธ์ ์ค์ฝํ์ ๊ฐ์ฒด๋ฅผ ์ค์ ํ๋ค.
public MockHttpServletRequestBuilder flashAttr(String name, Object value)
: ํ๋์ฌ ์ค์ฝํ์ ๊ฐ์ฒด๋ฅผ ์ค์ ํ๋ค.
๊ฒ์ฆ - MockMvcResultMatchers
public static ViewResultMatchers view()
: ์ปจํธ๋กค๋ฌ๊ฐ ๋ฐํํ ๋ทฐ ์ด๋ฆ์ ๊ฒ์ฆํ๋ค.
public static StatusResultMatchers status()
: HTTP ์ํ ์ฝ๋๋ฅผ ๊ฒ์ฆํ๋ค.
public static HeaderResultMatchers header()
: ์๋ต ํค๋์ ์ํ๋ฅผ ๊ฒ์ฆํ๋ค.
public static ContentResultMatchers content()
: ์๋ต ๋ณธ๋ฌธ ๋ด์ฉ์ ๊ฒ์ฆํ๋ค. jsonPath๋ xpath์ ๊ฐ์ ํน์ ์ฝํ ์ธ ๋ฅผ ์ํ ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ค
public static CookieResultMatchers cookie()
: ์ฟ ํค์ ์ํ๋ฅผ ๊ฒ์ฆํ๋ค
public static ModelResultMatchers model()
: ์คํ๋ง MVC ๋ชจ๋ธ ์ํ๋ฅผ ๊ฒ์ฆํ๋ค.
public static ResultMatcher redirectedUrl(String expectedUrl)
: ๋ฆฌ๋ค์ด๋ ํธ ๋์์ ๊ฒฝ๋ก ๋๋ URL์ ๊ฒ์ฆํ๋ค. ํจํด์ผ๋ก ๊ฒ์ฆํ ๋๋ redirectedUrlPattern ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค.
public static ResultMatcher forwardedUrlPattern(String urlPattern)
: ์ด๋ ๋์ ๊ฒฝ๋ก๋ฅผ ๊ฒ์ฆํ๋ค. ํจํด์ผ๋ก ๊ฒ์ฆํ ๋๋ forwardedUrlPattern ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค.
public static RequestResultMatchers request()
: ์๋ธ๋ฆฟ 3.๋ถํฐ ์ง์๋๋ ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ์ํ๋ ์์ฒญ์ค์ฝํ์ ์ํ, ์ธ์ ์ค์ฝํ์ ์ํ๋ฅผ ๊ฒ์ฆํ๋ค.
# ์ฌ๋ผ์ด์ค ํ ์คํธ๊ฐ ์ ํ์ํ์ฃ ?
์ฌ๋ผ์ด์ค ํ ์คํธ๋ ์ ์ฒด์ค ์ผ๋ถ๋ง ๊ฐ์ ธ์์ ํ๋ ํ ์คํธ๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค.
- @SpringBootTest๋ ํ
์คํธ์ ์๊ด์๋ ์ฑ ์ค์ , ๋ชจ๋ Bean๋ค์ ๋ถ๋ฌ์ค๊ธฐ์ ์ค๋๊ฑธ๋ฆฐ๋ค.
โก @SpringBootTest๋ ๋จ์ํ ์คํธ์ ์ฌ์ฉํ๊ธฐ์ ์ ํฉํ์ง ์๋ค. - ํ ์คํธ์ ๋จ์๊ฐ ์ปค์ ธ ๋๋ฒ๊น ์ด ์ด๋ ค์์ง๋ค.
- ๊ทธ๋ ๋ค๊ณ @Test๋ง ์ฌ์ฉํ๊ธฐ์๋ ์คํ๋ง ํ ์คํธ ์ค๋น ์์ ์ด ๋๋ฌด ๋ฒ๊ฑฐ๋กญ๋ค.
# SpringBoot์ ์ฌ๋ผ์ด์ค ํ ์คํธ
์ด๋ ธํ ์ด์ ์ ํ ์คํธ Class์ ๋ถ์ฌ์ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋ค. ์์ ์ฝ๋์ ํจ๊ป ๊ฐ๋จํ๊ฒ ์์๋ณด์.
@WebMvcTest - MockApiTest (MVC ๊ด๋ จ๋ Bean)
โก MockMvc์ ์๋์ผ๋ก ์์ฑ/์ค์ ํด์ฃผ์ด MVC์ ๊ด๋ จ๋ ๋จ์ํ ์คํธ์์ ์ฌ์ฉ๋๋ค.
โก @Mock ์ฒ๋ผ ์ฌ์ฉํ ์ ์๋๋ก ์คํ๋ง ๋น์ ์ํ @MockBean ์ด๋ ธํ ์ด์ ์ ์ ๊ณตํด์ค๋ค. (๊ป๋ฐ๊ธฐ๋ง ์์. ๊ตฌํ X)
@WebMvcTest(PaymentController.class) public class PaymentControllerTests { @AutoWired private MockMvc mvc; @MockBean private PaymentService paymentService; // PaymentService ๋ด๋ถ์์ ์ธ๋ถ์ ๊ฒฐ์ ๋ํ ์๋น์ค๋ฅผ ์ฌ์ฉํ๊ณ ์๋ ์ํ๋ผ๊ณ ๊ฐ์ @Test public void testPayment() throws Exception { // 5๋ง์ ๊ธ์ก ์ถฉ์ : ํ
์คํธ ํ๊ฒฝ์์๋ ์คํจํ๋ ํ์์ด์ง๋ง given(this.payMentService.chargePoint(50000L)) .willReturn(new Point(50000L)); // ์ฌ๋ฐ๋ฅธ ์์ฒญ์ผ๋ก ๊ฐ์ฃผํ๊ณ ๋ฐํํ๋๋ก ํ์ ์ง์ } }
// ํ
์คํธํ ์ปจํธ๋กค๋ฌ ํด๋์ค๋ฅผ ์ง์ ํด์ฃผ์ด์ผ ํ๋ค. @WebMvcTest(ArticleApiController.class) public class ArticleApiControllerTest { @Autowired private MockMvc mockMvc; // MockMvc๋ฅผ ์๋์ผ๋ก ์ค์ ํด์ฃผ๋ฉฐ, ์ด๋ฅผ ์ด์ฉํด ํ
์คํธํ ์ ์๋ค. @MockBean // ๋ฌผ๋ก MockBean์ ์ฌ์ฉํ์ง ์๊ณ , ์ง์ ๊ฐ์ง๊ฐ์ฒด(Stub)์ ๋ง๋ค์ด๋ ๋๋ค. private ArticleService articleService; @DisplayName("ํ ํฐ๊ณผ ์ด๋ฉ์ผ ์ ๋ณด๋ก ๊ฒ์ฆ์์ฒญ์ ๋ณด๋ธ ๊ฒฝ์ฐ 303 HTTP Code ๋ฆฌํดํ๋ค.") void valid_success() throws Exception { //Given var request = new LoginAccountRequest(); request.setEmail(args); request.setPassword(args); //When. ์ฐธ๊ณ ๋ก ๊ตณ์ด actions ๋ณ์๋ก ๋ฐ์ง ์์๋ ์ฌ์ฉ๊ฐ๋ฅํ๊ธด ํ๋ค. var actions = mockMvc.perform(post("/api/login") .accept(MediaType.APPLICATION_JSON) // accept ํค๋ ์ค์ .content(objectMapper.writeValueAsString(request)) // content Body ์ค์ .param("email","hi@naver.com") // ์์ฒญ ํ๋ผ๋ฏธํฐ ์ค์ ); //Then actions .andDo(print()) // ํ
์คํธ์ ์ฑ๊ณตํ๋๋ผ๋ ์ฝ์์ Printํ๋๋ก ํ๋ค. .andExpect(status().isOk()) //200์ฝ๋ .andExpect(jsonPath("$.id").isNumber()) // Json๋ ๊ฐ๋จํ๊ฒ ํ์ธ๊ฐ๋ฅ .andExpect(jsonPath("$.email").isString()) .andExpect(jsonPath("$.nickname").isString()); } }
์ฐธ๊ณ ๋ก ์ปจํธ๋กค๋ฌ์์ ๋น๋๊ธฐ๋ก ์๋ต (Future๋ DeferredResult๋ฅผ ๋ฐํ)ํ๋ ๊ฒฝ์ฐ ์๋์ ๊ฐ์ด ์ฌ์ฉํ๋ฉด ๋๋ค.
// MvcResult ์ธํฐํ์ด์ค๋ก ๋น๋๊ธฐ ์๋ต์ ๋ฐ์ (Future, DeferredResult) MvcResult result = mockMvc.perform(get("/api/articles/1")).andReturn(); Mockmvc.perform(asyncDispatch(result)) // asyncDispatch๋ฅผ ์ด์ฉ. .andExpect(status().isOk()) .andExpect(jsonPath("@.id").value(1));
@DataJpaTest - RepositoryTest (Jpa ๊ด๋ จ Bean)
โก ์ธ๋ฉ๋ชจ๋ฆฌ DB๋ฅผ ์์ฑํ๊ณ @Transaction์ ์๋์ผ๋ก ๋ถ์ฌ์ค๋ค. JPA์ ๊ด๋ จ๋ ๋จ์ํ ์คํธ์์ ์ฌ์ฉ๋๋ค.
์ฐธ๊ณ ๋ก Spring Data JPA๋ง๊ณ ๋ @JdbcTest, @DataMongoTest ๋ฑ์ ์ฌ๋ผ์ด์ค ํ ์คํธ๋ ์๋ค.
// ํ๋ก์ ํธ ๋ด์ @Entity๋ฅผ ์ค์บํ๊ณ , TestEntityManager ๋น์ ์๋ ์ค์ ํด์ค๋ค. @DataJpaTest public class ArticleDaoTest { @Autowired private TestEntityManager entityManager; @Autowired private ArticleDao articleDao; @Test public void test() { Article articleByKwseo = new Article(1, "kwseo", "good", "hello", Timestamp.valueOf( LocalDateTime.now())); Article articleByKim = new Article(2, "kim", "good", "hello", Timestamp.valueOf(LocalDateTime.now())); entityManager.persist(articleByKwseo); entityManager.persist(articleByKim); List<Article> articles = articleDao.findByAuthor("kwseo"); assertThat(articles) .isNotEmpty() .hasSize(1) .contains(articleByKwseo) .doesNotContain(articleByKim); } }
์ฐธ๊ณ ๋ก @DataJpaTest ์์๋ @Transactional ํค์๋๋ฅผ ํฌํจํ๊ณ ์๋ค. ๋ง์ฝ ์๋์ ์ผ๋ก ํธ๋์ญ์ ์ ์ ์ฉํ๊ณ ์ถ์ง ์๋ค๋ฉด ์๋์ ๊ฐ์ด propagation์ ๋ฐ๊ฟ์ฃผ์.
// @Entity๋ฅผ ์ค์บํ๊ณ TestEntityManager ๋น์ ์์ฑํ๋ค. @DataJpaTest @Transactional(propagation = Propagation.NOT_SUPPORTED) public class SomejpaTest { ... }
@JsonTest
โก JacksonTester๋ฅผ ์์ฑํด์ฃผ๋ฉฐ JSON์ ์ง๋ ฌํ, ์ญ์ง๋ ฌํํด๋ณด๊ณ ํ์ผ๊ณผ ๋น๊ตํ๋ ๋ฑ์ ํ ์คํธ ํด๋ณผ ์ ์๋ค.
@JsonTest public class ArticleJsonTest { @Autowired private JacksonTester<Article> json; @Test public void testSerialize() throws IOException { Article article = new Article( 1, "kwseo", "good", "good article", Timestamp.valueOf((LocalDateTime.now()))); // assertThat(json.write(article)).isEqualToJson("expected.json"); ์ง์ ํ์ผ๊ณผ ๋น๊ต assertThat(json.write(article)).hasJsonPathStringValue("@.author"); assertThat(json.write(article)) .extractingJsonPathStringValue("@.title") .isEqualTo("good"); } }
@RestClientTest
โก ์์ฒญ์ ๋ฐ์ํ๋ MockRestServiceServer ์๋ฒ๋ฅผ ๋ง๋ค๋ฉฐ, ํด๋ผ์ด์ธํธ ์ ์ฅ์์ ์ ์ํ๋ ๊ฒ์ฒ๋ผ ํ ์คํธํ๋ค.
@RestClientTest(ArticleServiceImpl.class) public class ArticleServiceImplWithRestClientTest { @MockBean private ArticleDao dao; @Autowired private ArticleServiceImpl service; @Autowired private MockRestServiceServer server; @Test public void testGetFindOneFromRemote() throws Exception { String articleJson = ...; // ์๋ฒ์ Response๋ฅผ ์คํฐ๋นํจ. server.expect(requestTo("http://sample.com/some/articles/1")) .andRespond(withSuccess(articleJson, MediaType.APPLICATION_JSON)); // ๊ฒ์ํ article์ ์๋ฒ์์ ๋ฐ์์ด Article article = service.findOneFromRemote(1); assertThat(article.getId()).isEqualTo(1); } }
@WebFluxTest
โก ์คํ๋ง WebFlux ์ ๊ด๋ จ๋ ์ปดํฌ๋ํธ๋ฅผ ์๋ ๊ตฌ์ถํ๊ณ , ๋จ์ํ ์คํธ์์ ์ฌ์ฉ๋๋ค.
์ฐธ๊ณ ๋ก @WebFluxTest๋ ์๋์ ๊ฐ์ ์ค์ ์ด ํฌํจ๋์ด ์๋ค.
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @BootstrapWith(WebFluxTestContextBootstrapper.class) @ExtendWith(SpringExtension.class) @OverrideAutoConfiguration(enabled = false) @TypeExcludeFilters(WebFluxTypeExcludeFilter.class) @AutoConfigureCache @AutoConfigureJson @AutoConfigureWebFlux @AutoConfigureWebTestClient @ImportAutoConfiguration public @interface WebFluxTest { }
์ฐธ๊ณ ๋ก WebFlux๋ฅผ ์ฌ์ฉํ์ง ์๋๋ผ๋ ํด๋น ํ ์คํธ๋ฅผ ์ด์ฉํ๋ฉด BDD (Given-When-Then) ํ ์คํธ๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ์์ฑํ ์ ์๋ค. ์์ธํ๊ฑด ์๋ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ์.
Spring WebFlux์ Kotlin์ผ๋ก ๋ง๋๋ Todo ์๋น์ค โ ํ ์คํธ ์ฌ๋ผ์ด์ค ์ ์ฉํ๊ธฐ | Popit
Spring WebFlux์ Kotlin์ผ๋ก ๋ง๋๋ Todo ์๋น์ค โ 1ํธ Spring WebFlux์ Kotlin์ผ๋ก ๋ง๋๋ Todo ์๋น์ค โ 2ํธ Spring WebFlux์ Kotlin์ผ๋ก ๋ง๋๋ Todo ์๋น์ค โ ํ ์คํธ ์ฌ๋ผ์ด์ค ์ ์ฉํ๊ธฐ ๊ฐ์ ์ง๋ ์์ ๋ค์์
www.popit.kr
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev