Search code examples
junit4mockmvcpactspring-cloud-contractspring-restdocs

How to generate both spring restdocs and Pact.io pact from junit test?


Using spring boot and mockmvc , I have test class with following @beforeEach method:

@BeforeEach
void setUp(WebApplicationContext context,
           RestDocumentationContextProvider restDocumentation) {
    MockMvcRestDocumentationConfigurer configurer = documentationConfiguration(restDocumentation);
    configurer.operationPreprocessors()
            .withRequestDefaults(prettyPrint())
            .withResponseDefaults(prettyPrint());
    configurer.snippets()
            .withDefaults(
                    httpRequest(),
                    httpResponse()
            );
    this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
            .apply(configurer)
            .build();

and the following test method:

@Test
void createAdminHttpRequest() throws Exception {
    var adminDTO = HandlerTestObjectGenerator.createFixedAdminDTO();

    mockMvc.perform(
            RestDocumentationRequestBuilders
                    .post("/api/admins")
                    .content(objectMapper.writeValueAsString(adminDTO))
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
    ).andExpect(status().isCreated())
            .andDo(document("create-admin",
                    preprocessRequest(),
                    preprocessResponse(),
                    requestFields(
                            usernameFieldDescriptor,
                            passwordFieldDescriptor,
                            rolesFieldDescriptor
                    ),
                    responseFields(
                            admin_adminIdFieldDescriptor,
                            admin_usernameFieldDescriptor,
                            admin_rolesFieldDescriptor
                    ),
                    SpringCloudContractRestDocs.dslContract(),
            ));
}

This test works well and generates both spring rest docs documentation and groovy contract.
But for front-end (react) testing, I need to generate Pact.io contract, which is framework independent.

Question
So, my question is if it is possible to generate both spring rest docs and pact.io pact using single @Test method?

My research
What I have found so far is that pacts are generated from @Pact annotated methods using its own rest builder.
Additonally, I have found this conversation: https://gitter.im/spring-cloud/spring-cloud-contract/archives/2018/08/06 and I am trying to implement own maven plugin to convert groovy contracts to pacts, but there seems to be error in the BodyConverter class and I am getting the following exception:

java.lang.UnsupportedOperationException: use the array(String name) form
at au.com.dius.pact.consumer.dsl.PactDslJsonBody.array(PactDslJsonBody.java:673)

My maven plugin code samples:
Inicialization:

private PactContractConverter pactContractConverter = new PactContractConverter();
private ContractVerifierDslConverter contractDslConverter = new ContractVerifierDslConverter();

Conversion:

private void processFiles(List<File> contractFiles) throws Exception {
        for(File file : contractFiles) {
            logger.info("Processing " + file.getAbsolutePath());
            Collection<Contract> contracts = contractDslConverter.convertFrom(file);
            Collection<Pact> pacts = pactContractConverter.convertTo(contracts);

            String jsonPacts = mapper.writeValueAsString(pactContractConverter.store(pacts));
            File pactsFile = new File(outputDir, file.getName() + "_pact.json");
            FileUtils.writeByteArrayToFile(pactsFile, jsonPacts.getBytes());
            logger.info("Generated pact file: " + pactsFile.getAbsolutePath());
        }
    }

But I am getting the exception mentioned above. There is a direct call to the method, which throws UnsupportedOperationException. I found other method array(String name), but that seems not to be called from the converter code.


Solution

  • Let's begin with this statement:

    But for front-end (react) testing, I need to generate Pact.io contract, which is framework independent.

    You can use Spring Cloud Contract in the polyglot world. Just use Docker (https://spring.io/blog/2018/02/13/spring-cloud-contract-in-a-polyglot-world) and https://cloud.spring.io/spring-cloud-static/spring-cloud-contract/2.2.0.RELEASE/reference/html/docker-project.html

    Coming back to your question

    So, my question is if it is possible to generate both spring rest docs and pact.io pact using single @Test method?

    Let's do it in a different way... Since you already have the DSLs, I guess you would like to also get the Pact files. If you check the documentation under this section (https://cloud.spring.io/spring-cloud-static/spring-cloud-contract/2.2.0.RELEASE/reference/html/howto.html#how-to-generate-pact-from-scc) you'll see exactly the answer to your question. It's enough to add a plugin that after your tests have generated the DSLs will convert those DSLs to something else, e.g. Pact files.

    Example of using Maven plugin

    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.6.0</version>
        <executions>
            <execution>
                <id>convert-dsl-to-pact</id>
                <phase>process-test-classes</phase>
                <configuration>
                    <classpathScope>test</classpathScope>
                    <mainClass>
                        org.springframework.cloud.contract.verifier.util.ToFileContractsTransformer
                    </mainClass>
                    <arguments>
                        <argument>
                            org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter
                        </argument>
                        <argument>${project.basedir}/target/pacts</argument>
                        <argument>
                            ${project.basedir}/src/test/resources/contracts
                        </argument>
                    </arguments>
                </configuration>
                <goals>
                    <goal>java</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
    

    If you modify the ${project.basedir}/src/test/resources/contracts to point to the location where the DSLs got dumped from your REST Docs tests, you'll get the PACT files dumped to ${project.basedir}/target/pacts. Below you have a similar example for Gradle

    task convertContracts(type: JavaExec) {
        main = "org.springframework.cloud.contract.verifier.util.ToFileContractsTransformer"
        classpath = sourceSets.test.compileClasspath
        args("org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter",
                "${project.rootDir}/build/pacts", "${project.rootDir}/src/test/resources/contracts")
    }