DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Multithreading in Modern Java: Advanced Benefits and Best Practices
  • Optimizing Java Applications for Arm64 in the Cloud
  • JPlus: A Modern Java Superset Language
  • Monitoring Java Microservices on EKS Using New Relic APM and Kubernetes Metrics

Trending

  • Product-Led Software Delivery: Intelligent Platforms for DevOps at Scale
  • RAG Done Right: When to Use SQL, Search, and Vector Retrieval and How To Combine Them
  • AWS Kiro: The Agentic IDE That Makes Specs the Unit of Work
  • Architecting Sub-Microsecond HFT Systems With C++ and Zero-Copy IPC
  1. DZone
  2. Coding
  3. Java
  4. Spring WebFlux Support in Pact JVM

Spring WebFlux Support in Pact JVM

By 
Paweł Pelczar user avatar
Paweł Pelczar
·
Nov. 16, 20 · Review
Likes (1)
Comment
Save
Tweet
Share
7.5K Views

Join the DZone community and get the full member experience.

Join For Free

Pact JVM has had Spring support for a while but on the provider side it was limited only to mocked Spring MVC. Starting with version 4.1.7  Pact also supports Spring WebFlux endpoints. 

In this article I'll demonstrate usage of Pact Consumer Driven Contracts to test Spring WebFlux provider endpoints and its consumers. I'll start with a short introduction to consumer driven contract testing and Spring WebFlux. Then I'll explain how to connect those two technologies together to produce a contract and with that contract verify both consumer and provider of a service.

What Is Pact Consumer Driven Contract Testing

Consumer Driven Contract Testing is a pattern for testing compatibility between consumers and providers of services. The idea is that between those two there's established a pact describing their interactions. If both parties fulfills assumptions made in the pact, a test passes successfully.

"Consumer driven" approach means that the force responsible for contract evolution are consumers of the service, not a provider.

DiUS Pact is a set of testing frameworks supporting Consumer Driven Contract Testing. It focuses mostly on HTTP communication but for some platforms also queueing support is available. Implementations are targeting a majority of popular runtime environments like JVM, .NET, JavaScript, Ruby to name a few.

What Is Spring WebFlux

Spring WebFlux is a reactive web framework introduced in Spring 5. It is a counterpart of the well known Spring MVC but relies on non-blocking APIs provided by reactive streams. The framework is capable of handling heavy loads on a small number of threads while consuming fewer hardware resources. 

WebFlux offers two programming models: 

  • annotated controllers
  • functional endpoints

Although Pact supports both in this article I'll focus on functional endpoints style.

How Does It Work Together

As it is a consumer responsibility to provide the contract, pact tests are written for the consumer at first. Each of those tests contains description of interactions between consumer and provider, e.g. HTTP requests, expected responses and assertions verifying communication. Pact framework takes care of starting a mock HTTP server equipped with mock responses and executes tests against that server.

When a test is executed, Pact framework creates a dedicated contract (JSON) file. The same file is then used to check if the provider of the service conforms to the pact. On the server side, the framework will execute requests recorded in the contract file and verify responses. We can choose to execute a test against the service as a whole or only its slice responsible for HTTP communication. In this article I will cover the letter.

Client Side Test

Let's assume that the provider exposes an HTTP endpoint. It responds to HTTP GET method with JSON representation of Foo objects collection. Let’s use the Spock framework to code the consumer pact test.

First we have to define a pact inside the given section of our test. For that we will use ConsumerPactBuilder class, provided by Pact library:

Groovy
xxxxxxxxxx
1
16
 
1
given:
2
def pact = ConsumerPactBuilder.consumer("consumerService")
3
    .hasPactWith("providerService")
4
    .uponReceiving("sample request")
5
    .method("GET")
6
    .path("/foo")
7
    .willRespondWith()
8
    .status(200)
9
    .headers(["Content-Type": "application/json"])
10
    .body("""
11
            [
12
                {"id": 1, "name": "Foo"},
13
                {"id": 2, "name": "Bar"}
14
            ]
15
        """.stripIndent())
16
    .toPact()


We can see here that contract defines interaction named sample request between consumer consumerService and provider providerService. As a reaction to HTTP GET request on /foo path, provider should respond with HTTP status 200 and response’s body section should contain representation of two Foo objects.

Now let's code assertions part and actual invocation of our client in when section:

Groovy
xxxxxxxxxx
1
14
 
1
when:
2
def result = ConsumerPactRunnerKt.runConsumerTest(
3
    pact, MockProviderConfig.createDefault()) { mockServer, context ->
4

          
5
    def webClient = WebClient.create(mockServer.getUrl())
6
    def consumerAdapter = new ConsumerAdapter(webClient)
7

          
8
    def resultFlux = consumerAdapter.invokeProvider()
9

          
10
    StepVerifier.create(resultFlux)
11
        .expectNext(new Foo(1l, 'Foo'))
12
        .expectNext(new Foo(2l, 'Bar'))
13
        .verifyComplete()
14
}


