0% found this document useful (0 votes)
160 views11 pages

Spring Boot - Integration Test by Mocking Other Microservices - LinkedIn

The document discusses how to perform integration testing of microservices by mocking other services with Spring Boot, Spring Cloud Contract, WireMock and REST Docs. It provides code samples for a producer project that generates stubs and a consumer project that consumes the stubs for integration testing. It also describes challenges encountered with plugin executions in the IDE and how to resolve them by disabling phases.

Uploaded by

centinel29
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
160 views11 pages

Spring Boot - Integration Test by Mocking Other Microservices - LinkedIn

The document discusses how to perform integration testing of microservices by mocking other services with Spring Boot, Spring Cloud Contract, WireMock and REST Docs. It provides code samples for a producer project that generates stubs and a consumer project that consumes the stubs for integration testing. It also describes challenges encountered with plugin executions in the IDE and how to resolve them by disabling phases.

Uploaded by

centinel29
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 11

Spring Boot: Integration Test by

Mocking other microservices


Publicada el 23 de junio de 2021

https://docs.spring.io/spring-cloud-contract/docs/2.2.7.RELEASE/reference/html/getting-started.html#getting-started-introducing-
spring-cloud-contract

Renan Franca 2 artículos


Seguir
Software Engineer | Microservices | Enjoy ☕🍃☸️🤓🍼

Spring Boot: Teste de Integração através da simulação de outros


micro serviços (pt-BR)

If you already figured out the subject of this article only by reading the title, I
will go straight to the code that implements a producer and a consumer by
forking the spring boot own sample code.

The producer project which generates stubs thanks to WireMock:


https://github.com/renanfranca/spring-cloud-contract-
samples/tree/main/producer_with_restdocs

The consumer project which implemented integration test by consuming the


producer's stubs through WireMock: https://github.com/renanfranca/spring-
cloud-contract-samples/tree/main/consumer_with_restdocs

Some step by step:

1. Download both projects and import them into Spring Tool Suite.

2. After that, disable the dependency between projects to make it works when both
projects were opened at the same Spring Tool Suite workspace. So, you can disable
the reference between projects like that: Right-click on top the project
name>Maven>Disable Workspace Resolution. This same procedure should be done
with the Consumer and the Producer.

3. Execute mvn clean install at the beer-api-producer-restdocs project

4. Execute force Update of Snapshots/Releases at beer-api-consumer-restdocs


project: Right-click on top the project name>Maven>Update Project...>Mark the
checkbox option 'Force Update of Snapshots/Releases' then click 'OK'

5. Run JUnit Test at the class BeerControllerTest.java from the beer-api-consumer-


restdocs project then you will see the producer's stub consumed smoothly.

Stay awhile and learn more about this subject

The main goal is to accomplish an integration test without the need to bring all
microservices up, their databases, and other components used by them to do an
integration test with your recently created/modified microservice. In addition that we
have to consider, all infrastructure should bring down and up to make sure that our tests
are deterministic.

The downside of mocking microservices is that could become out of date, even when the
tests succeed some errors could raise after deploying on production. A workaround for
this scenario could be possible if we ensure that the microservice generates the stubs
only after tests succeed. The microservice which generates the stubs is called the
producer and which microservice uses stubs in an integration test is called the consumer,
so it is very important an automatic way to deliver the stubs to ensure that they are
always up to date.

In this scenario, the stubs work as a contract between the producer and the consumer,
this contract establishes what is necessary to make a successful request to receive the
preset response. This excellent spring boot documentation explains in detail how to
implement microservices oriented by contracts. On the other hand, the implementation-
oriented by contract is a great change in the development flow. Is there another path if
you can not do this change?

One option is to take advantage of your already implemented integration tests to generate
contract definitions (only for documentation), the stubs definitions, and copy them with
the model classes to a stubs.jar package distributed automatically to other developers.
Those developers could implement consumers to interact with these stubs without the
need for the previous introduction from the developer who builds them.

Keep reading to learn more about the code shared at the beginning

The great article Integration Testing Spring Boot based Microservices using Spring
REST Docs and WireMock was my kick start to implement the integration test between
microservices using stubs with WireMock and RESTDocs. The article use gradle
instead of maven (which I use) and I felt that I needed to dive into these topics below:

Spring Boot Integration Tests With WireMock and JUnit 5

Creating stubs

Request Matching

JsonPath

Understood how spring boot encapsulated WireMock as a Spring Cloud


Contract WireMock dependency [1]
Spring Boot documentation teaches how WireMock and RESTDocs work
together

Spring Cloud Contract Overview

I will share with you some challenges and how I solved them.

Challenge:

While I am using Spring Tool Suite (IDE), the following message pops up as a problem
related to default-generateStubs, default-generateTests, and default-convert:

Description Resource Path Location Type  

Execution default-convert of goal org.springframework.cloud:spring-cloud-contr


org.apache.maven.plugin.PluginExecutionException: Execution default-convert of
at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildP
at org.eclipse.m2e.core.internal.embedder.MavenImpl.execute(MavenImpl.java:332
at org.eclipse.m2e.core.internal.embedder.MavenImpl.lambda$8(MavenImpl.java:13
at org.eclipse.m2e.core.internal.embedder.MavenExecutionContext.executeBare(Ma
at org.eclipse.m2e.core.internal.embedder.MavenExecutionContext.execute(MavenE
at org.eclipse.m2e.core.internal.embedder.MavenImpl.execute(MavenImpl.java:137
at org.eclipse.m2e.core.project.configurator.MojoExecutionBuildParticipant.bui
at org.eclipse.m2e.core.internal.builder.MavenBuilderImpl.build(MavenBuilderIm
at org.eclipse.m2e.core.internal.builder.MavenBuilder$1.method(MavenBuilder.ja
at org.eclipse.m2e.core.internal.builder.MavenBuilder$1.method(MavenBuilder.ja
at org.eclipse.m2e.core.internal.builder.MavenBuilder$BuildMethod.lambda$1(Mav
at org.eclipse.m2e.core.internal.embedder.MavenExecutionContext.executeBare(Ma
at org.eclipse.m2e.core.internal.embedder.MavenExecutionContext.execute(MavenE
at org.eclipse.m2e.core.internal.builder.MavenBuilder$BuildMethod.lambda$0(Mav
at org.eclipse.m2e.core.internal.embedder.MavenExecutionContext.executeBare(Ma
at org.eclipse.m2e.core.internal.embedder.MavenExecutionContext.execute(MavenE
at org.eclipse.m2e.core.internal.embedder.MavenExecutionContext.execute(MavenE
at org.eclipse.m2e.core.internal.builder.MavenBuilder$BuildMethod.execute(Mave
at org.eclipse.m2e.core.internal.builder.MavenBuilder.build(MavenBuilder.java:
at org.eclipse.core.internal.events.BuildManager$2.run(BuildManager.java:832) 
at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:45)  
at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:
at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:
at org.eclipse.core.internal.events.BuildManager$1.run(BuildManager.java:316) 
at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:45)  
at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:
at org.eclipse.core.internal.events.BuildManager.basicBuildLoop(BuildManager.j
at org.eclipse.core.internal.events.BuildManager.build(BuildManager.java:392) 
at org.eclipse.core.internal.events.AutoBuildJob.doBuild(AutoBuildJob.java:154
at org.eclipse.core.internal.events.AutoBuildJob.run(AutoBuildJob.java:244)  

at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)  

Caused by: org.apache.maven.plugin.PluginContainerException: Unable to load th


at org.apache.maven.plugin.internal.DefaultMavenPluginManager.getConfiguredMoj
at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildP
... 30 more  

Caused by: org.codehaus.plexus.component.repository.exception.ComponentLookupE


 role: org.apache.maven.plugin.Mojo   

roleHint: org.springframework.cloud:spring-cloud-contract-maven-plugin:2.2.6.R
at org.codehaus.plexus.DefaultPlexusContainer.lookup(DefaultPlexusContainer.ja
at org.codehaus.plexus.DefaultPlexusContainer.lookup(DefaultPlexusContainer.ja
at org.apache.maven.plugin.internal.DefaultMavenPluginManager.getConfiguredMoj
... 31 more  

Caused by: java.util.NoSuchElementException  

at org.eclipse.sisu.plexus.RealmFilteredBeans$FilteredItr.next(RealmFilteredBe
at org.eclipse.sisu.plexus.RealmFilteredBeans$FilteredItr.next(RealmFilteredBe
at org.eclipse.sisu.plexus.DefaultPlexusBeans$Itr.next(DefaultPlexusBeans.java
at org.eclipse.sisu.plexus.DefaultPlexusBeans$Itr.next(DefaultPlexusBeans.java
at org.codehaus.plexus.DefaultPlexusContainer.lookup(DefaultPlexusContainer.ja
... 33 more  

pom.xml line 195 Maven Build Problem 

Solution:

This problem happened because there is not any contract definition at


src/test/resources/contracts and I did not define any Base class which should be used
with the contract to generate the test class automatically. Even after i set the property
spring.cloud.contract.verifier.skip as true at the pom.xml, the error message keep
showing up (source: [1][2][3]). The solution came out after disabling the phase
individually for each execution. (source: https://peter.palaga.org/2020/10/29/skipping-
maven-mojos-properly.html ):

<plugin>

    <groupId>org.springframework.cloud</groupId>

    <artifactId>spring-cloud-contract-maven-plugin</artifactId>

    <version>2.2.7.RELEASE</version>

    <extensions>true</extensions>

    <executions>

        <execution>

        <id>default-convert</id>

        <phase>none</phase>

        </execution>

        <execution>

        <id>default-generateStubs</id>

        <phase>none</phase>

        </execution>

        <execution>

        <id>default-generateTests</id>

        <phase>none</phase>

        </execution>

    </executions>               

    <configuration>

        <testFramework>JUNIT5</testFramework> 

    </configuration>

</plugin> 

Challenge:

At the beer-api-consumer-restdocs project, there is an url that points to beer-api-


producer-restdocs microservice. That url should be updated to point to the stubs
without the need to change de production code when the integration test fired up.

Solution:

Using the @ContextConfiguration from Spring Boot, the WireMockInitializer class was
created to define a random port to be used by the WireMock server to make the beer-
api-producer-restdocs stubs available. Along with the possibility to specify which
properties from application.yml should be overwritten with the new url used by
WireMock.

WireMockInitializer.java

package com.example;

import org.springframework.boot.test.util.TestPropertyValues;

import org.springframework.cloud.contract.wiremock.WireMockSpring;

import org.springframework.context.ApplicationContextInitializer;

import org.springframework.context.ConfigurableApplicationContext;

import org.springframework.util.SocketUtils;

import com.github.tomakehurst.wiremock.common.Slf4jNotifier;

import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTe

public class WireMockInitializer implements ApplicationContextInitializer<Conf

private String[] servicesUrlProperties = { "beer-api-producer-restdocs

@Override

public void initialize(ConfigurableApplicationContext applicationConte


int randomPort = SocketUtils.findAvailableTcpPort(5000, 5100);

applicationContext.getBeanFactory().registerSingleton("Options

setupServicesUrl(randomPort, applicationContext);

private com.github.tomakehurst.wiremock.core.Options setupWireMockConf


com.github.tomakehurst.wiremock.core.WireMockConfiguration fac
factory.port(port);

factory.notifier(new Slf4jNotifier(true));

factory.extensions(new ResponseTemplateTransformer(false));

return factory;

private void setupServicesUrl(int port, ConfigurableApplicationContext


String baseUrl = "http://localhost:" + port;

for (String serviceUrl : servicesUrlProperties) {

TestPropertyValues.of(serviceUrl + "=" + baseUrl).appl


}

application.yml

spring:

  application.name: beer-api-consumer

beer-api-producer-restdocs:

  url: http://localhost:8081

logging:

  level:

    org.springframework.cloud: debug

BeerControllerTest.java

package com.example;

import static org.springframework.test.web.servlet.result.MockMvcResultMatcher


import static org.springframework.test.web.servlet.result.MockMvcResultMatcher

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTeste
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMo
import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;

import org.springframework.http.MediaType;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.web.servlet.MockMvc;

import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

/**

 * @author Marcin Grzejszczak

 */

@SpringBootTest(classes = ClientApplication.class, webEnvironment = WebEnviron


@AutoConfigureMockMvc

@AutoConfigureJsonTesters

@ContextConfiguration(initializers = {WireMockInitializer.class})

//remove::start[]

@AutoConfigureWireMock(stubs = "classpath:/META-INF/com.example/beer-api-produ
//remove::end[]

public class BeerControllerTest extends AbstractTest {

@Autowired MockMvc mockMvc;

@Autowired BeerController beerController;

@Test public void should_give_me_a_beer_when_im_old_enough() throws Ex


//remove::start[]

this.mockMvc.perform(MockMvcRequestBuilders.post("/beer")

.contentType(MediaType.APPLICATION_JSON)

.content(this.json.write(new Person("marcin",
.andExpect(status().isOk())

.andExpect(content().string("THERE YOU GO"));

//remove::end[]

@Test public void should_reject_a_beer_when_im_too_young() throws Exce


//remove::start[]

this.mockMvc.perform(MockMvcRequestBuilders.post("/beer")

.contentType(MediaType.APPLICATION_JSON)

.content(this.json.write(new Person("marcin",
.andExpect(status().isOk())

.andExpect(content().string("GET LOST"));

//remove::end[]

BeerController.java

package com.example;

import java.net.MalformedURLException;

import java.net.URI;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.http.MediaType;

import org.springframework.http.RequestEntity;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.client.RestTemplate;

/**

 * @author Marcin Grzejszczak

 */

@RestController

class BeerController {

private final RestTemplate restTemplate;

@Value("${beer-api-producer-restdocs.url}")

private String urlBeerApiProducer;

BeerController(RestTemplate restTemplate) {

this.restTemplate = restTemplate;

@RequestMapping(method = RequestMethod.POST,

value = "/beer",

consumes = MediaType.APPLICATION_JSON_VALUE)

public String gimmeABeer(@RequestBody Person person) throws MalformedU


//remove::start[]

ResponseEntity<Response> response = this.restTemplate.exchange


RequestEntity

.post(URI.create(urlBeerApiPro
.contentType(MediaType.APPLICA
.body(person),

Response.class);

switch (response.getBody().status) {

case OK:

return "THERE YOU GO";

default:

return "GET LOST";

//remove::end[return]

class Person {

public String name;

public int age;

public Person(String name, int age) {

this.name = name;

this.age = age;

public Person() {

class Response {

public ResponseStatus status;

enum ResponseStatus {

OK, NOT_OK

That is all. Thank you for your attention.

All this approach presented depends on a team where each member implements the
integration tests as each one builds/modifies a microservice.

Denunciar esto

Publicado por
Renan Franca 2 artículos Seguir
Software Engineer | Microservices | Enjoy ☕🍃☸️🤓🍼
Fecha de publicación: 1 año

#springboot #microservices #wiremock #restdocs #stub #integration #test

Recomendar Comentar Compartir 1

Reacciones
Ka
mbiz
H.

Renan Franca
Software Engineer | Microservices | Enjoy ☕🍃☸️🤓🍼

Seguir

Más de Renan Franca


Spring Boot: Teste de Integração
através da simulação de outros
micro serviços

Renan Franca en LinkedIn

You might also like