All Downloads are FREE. Search and download functionalities are using the official Maven repository.

au.com.dius.pact.provider.spring.junit5.MockMvcTestTarget.kt Maven / Gradle / Ivy

Go to download

# 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