To execute the test we will use the ConsumerPactRunnerKt class provided by Pact library. Before executing code in the closure, method runConsumerTest will start a temporary server that will respond to HTTP requests defined earlier in the pact. It will also save a contract in the form of a JSON file that would be shared with the service provider. The last parameter of the mentioned method is a code block containing a call to our service client. In this case we will initiate a reactive webClient preconfigured with a mock HTTP server URL. Then we will create an instance of our adapter (ConsumerAdapter) and call invokeProvider() method responsible for HTTP interaction with the provider. As the result of that interaction would be reactive Flux we can use StepVerifier from the Reactor project to verify its correctness.

The final part would be to check if pact verification has passed in then section:

Groovy
xxxxxxxxxx
1
 
1
then:
2
result == new PactVerificationResult.Ok()


Here we can check if StepVerifier didn’t generate an exception (it would be wrapped inside PactVerificationResult.Error class), some mandatory interaction wasn’t performed or was inconsistent with contract definitions.

Let’s write the code for the sample consumer used in our test. It would be conventional usage of reactive WebClient for reading data from /foo endpoint:

Groovy
xxxxxxxxxx
1
 
1
public Flux<Foo> invokeProvider() {
2
    return webClient
3
            .get()
4
            .uri("/foo")
5
            .accept(MediaType.APPLICATION_JSON)
6
            .retrieve()
7
            .bodyToFlux(Foo.class);
8
}


Now we are ready to run the test. It will start a Pact provided HTTP server, invoke that server from the client's adapter and verify the response provided by client class. As an additional outcome of the test Pact framework will produce JSON representation of pact in build/pacts directory. We should then share that contract file with tests verifying a provider of the service.

Server Side Test

As we already have the pact file generated, the integration test for a provider would be a bit more straightforward. This time we will use JUnit framework as Spock's given–when–then pattern wouldn't be very useful here:

Java
xxxxxxxxxx
1
17
 
1
@RunWith(RestPactRunner.class)
2
@Provider("providerService")
3
@PactFolder("pacts")
4
public class ProviderRouterPactTest {
5

          
6
    @TestTarget
7
    public WebFluxTarget target = new WebFluxTarget();
8

          
9
    private ProviderHandler handler = new ProviderHandler();
10
    private RouterFunction<ServerResponse> routerFunction
11
            = new ProviderRouter(handler).routes();
12

          
13
    @Before
14
    public void setup() {
15
        target.setRouterFunction(routerFunction);
16
    }
17
}


As we can notice code doesn't contain any test methods. It’s because test requests and assertions are already present in the contract file. SpringRestPactRunner (pointed in @RunWith annotation) will take care of executing pact test cases and verification of responses. We have to yet inform the test runner about location of contract files (in this case with @PactFolder annotation we are pointing directory on disk) and name of our provider (with @Provider annotation). Pact engine will use that name to match contracts dedicated to test particular providers. It is also used in consumer tests inside pact definition, e.g. hasPactWith("providerService").

The @TestTarget annotation is responsible for choosing the type of tested component. In the case of WebFlux endpoints it would be an instance of WebFluxTarget. It should be configured with the routerFunction that we use in our provider's code. Convenient place to do it would be the setup() method of a test.

We should also mention ProviderHandler and ProviderRouter classes. These are implementation parts of the sample provider. Router is responsible for building RouterFunction instance that binds URL paths with code of the handler:

Java
xxxxxxxxxx
1
15
 
1
@Configuration
2
@RequiredArgsConstructor
3
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
4
class ProviderRouter {
5

          
6
    ProviderHandler handler;
7

          
8
    @Bean
9
    RouterFunction<ServerResponse> routes() {
10
        return route()
11
                .GET("/foo", accept(APPLICATION_JSON), handler::getFoo)
12
                .build();
13
    }
14

          
15
}


The handler::getFoo method from sample above is responsible for returning Mono with provider’s response:

Java
 
x
 
1
Mono<ServerResponse> getFoo(ServerRequest request) {
2
    return ServerResponse
3
            .ok()
4
            .contentType(APPLICATION_JSON)
5
            .body(Flux.just(
6
                    new Foo(1l, "Foo"),
7
                    new Foo(2l, "Bar")
8
            ), Foo.class);
9
}


The handler method returns two Foo objects as it is expected by the contract between provider and consumer.

Running the provider pact test will now load the contract file, invoke a handler matched by URL path and verify if the generated response is consistent with the one stated in the contract.

Summary

Pact Consumer Driven Contracts is a very useful tool for ensuring consistency between elements of a complex system. Certainly, its advantage is the wide range of supported technologies, which allows for contract-based testing in a heterogeneous environment. Now yet another technology has joined the framework - the Spring WebFlux, thanks to that we have gained the ability to perform tests against reactive providers.

Source code for examples used in this article is available in Github repository.

References

  • Pact JVM Github page
  • Spring WebFlux documentation
  • Consumer-Driven Contracts: A Service Evolution Pattern, Ian Robinson
  • Pact WebFlux integration example on Github
PACT (interaction design) Java (programming language) Java virtual machine Spring Framework integration test

Published at DZone with permission of Paweł Pelczar. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Multithreading in Modern Java: Advanced Benefits and Best Practices
  • Optimizing Java Applications for Arm64 in the Cloud
  • JPlus: A Modern Java Superset Language
  • Monitoring Java Microservices on EKS Using New Relic APM and Kubernetes Metrics

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook