In the article “Get Your Microservices in Sync with Consumer-Driven Contract Testing”, we saw how easy it is to use the stubs generated with Spring Cloud Contract when working in the JVM world. But, what happens if we want to use them in a consumer written in a different language?
In real life, a remote control doesn’t need to be made by the same company as the TV to work—it just needs to follow the same protocol. The same thing happens if the client consuming the stubs is built in another language, such as a frontend application using Angular.
Use case
Let’s look at a use case where we need to develop a frontend application with Angular that consumes a service exposed by a REST API developed with Spring Boot 3.
Functional requirements
A web application must be developed with Angular for the Back Office, supporting the following operations:
- Display the list of products.
- Create a new product.
- Retrieve product details by its ID.
- Update an existing product.
- Delete a product.
Non-functional requirements
These requirements are meant to ensure seamless integration between frontend and backend, maintaining contract stability throughout development.
- Development will be done in parallel by two teams:
- Frontend: responsible for developing the Angular web application, acting as the consumer of the service.
- Backend: responsible for implementing the REST API that provides the service, acting as the provider.
- The contract should be defined between the two teams, with the frontend team stating their requirements and the backend team finding the optimal way to provide the data.
- The backend team will generate the stubs based on the defined contract and will publish them through their CI/CD pipeline to a public repository (Artifactory or Maven), where the consumer team can access them.
- The frontend team will use the stubs with Docker for their integration tests.
- If either team needs to change the contract, they should raise their hand and call a meeting to refine the contract. Once the changes are agreed upon, the backend team will update the contract, regenerate the stubs, publish them to the repository, and both teams will continue development in parallel.
Let’s get to work
Contract definition
Based on the functional requirements, the frontend team decides that they’ll need a REST API with the following endpoints:
- GET /api/products will return the list of products.
- GET /api/products/{id} will return a specific product.
- POST /api/products will create a new product.
- PUT /api/products/{id} will update an existing product.
- DELETE /api/products/{id} will delete a product.
Each product will contain the following fields:
{
"id": 17035535,
"name": "Cheap product",
"price": {
"amount": "9.99",
"currency": "EUR"
}
}
The two teams have also agreed on the behavior of the stubs when calling the endpoints:
- GET /api/products:
- will always return a list of products.
- GET /api/products/{id}:
- if the id = 17035535, it will return status code 200 and the product in the response body.
- for any other value, it will return status code 404.
- POST /api/products: the product name is a required field and cannot be empty.
- if the product has all fields filled, it will return status code 204.
- if the product name is missing or an empty string, it will return status code 400.
- PUT /api/products/{id}:
- if the id = 17035535, it will return status code 204.
- for any other value, it will return status code 404.
- DELETE /api/products/{id}:
- if the id = 17005954, it will return status code 204.
- for any other value, it will return status code 404.
Create the producer
Let’s go to Josh Long’s favorite page and generate the producer including the following dependencies:
- Spring Web
- Contract Verifier
- Spring Data JPA
- H2 Database
- Lombok

We generate the project and extract its content into the catalog module.
Create the contracts
In the path catalog/src/test/resources/ we will have a contracts folder. If it doesn’t exist, we create it, because this is where we will add our contracts.
We can write the contracts in Java or use one of the DSLs. Spring Cloud Contract offers two types of DSLs: one written in Groovy and another in YAML. My favorite is the YAML format for defining contracts, because it’s easier to read and understand.
- Return the list of products
The stub will always return a list of products defined in the file products.json:
{
"products": [
{
"id": "17035535",
"name": "Cheap product",
"price": {
"amount": "9.99",
"currency": "EUR"
}
},
{
"id": "17005954",
"name": "Quality product",
"price": {
"amount": "29.99",
"currency": "EUR"
}
}
]
}
And this would be the contract defined in shouldReturnProducts.yml:
description: Products list
request:
method: GET
url: /api/products
response:
status: 200
headers:
Content-Type: application/json
bodyFromFile: response/products.json
- Return the product
As agreed, this would be the shouldReturnProductById.yml contract:
name: Get existing product
request:
method: GET
url: /api/products/17035535
response:
status: 200
headers:
Content-Type: application/json
body:
id: 17035535
name: Cheap product
price:
amount: 9.99
currency: EUR
---
name: Get non-existing product
request:
method: GET
url: /api/products/99999999
response:
status: 404
- Create a new product.
In this contract, the product name is a mandatory field and cannot be empty. Therefore, if the product has all fields reported, status code 204 will be returned, but if the product name is not reported or is an empty string, status code 400 will be returned. Let's put it in shouldCreateProduct.yml:
name: Create product
request:
method: POST
url: /api/products
headers:
Content-Type: application/json
body:
name: Not so cheap product
price:
amount: 19.99
currency: EUR
response:
status: 204
---
name: Create product without name
request:
method: POST
url: /api/products
headers:
Content-Type: application/json
body:
name:
price:
amount: 19.99
currency: EUR
response:
status: 400
- Update a product.
If the product exists (has id = 17035535), the product will be updated. Otherwise, it will return a ‘Not Found’. This is the shouldUpdateProduct.yml contract:
name: Update product
request:
method: PUT
url: /api/products/17035535
headers:
Content-Type: application/json
body:
name: Inexpensive product
price:
amount: 10.99
currency: EUR
response:
status: 204
---
name: Try to update non-existing product
request:
method: PUT
url: /api/products/99999999
headers:
Content-Type: application/json
body:
name: Inexpensive product
price:
amount: 10.99
currency: EUR
response:
status: 404
- Delete a product.
If the product exists (has id = 17005954), the product will be deleted. Otherwise, it will return a "Not Found". The product will not be truly deleted, i.e. the same call will always return the same response. This is the shouldDeleteProduct.yml contract:
name: Delete existing product
request:
method: DELETE
url: /api/products/17005954
headers:
Content-Type: application/json
response:
status: 204
---
name: Try to delete non-existing product
request:
method: DELETE
url: /api/products/99999999
headers:
Content-Type: application/json
response:
status: 404
Configure the contract tests
We need to configure the main class of the contract tests so that they can run correctly by configuring RestAssuredMockMvc, which integrates Rest Assured with Spring's MockMvc, allowing REST API tests to be performed directly on Spring MVC controllers without the need to deploy the application on a real server.
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
public class BaseTestClass {
@Autowired
private ProductController productController;
@BeforeEach
public void setup() {
StandaloneMockMvcBuilder standaloneMockMvcBuilder = MockMvcBuilders.standaloneSetup(productController);
RestAssuredMockMvc.standaloneSetup(standaloneMockMvcBuilder);
}
}
Once the BaseTestClass.java is created, we need to configure it to work with the Maven plugin for Spring Cloud Contract.
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>4.2.1</version>
<extensions>true</extensions>
<configuration>
<testFramework>JUNIT5</testFramework>
<baseClassForTests>com.paradigmadigital.catalog.BaseTestClass</baseClassForTests>
</configuration>
</plugin>
Generate stubs
We can now generate the stubs without having implemented the business logic with the following command from the catalog folder:
mvn clean package -DskipTests
It’s important to skip the tests because we haven’t implemented any code yet.
Publish the contracts
There is a Docker project that can run a mock server with the stubs created by Spring Cloud Contract, but for our requirements it started up too slowly. For this reason, we created our own Docker Spring Cloud Contract Stub Runner, which builds a Docker image published on DockerHub. We use it as the base for the images we're creating in the CI/CD pipeline with integrated stubs. This way, the Docker image starts up almost instantly, 10 times faster than the original method. You can find documentation on how to use it and an example here.
Let’s create an image with the stubs we’ve generated. This is the content of our StubrunnerDockerfile:
FROM ismail2ov/spring-cloud-contract-stub-runner:3.1.1
LABEL Author="Ismail Ahmedov <i.a.ismailov@gmail.com>"
WORKDIR /home/scc
ENV STUBS_GROUP_ID com.paradigmadigital
ENV STUBS_ARTIFACT_ID catalog
ENV STUBS_VERSION 0.0.1-SNAPSHOT
ENV STUBS_FOLDER com/paradigmadigital
ENV STUBRUNNER_PORT 8080
ENV STUBRUNNER_STUBS_MODE LOCAL
ENV STUBRUNNER_IDS ${STUBS_GROUP_ID}:${STUBS_ARTIFACT_ID}:${STUBS_VERSION}:stubs:${STUBRUNNER_PORT}
ENV REPOSITORY_FOLDER .m2/repository/${STUBS_FOLDER}/${STUBS_ARTIFACT_ID}/${STUBS_VERSION}/
RUN mkdir -p ${REPOSITORY_FOLDER}
COPY --chown=scc:scc ./target/${STUBS_ARTIFACT_ID}-${STUBS_VERSION}-stubs.jar ${REPOSITORY_FOLDER}
ENTRYPOINT ["./run.sh"]
Now we can create the Docker image with the command:
docker build -f StubrunnerDockerfile --tag paradigmadigital/ecommerce-catalog-stubs .
We can also run the created image with the command:
docker run -d --rm --name ecommerce-catalog-stub-server -p 8080:8080 paradigmadigital/ecommerce-catalog-stubs
And test them by entering this URL in your browser: https://www.paradigmadigital.com/api/products.
ℹ️ In our case, we had a step in the CI/CD pipeline that created the Docker image and pushed it to the company's Docker Registry.
This way, both teams can work in parallel.
Create the consumer
The frontend team has been working and has finished their development. In order to run it, we need to switch to the ecommerce folder and install the dependencies:
npm install
Once we have all the dependencies installed, run the integration tests:
npm run integration-tests
We can see that 4 Test Suites were executed, containing 13 tests, and all of them passed successfully.

The configuration of the Docker image with the stub is done in the run-integration-tests.js file:
const STUB_IMAGE = 'paradigmadigital/ecommerce-catalog-stubs:latest';
Conclusions
In this article, we explored the optimal way to integrate stubs generated by a backend team using Spring Cloud Contract into a frontend project with Angular for contract testing.
We saw how to use a Docker image to run a stub runner with the stubs retrieved from Artifactory or a Git repository. Since that process was a bit slow, we created a Docker image that already contains the stubs and runs in just a few seconds.
You can check out all the code with step-by-step commits in the GitHub repository: Angular example with Docker Spring Cloud Contract Stub Runner.
Comments are moderated and will only be visible if they add to the discussion in a constructive way. If you disagree with a point, please, be polite.
Tell us what you think.