3. ์คํ๋ง ๋ถํธ ํ์ฉ
3. ์คํ๋ง ๋ถํธ ํ์ฉ ๊ด๋ จ
1. ์คํ๋ง ๋ถํธ ํต์ฌ ๊ธฐ๋ฅ
SpringApplication
SpringApplication
ํด๋์ค์ ๋ํด์ ์กฐ๊ธ ๋ ์ดํด ๋ณธ๋ค.
๋ก๊ทธ
- ์คํ๋ง ๋ถํธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ธฐ๋ณธ ๋ก๊ทธ ๋ ๋ฒจ์ INFO ์ด๋ค.
- IntelliJ์ Edit Configuration์์ VM options๊ฐ์
-Ddebug
๋ก ํ ๋นํด์ runํ๋ฉด, DEBUG ๋ ๋ฒจ์ ๋ก๊ทธ๋ ๋ณผ ์ ์๋ค.- DEBUG๋ ๋ฒจ๋ก ๋ก๊ทธ๋ฅผ ์ฐ์ ๋ ํ ๊ฐ์ง ํน์ดํ ์ ์ ์ด๋ค ์๋ ์ค์ ์ด ์ ์ฉ ๋๋์ง, ์ ์ฉ ์๋ ์๋ ์ค์ ์ ์ ์๋๋์ง์ ๊ดํ ๋ก๊ทธ๋ฅผ ๋ณผ ์ ์๋ค.
FailureAnalyzers
- ์ ํ๋ฆฌ์ผ์ด์ start๊ณผ์ ์์ error๊ฐ ๋ฐ์ํ์ ๋, ์๋ฌ ๋ฉ์ธ์ง๋ฅผ ๋ณด๊ธฐ ์ข๊ฒ ์ถ๋ ฅํด์ค๋ค.
- ๊ธฐ๋ณธ์ ์ผ๋ก ์คํ๋ง๋ถํธ์๋ ์ฌ๋ฌ๊ฐ์ง Failure Analyzer๊ฐ ๋ฑ๋ก๋์ด์๋ค.
๋ฐฐ๋
- .
src/main/resources
์ ํ์์banner.txt
|gif
|jpg
|png
ํ์ผ์ ์์น์ํค๋ฉด ๋๋ค. - .
yaml
ํ์ผ์ classpath ๋๋spring.banner.location
์ ์ฌ์ฉํด์ ์์น๋ฅผ ์ง์ ํ ์ ์๋ค. - Text to ASCII ์ ๋๋ ์ดํฐ๋ฅผ ์ฌ์ฉํด์ ํ๋ฉด ์์๋ค
- http://patorjk.com/software/taag
- ๋ฐฐ๋์ ์ฌ์ฉํ ์ ์๋ ๋ณ์๋ค๋ ์๋ค.
${spring-boot.version}
๋ฑ
- ๋ฐฐ๋๋ฅผ
app.setBanner(new Banner() { ... })
๋ก ์ง์ ๊ตฌํํ ์ ์๋ค. - ๋ฐฐ๋ ๋๋ ๋ฐฉ๋ฒ
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.run(args);
app.setBannerMode(Banner.Mode.OFF);
}
}
SpringApplicationBuilder
๋ก ๋น๋ ํจํด ์ฌ์ฉ ๊ฐ๋ฅ
ApplicationEvent
์ด๋ฒคํธ ๋ฆฌ์ค๋ ์์ฒด๋ฅผ @Component
๋ก ๋ง๋ค์ด์ ์ฌ์ฉํ๋ฉด ApplicationContext
๊ฐ ๋ง๋ค์ด์ง๊ธฐ ์ ์๋ ApplicationStartingEvent
๋ ๋ฐ์์ ์ํ๋ค. ์๋์ ์ฝ๋์ด๋ค.
@Component
public class SampleListener implements ApplicationListener<ApplicationStartingEvent> {
@Override
public void onApplicationEvent(ApplicationStartingEvent event) {
System.out.println("========================");
System.out.println("Application is starting");
System.out.println("========================");
}
}
ApplicationStartingEvent
๋์ ApplicationStartedEvent
๋ฅผ ์ฌ์ฉํ๊ฑฐ๋,
#
# . ____ _ __ _ _
# /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
# ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
# \\/ ___)| |_)| | | | | || (_| | ) ) ) )
# ' |____| .__|_| |_|_| |_\__, | / / / /
# =========|_|==============|___/=/_/_/_/
# :: Spring Boot :: (v2.1.0.RELEASE)
#
# 2018-11-15 15:19:41.137 INFO 15600 --- [ main] i.n.s.Application : Starting Application on DESKTOP-EI79USO with PID 15600 (C:\document\github\spring-boot-concept-and-utilization\out\production\classes started by njkim in C:\document\github\spring-boot-concept-and-utilization)
# ...
# ...
# ...
# ========================
# Application is started!!!
# ========================
๋ฉ์ธ ํด๋์ค์์ SpringApplication.addListeners()
๋ฅผ ์ฌ์ฉํด์ ์ด๋ฒคํธ๋ฅผ ์ง์ ๋ฑ๋กํด์ฃผ๋ฉด ๋๋ค. ์ด ๋, SampleListener
ํด๋์ค๋ @Component
์ ์ธ์ ํด์ค ํ์๊ฐ ์๊ฒ ๋๋ค.
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.addListeners(new SampleListener());
app.run(args);
}
}
#
# ========================
# Application is starting
# ========================
#
# . ____ _ __ _ _
# /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
# ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
# \\/ ___)| |_)| | | | | || (_| | ) ) ) )
# ' |____| .__|_| |_|_| |_\__, | / / / /
# =========|_|==============|___/=/_/_/_/
# :: Spring Boot :: (v2.1.0.RELEASE)
#
# ...
# ...
ApplicationArguments
์ฌ์ฉํ๊ธฐ
ApplicationArguments
๋ฅผ ๋น์ผ๋ก ๋ฑ๋กํด ์ฃผ๋๊น ๊ฐ์ ธ๋ค ์ฐ๋ฉด๋๋ค.
@Component
public class ArgumentsTester {
public ArgumentsTester(ApplicationArguments arguments) {
System.out.println("foo: " + arguments.containsOption("foo"));
System.out.println("bar: " + arguments.containsOption("bar"));
}
}
jarํ์ผ์ ์คํํ ๋ ์๋์ ๊ฒฐ๊ณผ๊ฐ ๋์จ๋ค.
java -jar [path]/~~~SNAPSHOT.jar -Dfoo --bar
# ...
# foo: false
# bar: true
# ...
์ ํ๋ฆฌ์ผ์ด์ ์คํํญ ๋ค ๋จผ๊ฐ ์คํํ๊ณ ์ถ์ ๋
ApplicationRunner
(์ถ์ฒ) ๋๋CommandLineRunner
@Order
๋ฅผ ํตํด์ ์์๋ ์ง์ ํ ์ ์๋ค.
์ธ๋ถ์ค์
ํ๋กํผํฐ ์ฐ์ ์์
- ์ ์ ํ ๋๋ ํ ๋ฆฌ์ ์๋
spring-boot-dev-tools.properties
- ํ
์คํธ์ ์๋
@TestPropertySource
@SpringBootTest
์ ๋ ธํ ์ด์ ์ properties ์ ํธ๋ฆฌ๋ทฐํธ- ์ปค๋งจ๋๋ผ์ธ ์๊ท๋จผํธ
SPRING_APPLICATION_JSON
(ํ๊ฒฝ ๋ณ์ ๋๋ ์์คํ ํ๋กํผํฐ)์ ๋ค์ด์๋ ํ๋กํผํฐServletConfig
ํ๋ผ๋ฏธํฐServletContext
ํ๋ผ๋ฏธํฐ- Java:comp/env JNDI ์ ํธ๋ฆฌ๋ทฐํธ
System.getProperties()
์๋ฐ ์์คํ ํ๋กํผํฐ- OS ํ๊ฒฝ ๋ณ์
RandomValuePropertySource
- JAR ๋ฐ์ ์๋ ํน์ ํ๋กํ์ผ์ฉ
application.properties
- JAR ์์ ์๋ ํน์ ํ๋กํ์ผ์ฉ
application.properties
- JAR ๋ฐ์ ์๋
application.properties
- JAR ์์ ์๋
application.properties
@PropertySource
- ๊ธฐ๋ณธ ํ๋กํผํฐ(
SpringApplication.setDefaultProperties
)
application.properties
์ฐ์ ์์(๋์๊ฒ ๋ฎ์๊ฑธ ๋ฎ์ด ์ด๋ค.)
file:./config/
file:./
classpath:/config/
classpath:/
properties
์์ ๋๋ค๊ฐ ์ค์ ํ๊ธฐ
${random.์๋ฃํ}
server.port
์ ๊ฒฝ์ฐ 0์ ํ ๋นํด์ผ ๊ฐ์ฉ๋ฒ์ ์์ ํฌํธ๋ฅผ ์ฐพ์์ ๋งตํํด์ค
์ปค๋ฐ ๋ก๊ทธ
ํ์
-์ธ์ดํ๋ผ๋ ์๋ฏธ๋ @Value("${namjune.name}")
๊ณผ ๊ฐ์ด ์ง์ ํ๋กํผํฐ ๊ฐ์ ์
๋ ฅํด์ ๋ฐ์ํ ์ ์๋ ์๋ฌ๋ฅผ ๋ด์ง ์์ ์ ์๋ค๋ ์๋ฏธ์ด๋ค. @ConfigurationProperties
์ผ๋ก ์ ์ํ๊ณ ๋น์ผ๋ก ๋ง๋ ๋ค getter๋ฅผ ํตํด์ ๊ฐ์ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ @Value๋ก ์ง์ ์ฐ๋ ๊ฒ ๋ณด๋ค ์์ ํ๊ฒ ์ฌ์ฉํ ์์๋ค.
ํ๋กํผํฐ์ค ํ์ผ ์์ฒด๊ฐ ํ์ -์ธ์ดํ ํ๋ค๋ ์๋ฏธ๋ ์๋๋ค.
ํ์
-์ธ์ดํ ํ๋กํผํฐ @ConfigurationProperties
- ์ฌ๋ฌ ํ๋กํผํฐ๋ฅผ ๋ฌถ์ด์ ์ฝ์ด์ฌ ์ ์์
- ๋น์ผ๋ก ๋ฑ๋กํด์ ๋ค๋ฅธ ๋น์ ์ฃผ์
ํ ์ ์์
@EnableConfigurationProperties
@Component
@Bean
- ์คํ๋ง๋ถํธ ์ ํ๋ฆฌ์ผ์ด์
์์๋
@EnableConfigurationProperties
์ด ๋ฑ๋ก์ด ๋์ด ์์ผ๋ฏ๋ก@ConfigurationProperties
๊ฐ ์ ์ธ๋์ด์๋ ํด๋์ค์@Component
๋ฅผ ์ถ๊ฐํ์ฌ ๋น์ผ๋ก ๋ง๋ค์ด ์ฃผ๊ธฐ๋ง ํ๋ฉด ๋๋ค.
- ์ตํต์ฑ ์๋ ๋ฐ์ธ๋ฉ(
RelaxedBinding
)context-path
(์ผ๋ฐฅ)context_path
(์ธ๋์ค์ฝ์ด)contextPath
(์นด๋ฉ)CONTEXTPATH
- ํ๋กํผํฐ ํ์
์ปจ๋ฒ์
- ํ๋กํผํฐ ํ์ผ์ txt๊ฐ ๋ฌธ์๋ก ์ ๋ ฅ๋์ง๋ง, int๋ก ์ปจ๋ฒ์ ๋์ด์ ๋ค์ด๊ฐ๋ค.
@DurationUnit
- ์๊ฐ์ ๋ณด๋ฅผ ๋ฐ๊ณ ์ถ์ ๋ ์ฌ์ฉํ๋ฉด ์ปจ๋ฒ์ ์ด ์ด๋ฃจ์ด ์ง๋ค.
- .
AppProperties.java
public class AppProperties {
// ...
@DurationUnit(ChronoUnit.SECONDS)
private Duration sessionTimeout = Duration.ofSeconds(30);
// ...
}
.
application.yaml
nj:
name: namjune
age: ${random.int(0,100)}
fullName: ${nj.name} Kim
sessionTimeout: 30
==========================
namjune
64
namjune Kim
PT30S
==========================
ํ๋กํผํฐ ๊ฐ ๊ฒ์ฆ
- ํ๋กํผํฐ ๊ฐ์ ๊ฒ์ฆํ๊ณ ์ถ์๋,
@Validated
์ ๋ ธํ ์ด์ ์ ์ ์ํ๊ณ JSR303 ๊ตฌํ์ฒด์ธhibernate-validator
์ ๋ ธํ ์ด์ ์ ์ฌ์ฉํด์ ๊ฒ์ฆํ๋ค. @Validated
- JSR-303(
@NotNull
, ...) ๊ตฌํ์ฒด =hibernate-validator
- ๋ฉํ ์ ๋ณด ์์ฑ
@Value
์์๋- SpEL ์ ์ฌ์ฉํ ์ ์์ง๋ง..
- ์์ ์๋ ๊ธฐ๋ฅ๋ค์ ์ ๋ถ ์ฌ์ฉ ๋ชปํ๋ค.
ํ๋กํ์ผ
์ด๋ค ํน์ ํ ํ๋กํ์ผ์์๋ง ํน์ ํ ๋น์ ๋ฑ๋กํ๊ณ ์ถ๊ฑฐ๋, ํน์ ํ๋กํ์ผ์์๋ง ์ ํ๋ฆฌ์ผ์ด์ ์ ๋์์ ๋ค๋ฅด๊ฒ ํ๊ณ ์ถ์๋ ํ๋กํ์ผ์ ์ฌ์ฉํ์๋ค.
@Profile
์ ๋ ธํ ์ด์ ์ ์ด๋์?@Configuration
@Component
- ์ด๋ค ํ๋กํ์ผ์ ํ์ฑํ ํ ๊ฒ์ธ๊ฐ?
spring.profiles.active
- ์ด๋ค ํ๋กํ์ผ์ ์ถ๊ฐํ ๊ฒ์ธ๊ฐ?
- ํ๋กํ์ผ์์ ํน์ ํ๋กํ์ผ์ด ์ ์๋ ํ๋กํผํฐ ํ์ผ์ ์ธํด๋ฃจ๋ ํด์ ์ฌ์ฉํ ์ ์๋ค.
spring.profiles.include
- ํ๋กํ์ผ์ฉ ํ๋กํผํฐ
- .
application-{profile}.properties
- .
๋ก๊น
- ์คํ๋ง ๋ถํธ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ก๊น
ํ์ฌ๋ Commons Logging ์ ์ฌ์ฉํ๋ค. ๊ฒฐ๊ตญ SLF4j๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ค. ์์ค์ฝ๋์์๋ SLF4j๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
- ๋ก๊น ํ์ฌ๋๋ ์ค์ ๋ก๊น ์ ํ์ง ์๊ณ , ๋ก๊ฑฐ API๋ค์ ์ถ์ํ ํด๋์ ์ธํฐํ์ด์ค๋ค์ด๋ค.
- ์ฃผ๋ก ํ๋ ์์ํฌ๋ค์ ๋ก๊น ํ์ฌ๋๋ฅผ ์ด์ฉํ๋ค. ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๋ ์ ํ๋ฆฌ์ผ์ด์ ๋ค์ ๋ก๊ฑฐ ์ฌ์ฉ์ ์์ ๋กญ๊ฒ ํด์ฃผ๊ธฐ ์ํด์.
- ๋ก๊น
ํ์ฌ๋์ ์ฅ์ ์ ๋ก๊ฑฐ๋ค์ ๋ฐ๊ฟ์ ์ฌ์ฉํ ์ ์๋ค๋ ๊ฒ์ด๋ค.
- JUL(Java Utility Logging), Log4J2, Logback
- ์ ๋ฆฌํ์๋ฉด ์คํ๋ง๋ถํธ์์ ์ฐํ๋ ๋ก๊ทธ๋ Commons Logging -> SLF4j -> Logback์ ํ๋ฆ์ ํ๊ณ ๊ฒฐ๊ตญ __Logback__์ ์ํด์ ์ฐํ๋ค.
- ์๋์
spring-boot-stater-logging
์์กด์ฑ์ ํตํด์ ํ์ธํ ์ ์๋ค.jul-to-slf4j
๋ผ์ด๋ธ๋ฌ๋ฆฌ์log4j-to-slf4j
๋ฅผ ํตํด์slf4j
๋ก ๋ก๊ทธ๋ฅผ ๋ณด๋ด๊ณ ,slf4j-api
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํตํด์ ๋ฐ์ ๋ก๊ทธ๋ค์ ๊ฒฐ๊ตญ logback์ผ๋ก ์ฒ๋ฆฌํ๋ค.
์คํ๋ง ๋ถํธ ๊ธฐ๋ณธ ๋ก๊น
--debug
: ์ผ๋ถ ์ฝ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ(embedded container, Hibernate, Spring Boot)๋ง ๋๋ฒ๊น ๋ชจ๋๋ก--trace
: ์ ๋ถ ๋ค ๋ ๋ฒ๊น ๋ชจ๋๋ก- ์ปฌ๋ฌ ์ถ๋ ฅ:
spring.output.ansi.enabled
- ํ์ผ ์ถ๋ ฅ
logging.file
๋๋logging.path
- ๋ก๊ทธํ์ผ์ ๊ธฐ๋ณธ์ ์ผ๋ก 10M๊น์ง ์ ์ฅ๋๊ณ , ๋์น๋ฉด ์์นด์ด๋นํ๋ ๋ฑ ์ฌ๋ฌ๊ฐ์ง ์ค์ ๋ ํ ์ ์๋ค.
- ๋ก๊ทธ ๋ ๋ฒจ ์กฐ์ :
logging.level.ํจํค์ง
= ๋ก๊ทธ ๋ ๋ฒจ
์ปค์คํ ๋ก๊ทธ ์ค์ ํ์ผ ์ฌ์ฉํ๊ธฐ
- Logback:
logback-spring.xml
- https://docs.spring.io/spring-boot/docs/current/reference/html/howto-logging.html#howto-configure-logback-for-logging
- Log4J2:
log4j2-spring.xml
- JUL(๋น์ถ์ฒ):
logging.properties
- Logback extension
logback-spring.xml
์ ์ฌ์ฉํ๋ฉดlogback.xml
์ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๊ฐ๊ณ , ์คํ๋ง๋ถํธ์์ ์ถ๊ฐ๋ก ์๋์ ์ต์คํ ์ ์ ์ฌ์ฉํ ์ ์๊ฒ ์ ๊ณตํ๋ค.- ํ๋กํ์ผ
<springProfile name="ํ๋กํ์ผ"\>
- Environment ํ๋กํผํฐ
<springProperty\>
๋ก๊ฑฐ๋ฅผ Log4j2๋ก ๋ณ๊ฒฝํ๊ธฐ
ํ ์คํธ
- ์์์ ์ผ๋จ
spring-boot-starter-test
๋ฅผ ์ถ๊ฐํ๋ ๊ฒ ๋ถํฐ- test scope์ผ๋ก ์ถ๊ฐ
@SpringBootTest
@SpringBootTest
๊ฐ ํ๋ ์ญํ ์@SpringBootApplication
์ ์ฐพ์์ ํ ์คํธ๋ฅผ ์ํ ๋น๋ค์ ๋ค ์์ฑํ๋ค. ๊ทธ๋ฆฌ๊ณ@MockBean
์ผ๋ก ์ ์๋ ๋น์ ์ฐพ์์ ๊ต์ฒดํ๋ค.@RunWith(SpringRunner.class)
๋ ๊ฐ์ด ์จ์ผ ํจ- ๋น ์ค์ ํ์ผ์ ์ํด์ฃผ๋? ์์์ ์ฐพ๋๋ค. (
@SpringBootApplication
)
SpringBootTest.webEnvironment
MOCK: mock servlet environment. ๋ด์ฅ ํฐ์บฃ ๊ตฌ๋ ์ํจ.
package io.namjune.springbootconceptandutilization.sample;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
public class SampleControllerTest {
@Autowired
MockMvc mockMvc;
@Test
public void hello() throws Exception {
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("hello namjune"))
.andDo(print());
}
}
RANDOM_PORT
,DEFINED_PORT
: ๋ด์ฅ ํฐ์บฃ ์ฌ์ฉ ํจRANDOM_PORT
๋ฅผ ์ฌ์ฉํ๋ฉด ์ค์ ๋ด์ฅ ํฐ์บฃ์ ์ฌ์ฉํ๋ค. ์ด๋๋MockMvc
๋์RestTemplate
๋ฅผ ์ฌ์ฉํ ์ ์๋ค.- ์ค์ ๊ฐ์ฉํ ํฌํธ๋ก ๋ด์ฅํฐ์บฃ์ ๋์ฐ๊ณ ์๋ต์ ๋ฐ์์ ํ ์คํธ๋ฅผ ์ํํ๋ค.
์ปค๋ฐ๋ก๊ทธ
package io.namjune.springbootconceptandutilization.sample;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SampleControllerTest {
@Autowired
TestRestTemplate testRestTemplate;
@Test
public void hello() {
String result = testRestTemplate.getForObject("/hello", String.class);
assertThat(result).isEqualTo("hello namjune");
}
}
- spring5 webflux์์ ์ถ๊ฐ๋
RestClient
์ค์ ํ๋์ธWebTestClient
๋ ์ฌ์ฉํ ์ ์๋ค. ๊ธฐ์กด์ ์ฌ์ฉํ๋WebClient
๋ synchronousํ๊ฒ ๋์ํ๊ธฐ ๋๋ฌธ์ ์์ฒญ ํ๋ ๋ณด๋ด๊ณ ๊ทธ ์์ฒญ์ด ๋๋๊ณ ๋ ๋ค์์ ๋ค์ ์์ฒญ์ ๋ณด๋ผ ์ ์์์ง๋ง,WebTestClient
๋ asynchronousํ๊ฒ ๋์ํ๋ฏ๋ก ์์ฒญ์ ๋ณด๋ด๊ณ ๊ธฐ๋ค๋ฆฌ์ง ์๋๋ค. ํ์ ์๋ต์ด ์ค๋ฉด, ์ฝ๋ฐฑ ์ด๋ฒคํธ๋ฅผ ์คํํ ์ ์๋ค. ๋ฐ๋ผ์, Test์ฝ๋์์๋WebClient
์ ๋น์ทํ API๋ฅผ ์ฌ์ฉํ ์ ์๋ค.- webflux ์์กด์ฑ์ ์ถ๊ฐํด์ผ ํ๋ค.
- API๊ฐ restTemplate๋ณด๋ค ๊ฐ๋ ์ฑ์ด ์ข๋ค.(์ถ์ฒ)
- ์๋์ @MockBean ์ฐธ๊ณ
์ปค๋ฐ๋ก๊ทธ
implementation('org.springframework.boot:spring-boot-starter-webflux')
// ...
package io.namjune.springbootconceptandutilization.sample;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SampleControllerTest {
@Autowired
WebTestClient webTestClient;
@MockBean
SampleService mockSampleService;
@Test
public void hello() {
when(mockSampleService.getName()).thenReturn("kim");
webTestClient.get().uri("/hello").exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("hello kim");
}
}
NONE: ์๋ธ๋ฆฟ ํ๊ฒฝ ์ ๊ณต ์ ํจ.
@MockBean
- ์์ ๊ฒฝ์ฐ ํ
์คํธ๊ฐ ๋๋ฌด ํฌ๋ค. Controller ํ
์คํธ์ฝ๋์์ Service๋จ๊น์ง ํ๋ฌ๊ฐ๋ค. ์ปจํธ๋กค๋ฌ๋ง ํ
์คํธํ๊ณ ์ถ์ ๊ฒฝ์ฐ ์๋น์ค ๊ฐ์ฒด๋ฅผ
MockBean
์ผ๋ก ๋ง๋ค์ด์ ์ฌ์ฉํ ์ ์๋ค. ApplicationContext
์ ๋ค์ด์๋ ๋น์ Mock์ผ๋ก ๋ง๋ ๊ฐ์ฒด๋ก ๊ต์ฒดํจ- ๋ชจ๋
@Test
๋ง๋ค ์๋์ผ๋ก ๋ฆฌ์ . ์ง์ ๋ฆฌ์ ์ ๊ด๋ฆฌ ํ์ง ์์๋ ๋๋ค.
package io.namjune.springbootconceptandutilization.sample;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SampleControllerTest {
@Autowired
TestRestTemplate testRestTemplate;
@MockBean
SampleService mockSampleService;
@Test
public void hello() {
when(mockSampleService.getName()).thenReturn("kim");
String result = testRestTemplate.getForObject("/hello", String.class);
assertThat(result).isEqualTo("hello kim");
}
}
์ฌ๋ผ์ด์ค ํ ์คํธ
๋ ์ด์ด ๋ณ๋ก ์๋ผ์ ํ ์คํธ ํ๊ณ ์ถ์ ๋
@JsonTest
- json ํ
์คํธ๋ฅผ ํ๊ณ ์ถ์ ๊ฒฝ์ฐ
@SpringBootTest
๋์@JsonTest
๋ฅผ ์ ์ธํ๊ณ ,JacksonTester
๋ฅผ ์ฃผ์ ๋ฐ์์ ์ฌ์ฉํ๋ฉด ๋๋ค.
- json ํ
์คํธ๋ฅผ ํ๊ณ ์ถ์ ๊ฒฝ์ฐ
@WebMvcTest
์ปจํธ๋กค๋ฌ๋ง ๋ฐ๋ก ํ ์คํธ ํ ๊ฒฝ์ฐ ์ฌ์ฉํ๋ค. ์น๊ณผ ๊ด๋ จ๋ ํด๋์ค๋ค๋ง ๋น์ผ๋ก ๋ฑ๋ก์ด ๋๊ณ ์ผ๋ฐ์ ์ธ ์ปดํฌ๋ํธ๋ค์ ๋น์ผ๋ก ๋ฑ๋ก์ด ๋์ง ์๋๋ค. ์ด๋ ๊ฒ ์์กด์ฑ์ด ๋๊ธฐ๊ธฐ ๋๋ฌธ์, ์ฌ์ฉํ๋ ๋ค๋ฅธ ์์กด์ฑ ์๋ฅผ ๋ค๋ฉด ์๋น์ค์ ๊ฐ์ ๊ฐ์ฒด๋ค์@MockBean
์ ์ฌ์ฉํด์ ๋ง๋ค์ด ์ฌ์ฉํด์ผ ํ๋ค.@WebFluxTest
@DataJpaTest
- ...
์ปค๋ฐ๋ก๊ทธ
- https://github.com/namjunemy/spring-boot-concept-and-utilization/commit/76a0e095576f0b52b23f2cec3f60f050d1bb2042
ํ ์คํธ ์ ํธ
์คํ๋ง ํ ์คํธ๊ฐ ์ ๊ณตํ๋ ํ ์คํธ ์ ํธ๋ฆฌํฐ๊ฐ 4๊ฐ์ง ์๋ค.
- OutputCapture
TestPropertyValues
TestRestTemplate
ConfigFileApplicationContextInitializer
- Junit์ ์๋ Rule์ ํ์ฅํด์ ๋ง๋ OutputCapture๊ฐ ์ ์ผ ๋ง์ด ์ฐ์ธ๋ค.
- OutputCapture๋ ๋ก๊ทธ๋ฅผ ๋น๋กฏํด์ ์ฝ์์ ์ฐํ๋ ๋ชจ๋ ๊ฒ์ ์บก์ณํ๋ค.
- ๋ก๊ทธ ๋ฉ์ธ์ง๊ฐ ์ด๋ป๊ฒ ์ฐํ๋์ง ํ ์คํธํ ์ ์๋ค.
- @Rule์ ์ ์ธํ๊ณ ,
- Junit์ด ์ ๊ณตํ๋ OutputCapture๋ฅผ public์ผ๋ก ๋ง๋ ๋ค.(@Rule์ ์ ์ฝ์ฌํญ. ๋น์ ์ฃผ์ ๋ฐ๋๊ฒ ์๋)
- OutputCapture๋ ๋ก๊ทธ๋ฅผ ๋น๋กฏํด์ ์ฝ์์ ์ฐํ๋ ๋ชจ๋ ๊ฒ์ ์บก์ณํ๋ค.
@RestController
@RequiredArgsConstructor
public class SampleController {
Logger logger = LoggerFactory.getLogger(SampleController.class);
private final SampleService sampleService;
@GetMapping("/hello")
public String hello() {
logger.info("hello logger");
System.out.println("hello sout");
return "hello " + sampleService.getName();
}
}
@RunWith(SpringRunner.class)
@WebMvcTest(SampleController.class)
public class SampleControllerTest {
@Rule
public OutputCapture outputCapture = new OutputCapture();
@MockBean
SampleService mockSampleService;
@Autowired
MockMvc mockMvc;
@Test
public void hello() throws Exception {
when(mockSampleService.getName()).thenReturn("kim");
mockMvc.perform(get("/hello"))
.andExpect(content().string("hello kim"));
assertThat(outputCapture.toString())
.contains("hello")
.contains("sout");
}
}