Consumer Driven Contract home - lstefaniszyn/e-bank_ GitHub Wiki

Why ?

Consumer-driven contract testing is a way to formalize data schema expectations into a contract between each Consumer-Provider pair.

Contract testing will keep the APIs you produce and consume from breaking unexpectedly, without any manual intervention needed. This makes integration testing and developing new features much easier.

The Provider is an application responsible for publishing an API; while Client of the Provider is another application using (consuming) said API.

Contract test

Tip
Contract tests are used to validate request and response API schema and not the business logic.

While building the Contract tests we have to separate Producer and Client test scenarios. Response and request input data differ based on context.

In case for:

  • Producer we would like to use more regexp approach, to catch any request and response input data structure

While for

  • Client we would like to use specific test data

Producer / Server / Test

Test scenario:

Producer test structure

Here is simple contract tests get_AppStatus_OK_Response.groovy to run against /api and to validate response header and body

The test content was created based on Contract DSL language.

Contract.make {
	description("""
""")
	request {
		method('GET')
		url('/api')
		headers {
			contentType(applicationJson())
		}
	}
	response {
		status 200
		headers {
			contentType(applicationJson())
		body(
            'app-version': $(producer(regex('[0-9]+\\.[0-9]+\\.[0-9]+')), consumer('1.0.0'))
        )
		}
	}
}

To build contract tests in comparison to integration tests we need to make, clear cut off, just below Controller level. Therefore mocking Service/Repository level is a must-have for such tests.

Producer layers to test

Service / Repository application layer was mocked here as a example in : Mocked service layer

How to auto-generate contract tests for producer and client

During >mvn test-compile goal, test files are created under target\generated-test-sources\contracts\com\example\ebank\api\autogenerate\ folder, based on given ingredients:

  1. Take your OpenAPI file and start to define request and response schema, based on Contract DSL

  2. Mock Service/Repository application layer, like in example Mocked service layer

  3. List of contract tests written in Groovy files

  4. Update Maven pom.xml file

<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>${spring-cloud-contract.version}</version>
    <extensions>true</extensions>
    <configuration>
        <contractsDirectory>${project.basedir}/src/test/resources/contracts</contractsDirectory>
        <!-- Create package name for your auto-generated tests -->
        <basePackageForTests>com.example.ebank.api.autogenerate</basePackageForTests>
        <!-- If there’s a contract under /com/example/ebank/api/<folderName>/*.groovy` then we’ll search for a <folderName>Base.java files -->
        <packageWithBaseClasses>com.example.ebank.api</packageWithBaseClasses>
    </configuration>
</plugin>

Consumer / Client / Stub

Test scenario: :

Client test structure

Creating contract tests on consumer side using Stub Runner from Spring Cloud Contract

To benefit from Consumer Driven Contract, we have to create some tests, which will use stubs, generated by Producer and deployed on remote or local maven repository, and verify if the contract between consumer and provider has not been broken. We will use Stub Runner from Spring Cloud Contract implementation to achieve this approach.

To use stub runner we have to add following dependencies in pom.xml file:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
    <version>${spring-cloud-contract.version}</version>
</dependency>

Then, we need to write some test to verify if contract between consumer and API provider is valid. The code below presents example test class for verifying contract between E-bank application (consumer) and Exchange rate service (provider):

@RunWith(SpringRunner.class)
@AutoConfigureStubRunner(ids = "com.example:exchangerate:+:stubs:8095", stubsMode = StubsMode.LOCAL)
@SpringBootTest(classes = EBankApplication.class, webEnvironment = WebEnvironment.MOCK)
@ActiveProfiles({ "mock", "it-local" })
public class ExchangeRateServiceContractTest {

	@Autowired
	@Qualifier("feign")
	private ExternalAPIClient exchangeRateService;

	@Test
	public void testExchangerateEndpoint() throws Exception {
		InlineResponse200Dto response = exchangeRateService.getExchangeRate(Currency.EUR);
		assertThat(response).isNotNull();
		assertThat(response.getValue()).isEqualByComparingTo(Double.valueOf("0.90872"));
	}
}

The ids property in @AutoConfigureStubRunner contains a list of generated stubs which are used in this test and specifies:

  • com.example - the groupId of our artifact

  • exchangerate - artifactId of producer’s stub

  • + - means that we always fetch the newest version of stub jar from repository

  • 8095 - the port on with generated stub will run

stubsMode = StubsMode.LOCAL - states that we will fetch stubs from our local .m2 repository on local machine. Use StubsMode.REMOTE to fetch from remote location

⚠️ **GitHub.com Fallback** ⚠️