¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
dev
Ismail Ahmedov 16/06/2025 Cargando comentarios…
En el artículo “pon tus microservicios de acuerdo con Consumer Driven Contract Testing” vimos lo fácil que es usar los stubs generados con Spring Cloud Contract si trabajamos en el mundo JVM. Pero, ¿qué pasa si queremos usarlos en un consumidor que está escrito en un lenguaje diferente?
En la vida real, para que un mando de distancia funcione con una televisión, no es necesario que estén producidos por la misma empresa. Simplemente los dos deben implementar el contrato definido. Lo mismo pasa si el cliente de los stubs está creado en un lenguaje diferente. Por ejemplo, una aplicación frontend con Angular.
Vamos a ver un caso de uso de una app de frontend que se debe desarrollar con Angular y que va a consumir un servicio a través de una API Rest desarrollada con Spring Boot 3.
Se debe desarrollar una aplicación web con Angular para el Back Office que permitirá las siguientes operaciones:
Estos requisitos son para garantizar una integración fluida entre frontend y backend, asegurando la estabilidad del contrato a lo largo del desarrollo.
Basándose en los requisitos funcionales, el equipo de frontend decide que necesitará una API Rest que contendrá los siguientes endpoints:
Un producto contendrá los siguientes datos:
{
"id": 17035535,
"name": "Cheap product",
"price": {
"amount": "9.99",
"currency": "EUR"
}
}
Los dos equipos también han acordado el comportamiento de los stubs al llamar a los endpoint:
Vamos a la página favorita de Josh Long y generamos el producer incluyendo las siguientes dependencias:
Generamos el proyecto y extraemos su contenido en el módulo catalog.
En la ruta catalog/src/test/resources/ tendremos una carpeta contracts. Si no existe, la creamos, porque en ella vamos a añadir los contratos.
Podemos escribir los contratos en Java o utilizar alguno de los DSL. Spring Cloud Contract ofrece dos tipos de DSL: uno escrito en Groovy y otro en YAML. Mi favorito es el formato YAML para definir los contratos, porque es más fácil de leer y entender.
El stub siempre devolverá una lista con productos definidos en el fichero 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"
}
}
]
}
Y este sería el contrato definido en shouldReturnProducts.yml:
description: Products list
request:
method: GET
url: /api/products
response:
status: 200
headers:
Content-Type: application/json
bodyFromFile: response/products.json
Según lo acordado, este sería el contrato de shouldReturnProductById.yml:
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
En este contrato, el nombre del producto es un campo obligatorio y no puede estar vacío. Por tanto, si el producto tiene informados todos los campos, se devolverá status code 204, pero si el nombre del producto no está informado o es un string vacío, devolverá status code 400. Vamos a plasmarlo en 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
Si el producto existe (tiene id = 17035535), el producto se actualizará. En caso contrario, devolverá un “Not Found”. Este es el contrato shouldUpdateProduct.yml:
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
Si el producto existe (tiene id = 17005954), el producto se eliminará. En caso contrario, devolverá un "Not Found". El producto no se eliminará de verdad, es decir, que la misma llamada devolverá siempre la misma respuesta. Este es el contrato shouldDeleteProduct.yml:
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
Nos falta configurar la clase principal de los tests de contrato para que se puedan ejecutar correctamente configurando RestAssuredMockMvc, que integra Rest Assured con MockMvc de Spring, permitiendo realizar pruebas de API REST directamente sobre los controladores Spring MVC sin la necesidad de desplegar la aplicación en un servidor real.
@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);
}
}
Una vez creado el BaseTestClass.java, debemos configurarlo para que pueda funcionar con el plugin de Maven para 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>
Ya podemos generar los stubs sin que hayamos implementado la lógica de negocio con el siguiente comando desde la carpeta catalog:
mvn clean package -DskipTests
Es importante saltar los test porque todavía no tenemos implementado nada de código.
Hay un proyecto de Docker que puede ejecutar mock server con los stubs creados por Spring Cloud Contract, pero para nuestros requisitos arrancaba muy lento. Por esta razón creamos nuestro propio Docker Spring Cloud Contract Stub Runner con el que creamos una imagen de Docker que está publicada en DockerHub. La utilizamos como base de las que estamos creando en el pipeline de CI/CD con los stubs integrados. De esta manera, el arranque de la imagen de Docker es casi instantáneo, 10 veces más rápido que la manera original. Aquí podéis ver la documentación de cómo se puede usar y un ejemplo.
Vamos a crear una imagen con los stubs que hemos generado. Este es el contenido de nuestro 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"]
Ahora podemos crear la imagen de Docker con el comando:
docker build -f StubrunnerDockerfile --tag paradigmadigital/ecommerce-catalog-stubs .
También podemos ejecutar el la imagen creada con el comando:
docker run -d --rm --name ecommerce-catalog-stub-server -p 8080:8080 paradigmadigital/ecommerce-catalog-stubs
Y probarlos introduciendo esta ruta en el navegador: https://www.paradigmadigital.com/api/products.
ℹ️ En nuestro caso, teníamos un paso en el pipeline de CI/CD que creaba la imagen Docker y la subía en el Docker Registry de la empresa.
De esta manera los dos equipos pueden trabajar en paralelo.
El equipo de frontend ha estado trabajando y ha terminado su desarrollo. Para poder ejecutarlo, debemos cambiar a la carpeta de ecommerce e instalar las dependencias:
npm install
Una vez que tengamos instalados todas las dependencias, ejecutamos los tests de integración:
npm run integration-tests
Podemos ver que se han ejecutado 4 Test Suites que contenían 13 tests y todos han pasado exitosamente.
La configuración de la imagen de Docker con el stub se hace en el fichero run-integration-tests.js:
const STUB_IMAGE = 'paradigmadigital/ecommerce-catalog-stubs:latest';
En este artículos hemos visto la manera óptima de integrar los stubs generados por un equipo backend con Spring Cloud Contract en un proyecto frontend con Angular en nuestros tests de contrato.
Hemos visto cómo se puede usar una imagen de Docker para ejecutar stub runner con los los stubs obtenidos desde Artifactory o un repositorio de Git. Como esto era un poquito lento, hemos creado una imagen de Docker que contiene ya los stubs y cómo se ejecuta en pocos segundos.
Podéis ver todo el código con los commits por pasos en el repositorio GitHub: Angular example with Docker Spring Cloud Contract Stub Runner.
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.