Spring-cloud-contract: @AutoConfigureStubRunner ids attribute does not use the artifact麓s groupId to locate the generated stubs

Created on 14 Mar 2018  路  4Comments  路  Source: spring-cloud/spring-cloud-contract

Spring contract version 1.2.3

In the company that I work, we have conventioned that the rest endpoints are located under a "rest-adapter" module. When working with multiple projects with spring cloud contracts, the generated jar have all the same name: rest-adapter.jar

So on the client side, when using the @AutoConfigureStubRunner with, for example, ids =["com.mycompany.foo:rest-adapter", "com.mycompany.bar:rest-adapter"] I've noticed that spring cloud apparently only uses the artifactId to located the stubs, and, eventually will load the wrong one and the test will fail because it would not find the desired stubs.

Thanks

bug

All 4 comments

Hi! I think you need to setup a sample cause I can't replicate it. I've added some additional tests to check the same group id, different artifact ids. Then I've added same artifact ids, different group ids. Everything seems to work fine.

Hi Marcin,

contract for artifact "mycompany.processo:rest-adapter" (Producer: processo)

org.springframework.cloud.contract.spec.Contract.make {
    request {
        method 'GET'
        url('/processos/fiscalizacao') {
            queryParameters {
                parameter("codigo", 1)
            }
        }
        headers {
            accept(applicationJson())
        }
    }
    response {
        status 200
        body(

                cod: fromRequest().query("codigo"),
                numeroProcessoFormatado: "000.001/2018-1",
                confidencialidade: "P煤blico",
                assunto: "assunto do processo",
                codUnidadeResponsavelPorAgir: 1,
                codUnidadeResponsavelTecnica: 1,
                codSubunidadeResponsavelPorAgir: 1,
                codSubunidadeResponsavelTecnica: 1,
                codClasseAssunto: 1,
                codUsuarioResponsavelPorAgir: 1

        )
        headers {
            contentType(applicationJson())
        }
    }
}

contract for artifact "mycompany.documento:rest-adapter" (Producer: documento)

org.springframework.cloud.contract.spec.Contract.make {
    request {
        method 'GET'
        url '/documentos/1/conteudo'
        headers {
            accept(applicationJson())
        }
    }
    response {
        status 200
        body([
                conteudoBase64: "0123456789ABCDEF",
                nomeArquivo: "teste.pdf"
        ])
        headers {
            contentType(applicationJson())
        }
    }
}

On my consumer module, there are independent tests that are using the contracts from the producers above. All of them inherit from this base test class:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, properties = ['flyway.enabled=false'])
@AutoConfigureStubRunner(workOffline = true,
        ids = ["mycompany.processo:rest-adapter",
                "mycompany.documento:rest-adapter"])

@ContextConfiguration(name = "testeContrato")
class TesteContratoBase extends Specification {}

If I run the following test for the "processo" producer:

class ProcessoServicoExternoTest extends TesteContratoBase {

    @Value('${stubrunner.runningstubs.rest-adapter.port}')
    int producerPort

   def 'deve recuperar processo de fiscalizacao por cod'() {
        given:

        when:
        ProcessosServicoExterno servico = 
Feign.builder().contract(new SpringMvcContract())
                .decoder(new JacksonDecoder()).encoder(new JacksonEncoder())
                .target(ProcessosServicoExterno.class, 'http://localhost:' + producerPort)

        then:
        dto.cod == 1L
        dto.codUsuarioResponsavelPorAgir != null
        dto.numero == '000.001/2018-1'
        dto.confidencialidade == 'P煤blico'      
    }
}
class ProcessoServicoExterno {
   ...
 @GetMapping(value = "/processos/fiscalizacao", headers = "Accept=application/json")
    ProcessoDto processoPorCodigoSeForFiscalizacao(@RequestParam("codigo") Long codigo)
  ...
}

The test will fail with the result:


 Request was not matched
                                               =======================

-----------------------------------------------------------------------------------------------------------------------
| Closest stub                                             | Request                                                  |
-----------------------------------------------------------------------------------------------------------------------
                                                           |
GET                                                        | GET
/documentos/1/conteudo                                     | /processos/fiscalizacao?codigo=1                    <<<<< URL does not match
                                                           |
Accept [matches] : application/json.*                      | Accept: application/json
                                                           |
                                                           |
-----------------------------------------------------------------------------------------------------------------------

If, however, I remove the entry for "mycompany.documento:rest-adapter" on the @AutoConfigureStubRunner, the tests pass. If I rename the name of the artifact, so that they are all unique, the tests also pass.

Hope my explanation was clear.

Aaaa ok. So the stub downloading is correct. The port is wrong

@Value('${stubrunner.runningstubs.rest-adapter.port}')
    int producerPort

I fixed it in https://github.com/spring-cloud/spring-cloud-contract/commit/32888e3b92875655068f571fd2c17b70911c2831 and https://github.com/spring-cloud/spring-cloud-contract/commit/c3d7e2721de4925ab370e2c969cc8008ab68a817 . It fixed https://github.com/spring-cloud/spring-cloud-contract/issues/573 . In other words, once I fix the build, you'll be able to use snapshots to do:

@Value('${stubrunner.runningstubs.mycompany.processo.rest-adapter.port}')
    int producerPort

or

@StubRunnerPort("mycompany.processo:rest-adapter")
    int producerPort

Stub Runner Spring registers environment variables in the following manner
for every registered WireMock server. Example for Stub Runner ids
com.example:foo, com.example:bar.

stubrunner.runningstubs.foo.port
stubrunner.runningstubs.com.example.foo.port
stubrunner.runningstubs.bar.port
stubrunner.runningstubs.com.example.bar.port

Which you can reference in your code.

You can also use the @StubRunnerPort annotation to inject the port of a running stub.
Value of the annotation can be the groupid:artifactid or just the artifactid. Example for Stub Runner ids
com.example:foo, com.example:bar.

@StubRunnerPort("foo")
int fooPort;
@StubRunnerPort("com.example:bar")
int barPort;

Also, as a workaround you can use the StubFinder interface.

@Autowired StubFinder stubFinder;

stubFinder.findStubUrl('loanIssuance').port
stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance').port
stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance').port

These are all equivalent calls. You can use the findStubUrl(groupId:artifactId) or findStubUrl(groupId, artifactId) methods to retrieve the proper port

Was this page helpful?
0 / 5 - 0 ratings