
au.com.dius.pact.provider.spring.junit5.MockMvcTestTarget.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pact-jvm-provider-junit5-spring Show documentation
Show all versions of pact-jvm-provider-junit5-spring Show documentation
# Pact Spring/JUnit5 Support
This module extends the base [Pact JUnit5 module](../pact-jvm-provider-junit5). See that for more details.
For writing Spring Pact verification tests with JUnit 5, there is an JUnit 5 Invocation Context Provider that you can use with
the `@TestTemplate` annotation. This will generate a test for each interaction found for the pact files for the provider.
To use it, add the `@Provider` and `@ExtendWith(SpringExtension.class)` and one of the pact source annotations to your test class (as per a JUnit 5 test), then
add a method annotated with `@TestTemplate` and `@ExtendWith(PactVerificationSpringProvider.class)` that
takes a `PactVerificationContext` parameter. You will need to call `verifyInteraction()` on the context parameter in
your test template method.
For example:
```java
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@Provider("Animal Profile Service")
@PactBroker
public class ContractVerificationTest {
@TestTemplate
@ExtendWith(PactVerificationSpringProvider.class)
void pactVerificationTestTemplate(PactVerificationContext context) {
context.verifyInteraction();
}
}
```
You will now be able to setup all the required properties using the Spring context, e.g. creating an application
YAML file in the test resources:
```yaml
pactbroker:
host: your.broker.host
auth:
username: broker-user
password: broker.password
```
You can also run pact tests against `MockMvc` without need to spin up the whole application context which takes time
and often requires more additional setup (e.g. database). In order to run lightweight tests just use `@WebMvcTest`
from Spring and `MockMvcTestTarget` as a test target before each test.
For example:
```java
@WebMvcTest
@Provider("myAwesomeService")
@PactBroker
class ContractVerificationTest {
@Autowired
private MockMvc mockMvc;
@TestTemplate
@ExtendWith(PactVerificationInvocationContextProvider.class)
void pactVerificationTestTemplate(PactVerificationContext context) {
context.verifyInteraction();
}
@BeforeEach
void before(PactVerificationContext context) {
context.setTarget(new MockMvcTestTarget(mockMvc));
}
}
```
You can also use `MockMvcTestTarget` for tests without spring context by providing the controllers manually.
For example:
```java
@Provider("myAwesomeService")
@PactFolder("pacts")
class MockMvcTestTargetStandaloneMockMvcTestJava {
@TestTemplate
@ExtendWith(PactVerificationInvocationContextProvider.class)
void pactVerificationTestTemplate(PactVerificationContext context) {
context.verifyInteraction();
}
@BeforeEach
void before(PactVerificationContext context) {
MockMvcTestTarget testTarget = new MockMvcTestTarget();
testTarget.setControllers(new DataResource());
context.setTarget(testTarget);
}
@RestController
static class DataResource {
@GetMapping("/data")
@ResponseStatus(HttpStatus.NO_CONTENT)
void getData(@RequestParam("ticketId") String ticketId) {
}
}
}
```
**Important:** Since `@WebMvcTest` starts only Spring MVC components you can't use `PactVerificationSpringProvider`
and need to fallback to `PactVerificationInvocationContextProvider`
The newest version!
package au.com.dius.pact.provider.spring.junit5
import au.com.dius.pact.core.model.Interaction
import au.com.dius.pact.core.model.PactSource
import au.com.dius.pact.core.model.Request
import au.com.dius.pact.core.model.RequestResponseInteraction
import au.com.dius.pact.provider.IProviderVerifier
import au.com.dius.pact.provider.ProviderInfo
import au.com.dius.pact.provider.junit5.TestTarget
import mu.KLogging
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.converter.HttpMessageConverter
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.mock.web.MockMultipartFile
import org.springframework.test.web.client.match.MockRestRequestMatchers.anything
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.RequestBuilder
import org.springframework.test.web.servlet.ResultActions
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultHandlers
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder
import org.springframework.web.util.UriComponentsBuilder
import java.net.URI
import javax.mail.internet.ContentDisposition
import javax.mail.internet.MimeMultipart
import javax.mail.util.ByteArrayDataSource
/**
* Test target for tests using Spring MockMvc.
*/
class MockMvcTestTarget @JvmOverloads constructor(
var mockMvc: MockMvc? = null,
var controllers: List = mutableListOf(),
var controllerAdvices: List = mutableListOf(),
var messageConverters: List> = mutableListOf(),
var printRequestResponse: Boolean = false,
var servletPath: String? = null
) : TestTarget {
override fun getProviderInfo(serviceName: String, pactSource: PactSource?) = ProviderInfo(serviceName)
override fun prepareRequest(interaction: Interaction, context: Map): Pair {
if (interaction is RequestResponseInteraction) {
return toMockRequestBuilder(interaction.request.generatedRequest(context)) to buildMockMvc()
}
throw UnsupportedOperationException("Only request/response interactions can be used with an MockMvc test target")
}
fun setControllers(vararg controllers: Any) {
this.controllers = controllers.asList()
}
fun setControllerAdvices(vararg controllerAdvices: Any) {
this.controllerAdvices = controllerAdvices.asList()
}
fun setMessageConverters(vararg messageConverters: HttpMessageConverter<*>) {
this.messageConverters = messageConverters.asList()
}
private fun buildMockMvc(): MockMvc {
if (mockMvc != null) {
return mockMvc!!
}
val requestBuilder = MockMvcRequestBuilders.get("/")
if (!servletPath.isNullOrEmpty()) {
requestBuilder.servletPath(servletPath)
}
return MockMvcBuilders.standaloneSetup(*controllers.toTypedArray())
.setControllerAdvice(*controllerAdvices.toTypedArray())
.setMessageConverters(*messageConverters.toTypedArray())
.defaultRequest(requestBuilder)
.build()
}
private fun toMockRequestBuilder(request: Request): MockHttpServletRequestBuilder {
val body = request.body
val requestBuilder = if (body != null && body.isPresent()) {
if (request.isMultipartFileUpload()) {
val multipart = MimeMultipart(ByteArrayDataSource(body.unwrap(), request.contentTypeHeader()))
val multipartRequest = MockMvcRequestBuilders.fileUpload(requestUriString(request))
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()
multipartRequest.file(MockMultipartFile(name, filename, bodyPart.contentType, bodyPart.inputStream))
i++
}
multipartRequest.headers(mapHeaders(request, true))
} else {
MockMvcRequestBuilders.request(HttpMethod.valueOf(request.method), requestUriString(request))
.headers(mapHeaders(request, true))
.content(body.value)
}
} else {
MockMvcRequestBuilders.request(HttpMethod.valueOf(request.method), requestUriString(request))
.contentType(request.contentTypeHeader())
.headers(mapHeaders(request, false))
}
return requestBuilder
}
private fun requestUriString(request: Request): URI {
val uriBuilder = UriComponentsBuilder.fromPath(request.path)
val query = request.query
if (query != null && query.isNotEmpty()) {
query.forEach { key, value ->
uriBuilder.queryParam(key, *value.toTypedArray())
}
}
return URI.create(uriBuilder.toUriString())
}
private fun mapHeaders(request: Request, hasBody: Boolean): HttpHeaders {
val httpHeaders = HttpHeaders()
request.headers?.forEach { k, v ->
httpHeaders.add(k, v.joinToString(", "))
}
if (hasBody && !httpHeaders.containsKey(HttpHeaders.CONTENT_TYPE)) {
httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
}
return httpHeaders
}
override fun isHttpTarget() = true
override fun executeInteraction(client: Any?, request: Any?): Map {
val mockMvcClient = client as MockMvc
val requestBuilder = request as MockHttpServletRequestBuilder
val mvcResult = performRequest(mockMvcClient, requestBuilder).andDo {
if (printRequestResponse) {
MockMvcResultHandlers.print().handle(it)
}
}.andReturn()
return handleResponse(mvcResult.response)
}
private fun performRequest(mockMvc: MockMvc, requestBuilder: RequestBuilder): ResultActions {
val resultActions = mockMvc.perform(requestBuilder)
return if (resultActions.andReturn().request.isAsyncStarted) {
mockMvc.perform(MockMvcRequestBuilders.asyncDispatch(resultActions
.andExpect(MockMvcResultMatchers.request().asyncResult(anything()))
.andReturn()))
} else {
resultActions
}
}
private fun handleResponse(httpResponse: MockHttpServletResponse): Map {
logger.debug { "Received response: ${httpResponse.status}" }
val response = mutableMapOf("statusCode" to httpResponse.status)
val headers = mutableMapOf>()
httpResponse.headerNames.forEach { headerName ->
headers[headerName] = listOf(httpResponse.getHeader(headerName))
}
response["headers"] = headers
if (httpResponse.contentType.isNullOrEmpty()) {
response["contentType"] = org.apache.http.entity.ContentType.APPLICATION_JSON
} else {
response["contentType"] = org.apache.http.entity.ContentType.parse(httpResponse.contentType)
}
response["data"] = httpResponse.contentAsString
logger.debug { "Response: $response" }
return response
}
override fun prepareVerifier(verifier: IProviderVerifier, testInstance: Any) {
/* NO-OP */
}
companion object : KLogging()
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy