반응형
Embedded 방식으로 사용하기
build.gradle.kts
dependencies {
// ...
// https://central.sonatype.com/artifact/org.wiremock/wiremock-standalone
implementation("org.wiremock:wiremock-standalone:3.9.1")
}
WireMockConfig
@Configuration
public class WireMockConfig {
private final Logger log = LoggerFactory.getLogger(WireMockConfig.class);
@Bean(initMethod = "start", destroyMethod = "stop")
public WireMockServer wireMockServer() throws IOException {
log.info("[WIRE_MOCK_CONFIG] start");
WireMockServer wireMockServer = new WireMockServer(
WireMockConfiguration.options()
.disableRequestJournal()
.asynchronousResponseEnabled(true)
.port(19090)
);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("wiremock/**/*.json");
for (Resource resource : resources) {
log.info("[WIRE_MOCK_CONFIG] jsonFilePath : {}", resource.getURL().getPath());
wireMockServer.addStubMapping(StubMapping.buildFrom(IOUtils.toString(resource.getInputStream())));
}
log.info("[WIRE_MOCK_CONFIG] finish");
return wireMockServer;
}
}
Mocking
JavaConfig를 활용한 방법
@RequiredArgsConstructor
@Configuration
public class DemoApiMocking {
private final WireMockServer wireMockServer;
@PostConstruct
public void mocking() {
wireMockServer.stubFor(
WireMock.post(WireMock.urlPathMatching("/api/demo/(.*?)"))
.withRequestBody(WireMock.equalToJson("{\"name\": \"john\"}"))
.willReturn(
WireMock.aResponse()
.withStatus(200)
.withHeader("Content-Type", MediaTypes.APPLICATION_JSON)
.withBody("{\"message\": \"Hi, john\"}")
)
);
}
}
json 파일을 활용한 방법
- 경로 : ~/src/main/resources/wiremock/demo-api-mocking.json
{ "request": { "urlPathPattern": "/api/demo/(.*?)", "method": "POST", "bodyPatterns": [ { "equalToJson": { "name": "john" } } ] }, "response": { "status": 200, "headers": { "Content-Type": "application/json" }, "jsonBody": { "message": "Hi, john" } } }
Test
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> result = restTemplate.exchange("http://localhost:19090/api/demo/john", HttpMethod.POST, new HttpEntity<>("{\"name\": \"john\"}"), String.class);
String body = result.getBody();
System.out.println(body); // {"message": "Hi, john"}
참고
- 요청 매칭 예시 : https://wiremock.org/docs/request-matching
테스트 케이스에서 사용하기
src/test/resources/application-unittest.properties
demo.url=http://localhost:${wiremock.server.port}
Java 코드로 모킹하여 테스트
WireMockTest
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWireMock(port = 0)
@ActiveProfiles("unittest")
public class WireMockTest {
@Value("${demo.url}")
private String demoUrl;
@Test
public void demo() throws Exception {
// WireMock.stubFor(
// WireMock.get(WireMock.urlPathEqualTo("/api/v1/demo"))
// .willReturn(WireMock.aResponse()
// .withStatus(HttpStatus.OK.value())
// .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
// .withBody(Files.readString(ResourceUtils.getFile("classpath:payload/demo-body.json").toPath()))
// )
// );
WireMock.stubFor(
WireMock.get(WireMock.urlPathEqualTo("/api/v1/demo"))
.willReturn(WireMock.aResponse()
.withStatus(HttpStatus.OK.value())
.withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.withBody("{\"name\":\"tyler\", \"age\": 50}")
)
);
RestTemplate restTemplate = new RestTemplate();
Map result = restTemplate.getForObject(demoUrl + "/api/v1/demo", Map.class);
System.out.println(result.get("name")); // tyler
System.out.println(result.get("age")); // 50
}
}
json 파일로 모킹하여 테스트
src/test/resources/stubs/demo-v1.json
{
"request": {
"method": "GET",
"url": "/api/v1/demo"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"name": "tyler",
"age": 50
}
}
}
WireMockTest
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWireMock(port = 0, stubs = "classpath:/stubs")
@ActiveProfiles("unittest")
public class WireMockTest {
@Value("${demo.url}")
private String demoUrl;
@Test
public void demo() {
RestTemplate restTemplate = new RestTemplate();
Map result = restTemplate.getForObject(demoUrl + "/api/v1/demo", Map.class);
System.out.println(result.get("name")); // tyler
System.out.println(result.get("age")); // 50
}
}
주요 기능
request body 매칭(json)
demo.json
{
"request": {
"urlPathPattern": "/api/demo/(.*?)",
"method": "POST",
"bodyPatterns": [
{
"matchesJsonPath": "$.student.name"
}
]
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"message": "Hi, john"
}
}
}
command
curl -X POST --location "http://localhost:19090/api/demo/abcde" \
-H "Content-Type: application/json" \
-d "{ \"student\": { \"name\": \"john\" } }"
request header 매칭
demo.json
{
"request": {
"urlPathPattern": "/api/demo/(.*?)",
"method": "POST",
"headers": {
"X-TX-ID": {
"equalTo": "tx-1"
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"message": "Hi, john"
}
}
}
command
curl -X POST --location "http://localhost:19090/api/demo/abcde" \
-H "Content-Type: application/json" \
-H "X-TX-ID: tx-1"
request header 매칭 (or 조건)
demo.json
{
"request": {
"urlPathPattern": "/api/demo/(.*?)",
"method": "POST",
"headers": {
"X-TX-ID": {
"or": [
{
"equalTo": "tx-1"
},
{
"equalTo": "tx-2"
}
]
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"message": "Hi, john"
}
}
}
command
curl -X POST --location "http://localhost:19090/api/demo/abcde" \
-H "Content-Type: application/json" \
-H "x-tx-id: tx-2"
response 랜덤값 응답하기
WireMockConfig
WireMockConfiguration.options()
// ...
.globalTemplating(true) // 추가
demo.json
{
"request": {
"urlPathPattern": "/api/demo/(.*?)",
"method": "POST"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"value1": "{{randomInt lower=5 upper=9}}",
"value2": "{{randomDecimal lower=-5.0 upper=5.0}}",
"value3": "{{pickRandom '1' '2' '3'}}"
}
}
}
proxy 기능 사용하기
demo.json
{
"request": {
"urlPattern": "/proxy-demo/(.*?)"
},
"response": {
"proxyBaseUrl": "https://jsonplaceholder.typicode.com",
"proxyUrlPrefixToRemove": "/proxy-demo"
}
}
command
curl -X GET --location "http://localhost:19090/proxy-demo/todos/1"
request 조작하기
DemoRequestFilter
public class DemoRequestFilter extends StubRequestFilter {
@Override
public RequestFilterAction filter(Request request) {
if (request.getUrl().startsWith("/proxy-demo/posts/")) {
Request newRequest = RequestWrapper.create()
.transformAbsoluteUrl(url -> url.replace("/posts/1", "/posts/2"))
.transformBody(body -> new Body(body.asString().replace("john", "tom")))
.wrap(request);
return RequestFilterAction.continueWith(newRequest);
}
return RequestFilterAction.continueWith(request);
}
@Override
public String getName() {
return "demo-filter";
}
}
WireMockConfig
WireMockServer wireMockServer = new WireMockServer(
WireMockConfiguration.options()
.extensions(new DemoRequestFilter())
// ...
);
demo.json
{
"request": {
"urlPattern": "/proxy-demo/(.*?)"
},
"response": {
"proxyBaseUrl": "https://jsonplaceholder.typicode.com",
"proxyUrlPrefixToRemove": "/proxy-demo"
}
}
command
curl -X PATCH --location "http://localhost:19090/proxy-demo/posts/1" \
-H "Content-Type: application/json" \
-d "{\"title\": \"Hi, john\"}"
response 조작하기
DemoResponseTransformer
public class DemoResponseTransformer extends ResponseTransformer {
@Override
public Response transform(Request request, Response response, FileSource files, Parameters parameters) {
String body = response.getBodyAsString().replace("john", "tom");
return Response.Builder.like(response)
.body(body)
.build();
}
@Override
public String getName() {
return "demo-transformer";
}
@Override
public boolean applyGlobally() {
return false;
}
}
WireMockConfig
WireMockServer wireMockServer = new WireMockServer(
WireMockConfiguration.options()
.extensions(new DemoResponseTransformer())
// ...
);
demo.json
{
"request": {
"urlPathPattern": "/api/demo/(.*?)"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"message": "Hi, john"
},
"transformers": ["demo-transformer"]
}
}
command
curl -X GET --location "http://localhost:19090/api/demo/1234"
반응형
'Development > Spring' 카테고리의 다른 글
[Spring] Elasticsearch (0) | 2024.01.01 |
---|---|
[Spring] 헥사고날 아키텍처(Hexagonal Architecture) (2) | 2023.11.23 |
[Spring] SpEL (0) | 2023.08.09 |
[Spring] actuator (0) | 2021.11.27 |
[Spring] Scheduler Lock (0) | 2021.11.25 |