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 Framework > Test (JUnit, SpringBootTest)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
ํ ์คํธ ๋ฐ์ดํฐ๋๊ตฌ - Fixture Monkey (3) | 2022.04.02 |
---|---|
์ข์ ํ ์คํธ๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ (5) | 2022.03.08 |
spring security ํ ์คํธ์ฉ ์ค์ ๋ฐฉ๋ฒ (1) | 2022.03.07 |
์๋ฐ ํ ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ - JUnit5, AssertJ (0) | 2021.09.07 |
๋ค์ํ ํ ์คํธ์ Test Double (+Mockist) (0) | 2021.09.07 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev