From 4b0dd81725538751c252dfd1a594cfe0c9099f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A4=80=ED=98=81?= Date: Sat, 8 Nov 2025 13:50:02 +0900 Subject: [PATCH] feat(wiremock): add partial body matching for POST/PUT/PATCH requests (#776) --- .../stove/testing/e2e/http/HttpSystemTests.kt | 87 +++++++++++++++++++ .../testing/e2e/wiremock/WireMockSystem.kt | 78 +++++++++++++++++ 2 files changed, 165 insertions(+) diff --git a/lib/stove-testing-e2e-http/src/test/kotlin/com/trendyol/stove/testing/e2e/http/HttpSystemTests.kt b/lib/stove-testing-e2e-http/src/test/kotlin/com/trendyol/stove/testing/e2e/http/HttpSystemTests.kt index e70a1e4f2..86d9f4fe8 100644 --- a/lib/stove-testing-e2e-http/src/test/kotlin/com/trendyol/stove/testing/e2e/http/HttpSystemTests.kt +++ b/lib/stove-testing-e2e-http/src/test/kotlin/com/trendyol/stove/testing/e2e/http/HttpSystemTests.kt @@ -285,6 +285,93 @@ class HttpSystemTests : } } + test("should match POST request with partial body containing specific fields") { + val aUniqueIdentifierForTest = 123 + val aProductCategoryForTest = "food" + val expectedPostDtoName = UUID.randomUUID().toString() + + TestSystem.validate { + wiremock { + mockPostRequestContaining( + url = "/post-with-request-containing", + requestContaining = mapOf("productId" to aUniqueIdentifierForTest), + statusCode = 200, + responseBody = TestDto(expectedPostDtoName).some() + ) + } + + http { + postAndExpectBody( + uri = "/post-with-request-containing", + body = mapOf( + "productId" to aUniqueIdentifierForTest, + "productCategory" to aProductCategoryForTest + ).some() + ) { actual -> + actual.body().name shouldBe expectedPostDtoName + } + } + } + } + + test("should match PUT request with partial body containing specific fields") { + val aUniqueIdentifierForTest = 123 + val aProductCategoryForTest = "food" + val expectedPutDtoName = UUID.randomUUID().toString() + + TestSystem.validate { + wiremock { + mockPutRequestContaining( + url = "/put-with-request-containing", + requestContaining = mapOf("productId" to aUniqueIdentifierForTest), + statusCode = 200, + responseBody = TestDto(expectedPutDtoName).some() + ) + } + + http { + putAndExpectBody( + uri = "/put-with-request-containing", + body = mapOf( + "productId" to aUniqueIdentifierForTest, + "productCategory" to aProductCategoryForTest + ).some() + ) { actual -> + actual.body().name shouldBe expectedPutDtoName + } + } + } + } + + test("should match PATCH request with partial body containing specific fields") { + val aUniqueIdentifierForTest = 123 + val aProductCategoryForTest = "food" + val expectedPatchDtoName = UUID.randomUUID().toString() + + TestSystem.validate { + wiremock { + mockPatchRequestContaining( + url = "/patch-with-request-containing", + requestContaining = mapOf("productId" to aUniqueIdentifierForTest), + statusCode = 200, + responseBody = TestDto(expectedPatchDtoName).some() + ) + } + + http { + patchAndExpectBody( + uri = "/patch-with-request-containing", + body = mapOf( + "productId" to aUniqueIdentifierForTest, + "productCategory" to aProductCategoryForTest + ).some() + ) { actual -> + actual.body().name shouldBe expectedPatchDtoName + } + } + } + } + test("java time instant should work") { val expectedGetDtoName = UUID.randomUUID().toString() TestSystem.validate { diff --git a/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockSystem.kt b/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockSystem.kt index 4093c58f1..d7368a392 100644 --- a/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockSystem.kt +++ b/lib/stove-testing-e2e-wiremock/src/main/kotlin/com/trendyol/stove/testing/e2e/wiremock/WireMockSystem.kt @@ -218,6 +218,84 @@ class WireMockSystem( return this } + @WiremockDsl + fun mockPostRequestContaining( + url: String, + requestContaining: Map, + statusCode: Int = 200, + responseBody: Option = None, + urlPatternFn: (url: String) -> UrlPattern = { urlEqualTo(it) } + ): WireMockSystem = mockPostConfigure(url, urlPatternFn) { builder, serde -> + + requestContaining.forEach { (key, value) -> + builder.withRequestBody( + matchingJsonPath( + "$.$key", + equalToJson(String(serde.serialize(value))) + ) + ) + } + + val response = aResponse() + .withStatus(statusCode) + .withHeader("Content-Type", "application/json; charset=UTF-8") + .also { rb -> responseBody.map { rb.withBody(serde.serialize(it)) } } + + builder.willReturn(response) + } + + @WiremockDsl + fun mockPutRequestContaining( + url: String, + requestContaining: Map, + statusCode: Int = 200, + responseBody: Option = None, + urlPatternFn: (url: String) -> UrlPattern = { urlEqualTo(it) } + ): WireMockSystem = mockPutConfigure(url, urlPatternFn) { builder, serde -> + + requestContaining.forEach { (key, value) -> + builder.withRequestBody( + matchingJsonPath( + "$.$key", + equalToJson(String(serde.serialize(value))) + ) + ) + } + + val response = aResponse() + .withStatus(statusCode) + .withHeader("Content-Type", "application/json; charset=UTF-8") + .also { rb -> responseBody.map { rb.withBody(serde.serialize(it)) } } + + builder.willReturn(response) + } + + @WiremockDsl + fun mockPatchRequestContaining( + url: String, + requestContaining: Map, + statusCode: Int = 200, + responseBody: Option = None, + urlPatternFn: (url: String) -> UrlPattern = { urlEqualTo(it) } + ): WireMockSystem = mockPatchConfigure(url, urlPatternFn) { builder, serde -> + + requestContaining.forEach { (key, value) -> + builder.withRequestBody( + matchingJsonPath( + "$.$key", + equalToJson(String(serde.serialize(value))) + ) + ) + } + + val response = aResponse() + .withStatus(statusCode) + .withHeader("Content-Type", "application/json; charset=UTF-8") + .also { rb -> responseBody.map { rb.withBody(serde.serialize(it)) } } + + builder.willReturn(response) + } + @WiremockDsl fun behaviourFor( url: String,