-
-
Notifications
You must be signed in to change notification settings - Fork 481
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added support for WebFlux with Spring JUnit 5 tests #1373
- Loading branch information
Ronald Holshausen
committed
Jun 16, 2021
1 parent
4abd4ff
commit d81b2b8
Showing
8 changed files
with
250 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
126 changes: 126 additions & 0 deletions
126
...der/junit5spring/src/main/kotlin/au/com/dius/pact/provider/spring/junit5/WebFluxTarget.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package au.com.dius.pact.provider.spring.junit5 | ||
|
||
import au.com.dius.pact.core.model.ContentType | ||
import au.com.dius.pact.core.model.IRequest | ||
import au.com.dius.pact.core.model.Interaction | ||
import au.com.dius.pact.core.model.PactSource | ||
import au.com.dius.pact.core.model.SynchronousRequestResponse | ||
import au.com.dius.pact.core.model.generators.GeneratorTestMode | ||
import au.com.dius.pact.provider.IProviderVerifier | ||
import au.com.dius.pact.provider.ProviderInfo | ||
import au.com.dius.pact.provider.ProviderResponse | ||
import au.com.dius.pact.provider.junit5.TestTarget | ||
import org.apache.commons.lang3.StringUtils | ||
import org.springframework.http.HttpHeaders | ||
import org.springframework.http.HttpMethod | ||
import org.springframework.http.MediaType | ||
import org.springframework.http.client.MultipartBodyBuilder | ||
import org.springframework.test.web.reactive.server.WebTestClient | ||
import org.springframework.web.reactive.function.BodyInserters | ||
import org.springframework.web.reactive.function.server.RouterFunction | ||
import org.springframework.web.util.UriComponentsBuilder | ||
import javax.mail.internet.ContentDisposition | ||
import javax.mail.internet.MimeMultipart | ||
import javax.mail.util.ByteArrayDataSource | ||
|
||
class WebFluxTarget(private val routerFunction: RouterFunction<*>) : TestTarget { | ||
override fun getProviderInfo(serviceName: String, pactSource: PactSource?) = ProviderInfo(serviceName) | ||
|
||
override fun prepareRequest(interaction: Interaction, context: MutableMap<String, Any>): Pair<WebTestClient.RequestHeadersSpec<*>, WebTestClient> { | ||
if (interaction is SynchronousRequestResponse) { | ||
val request = interaction.request.generatedRequest(context, GeneratorTestMode.Provider) | ||
val webClient = WebTestClient.bindToRouterFunction(routerFunction).build() | ||
return toWebFluxRequestBuilder(webClient, request) to webClient | ||
} | ||
throw UnsupportedOperationException("Only request/response interactions can be used with an MockMvc test target") | ||
} | ||
|
||
private fun toWebFluxRequestBuilder(webClient: WebTestClient, request: IRequest): WebTestClient.RequestHeadersSpec<*> { | ||
return if (request.body.isPresent()) { | ||
if (request.isMultipartFileUpload()) { | ||
val multipart = MimeMultipart(ByteArrayDataSource(request.body.unwrap(), request.contentTypeHeader())) | ||
|
||
val bodyBuilder = MultipartBodyBuilder() | ||
var i = 0 | ||
while (i < multipart.count) { | ||
val bodyPart = multipart.getBodyPart(i) | ||
val contentDisposition = ContentDisposition(bodyPart.getHeader("Content-Disposition").first()) | ||
val name = StringUtils.defaultString(contentDisposition.getParameter("name"), "file") | ||
val filename = contentDisposition.getParameter("filename").orEmpty() | ||
|
||
bodyBuilder | ||
.part(name, bodyPart.content) | ||
.filename(filename) | ||
.contentType(MediaType.valueOf(bodyPart.contentType)) | ||
.header("Content-Disposition", "form-data; name=$name; filename=$filename") | ||
|
||
i++ | ||
} | ||
|
||
webClient | ||
.method(HttpMethod.POST) | ||
.uri(requestUriString(request)) | ||
.body(BodyInserters.fromMultipartData(bodyBuilder.build())) | ||
.headers { request.headers.forEach { (k, v) -> it.addAll(k, v) } } | ||
} else { | ||
webClient | ||
.method(HttpMethod.valueOf(request.method)) | ||
.uri(requestUriString(request)) | ||
.bodyValue(request.body.value!!) | ||
.headers { | ||
request.headers.forEach { (k, v) -> it.addAll(k, v) } | ||
if (!request.headers.containsKey(HttpHeaders.CONTENT_TYPE)) { | ||
it.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) | ||
} | ||
} | ||
} | ||
} else { | ||
webClient | ||
.method(HttpMethod.valueOf(request.method)) | ||
.uri(requestUriString(request)) | ||
.headers { | ||
request.headers.forEach { (k, v) -> it.addAll(k, v) } | ||
} | ||
} | ||
} | ||
|
||
private fun requestUriString(request: IRequest): String { | ||
val uriBuilder = UriComponentsBuilder.fromPath(request.path) | ||
|
||
request.query.forEach { (key, value) -> | ||
uriBuilder.queryParam(key, value) | ||
} | ||
|
||
return uriBuilder.toUriString() | ||
} | ||
|
||
override fun isHttpTarget() = true | ||
|
||
override fun executeInteraction(client: Any?, request: Any?): ProviderResponse { | ||
val requestBuilder = request as WebTestClient.RequestHeadersSpec<*> | ||
val exchangeResult = requestBuilder.exchange().expectBody().returnResult() | ||
|
||
val headers = mutableMapOf<String, List<String>>() | ||
exchangeResult.responseHeaders.forEach { header -> | ||
headers[header.key] = header.value | ||
} | ||
|
||
val contentTypeHeader = exchangeResult.responseHeaders.contentType | ||
val contentType = if (contentTypeHeader == null) { | ||
ContentType.JSON | ||
} else { | ||
ContentType.fromString(contentTypeHeader.toString()) | ||
} | ||
|
||
return ProviderResponse( | ||
exchangeResult.status.value(), | ||
headers, | ||
contentType, | ||
exchangeResult.responseBody?.let { String(it) } | ||
) | ||
} | ||
|
||
override fun prepareVerifier(verifier: IProviderVerifier, testInstance: Any) { | ||
/* NO-OP */ | ||
} | ||
} |
82 changes: 82 additions & 0 deletions
82
...t5spring/src/test/groovy/au/com/dius/pact/provider/spring/junit5/WebFluxTargetSpec.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package au.com.dius.pact.provider.spring.junit5 | ||
|
||
import au.com.dius.pact.core.model.OptionalBody | ||
import au.com.dius.pact.core.model.Request | ||
import au.com.dius.pact.core.model.RequestResponseInteraction | ||
import org.springframework.http.MediaType | ||
import org.springframework.test.web.reactive.server.WebTestClient | ||
import org.springframework.web.reactive.function.BodyInserters | ||
import org.springframework.web.reactive.function.server.RequestPredicates | ||
import org.springframework.web.reactive.function.server.RouterFunction | ||
import org.springframework.web.reactive.function.server.RouterFunctions | ||
import org.springframework.web.reactive.function.server.ServerResponse | ||
import spock.lang.Specification | ||
|
||
import java.nio.charset.StandardCharsets | ||
|
||
@SuppressWarnings('ClosureAsLastMethodParameter') | ||
class WebFluxTargetSpec extends Specification { | ||
RouterFunction routerFunction = RouterFunctions.route(RequestPredicates.GET('/data'), { req -> | ||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) | ||
.body(BodyInserters.fromValue('{"id":1234}')) | ||
}) | ||
|
||
def 'should prepare get request'() { | ||
given: | ||
WebFluxTarget webFluxTarget = new WebFluxTarget(routerFunction) | ||
def request = new Request('GET', '/data', [id: ['1234']]) | ||
def interaction = new RequestResponseInteraction('some description', [], request) | ||
|
||
when: | ||
def requestAndClient = webFluxTarget.prepareRequest(interaction, [:]) | ||
def requestBuilder = requestAndClient.first | ||
def builtRequest = requestBuilder.exchange().expectBody().returnResult() | ||
|
||
then: | ||
requestBuilder instanceof WebTestClient.RequestHeadersSpec | ||
builtRequest.url.path == '/data' | ||
builtRequest.method.toString() == 'GET' | ||
new String(builtRequest.responseBody) == '{"id":1234}' | ||
} | ||
|
||
def 'should prepare post request'() { | ||
given: | ||
RouterFunction postRouterFunction = RouterFunctions.route(RequestPredicates.POST('/data'), { req -> | ||
assert req.queryParams() == [id: ['1234']] | ||
def reqBody = req.bodyToMono(String).doOnNext({ s -> assert s == '{"foo":"bar"}' }) | ||
ServerResponse.ok().build(reqBody) | ||
}) | ||
WebFluxTarget webFluxTarget = new WebFluxTarget(postRouterFunction) | ||
def request = new Request('POST', '/data', [id: ['1234']], [:], | ||
OptionalBody.body('{"foo":"bar"}'.getBytes(StandardCharsets.UTF_8))) | ||
def interaction = new RequestResponseInteraction('some description', [], request) | ||
|
||
when: | ||
def requestAndClient = webFluxTarget.prepareRequest(interaction, [:]) | ||
def requestBuilder = requestAndClient.first | ||
|
||
then: | ||
requestBuilder instanceof WebTestClient.RequestHeadersSpec | ||
def builtRequest = requestBuilder.exchange().expectBody().returnResult() | ||
builtRequest.url.path == '/data' | ||
builtRequest.method.toString() == 'POST' | ||
builtRequest.rawStatusCode == 200 | ||
} | ||
|
||
def 'should execute interaction'() { | ||
given: | ||
def request = new Request('GET', '/data', [id: ['1234']]) | ||
def interaction = new RequestResponseInteraction('some description', [], request) | ||
WebFluxTarget webFluxTarget = new WebFluxTarget(routerFunction) | ||
def requestAndClient = webFluxTarget.prepareRequest(interaction, [:]) | ||
def requestBuilder = requestAndClient.first | ||
|
||
when: | ||
def response = webFluxTarget.executeInteraction(requestAndClient.second, requestBuilder) | ||
|
||
then: | ||
response.statusCode == 200 | ||
response.contentType.toString() == 'application/json' | ||
response.body == '{"id":1234}' | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters