How to set up different responses for the same request to MockServer?

13.9k views Asked by At

i'm having an issue when setting up the MockServerClient for multiple responses with the exact same request.

I read that with expectations with "Times" this might be done, but i coulnd't make it work with my scenario.

If you call the service with this JSON (twice):

{
    "id": 1
}

The first response should be "passed true", the second "passed false"

Response 1:

{
    "passed":true
}

Response 2:

{
    "passed":false
}

I set up the first request, but how do i set the second one?

import com.nice.project.MyService;
import com.nice.project.MyPojo;
import org.mockito.Mock;
import org.mockserver.integration.ClientAndServer;
import org.mockserver.matchers.TimeToLive;
import org.mockserver.matchers.Times;
import org.mockserver.model.Header;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.Mockito.when;
import static org.mockserver.integration.ClientAndServer.startClientAndServer;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;

@SpringBootTest    
public class Tests{
    
    private static final int PORT = 9998;

    private static ClientAndServer mockServer;

    @Autowired
    private MyService myService;

    @BeforeAll
    public void init(){
        mockServer = startClientAndServer(PORT);
        mockServer
            .when(
                request()
                    .withPath(testUrlValidateTransactionOk).withMethod(HttpMethod.POST.name())
                    .withHeaders(
                        new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString())
                    )
                    .withBody(contains("\"id\":\"1\""))
                ).respond(
            response().withStatusCode(HttpStatus.OK.value())
                .withHeaders(
                    new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString())
                )
                .withBody("{\"passed\":true}"));

        // What do i set here? Or in the snippet before by chaining?
        // mockServer.when()...

    }

    @Test
    void t1{
        //myService will internally call the MockServer

        //FIRST CALL -> Pass
        MyPojo p = myService.call(1);

        assertThat(p.isPassed()).isEqualTo(Boolean.TRUE);

        //SECOND CALL -> No Pass
        MyPojo p2 = myService.call(1);
        assertThat(p2.isPassed()).isEqualTo(Boolean.FALSE);

    }
}

Dependencies (relevant):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.4</version>
    </parent>

    <groupId>com.nice.project</groupId>
    <artifactId>testing</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>test</name>
    <description>Testing</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <httpclient.version>4.5.13</httpclient.version>
        <mock-server.version>5.11.2</mock-server.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>   
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>               
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-json</artifactId>
        </dependency>   
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>   
        
        <!--HTTP CLIENT-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        
        <!--TEST-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mock-server</groupId>
            <artifactId>mockserver-netty</artifactId>
            <version>${mock-server.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mock-server</groupId>
            <artifactId>mockserver-client-java</artifactId>
            <version>${mock-server.version}</version>
            <scope>test</scope>
        </dependency>       

    </dependencies>

</project>

Thank you in advance.

2

There are 2 answers

2
Gabriel Vitale On BEST ANSWER

After following and diving into the documentation and testing.

I found that you can specify a "Times" that an expectation will be match which solves perfectly my problem.

Link: https://www.mock-server.com/mock_server/creating_expectations.html

For every expectation i used "Times.exactly(1)".

The way this works is that an expectation is added to the list, when it's matched it will be consumed, and removed from the list, leaving the following ones.

If no expectation is found for a call it will return a 404 from the mock server.

Link for examples from documentation: https://www.mock-server.com/mock_server/creating_expectations.html#button_match_request_by_path_exactly_twice

Correct code:

//The first call will land here, and then this expectation will be deleted, remaining the next one
mockServer
.when(
    request()
        .withPath(testUrlValidateTransactionOk).withMethod(HttpMethod.POST.name())
        .withHeaders(
            new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString())
        )
        .withBody(
            json("{\"id\":1}",
                MatchType.ONLY_MATCHING_FIELDS)),
        Times.exactly(1)
    ).respond(
response().withStatusCode(HttpStatus.OK.value())
    .withHeaders(
        new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString())
    )
    .withBody("{\"passed\":true}"));


//After the first call this will be consumed and removed, leaving no expectations
mockServer
.when(
    request()
        .withPath(testUrlValidateTransactionOk).withMethod(HttpMethod.POST.name())
        .withHeaders(
            new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString())
        )
        .withBody(
            json("{\"id\":1}",
                MatchType.ONLY_MATCHING_FIELDS)),
        Times.exactly(1)
    ).respond(
response().withStatusCode(HttpStatus.OK.value())
    .withHeaders(
        new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString())
    )
    .withBody("{\"passed\":false}"));
0
DV82XL On

You can create a sequence of responses by wrapping the when/request/response behavior in a method and calling it multiple times, like this:

private void whenValidateTransactionReturn(boolean isPassed) {
    mockServer
        .when(
            request()
                .withPath(testUrlValidateTransactionOk)
                .withMethod(HttpMethod.POST.name())
                .withHeaders(
                    new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString()))
                .withBody(contains("\"id\":\"1\"")))
        .respond(
            response()
                .withStatusCode(HttpStatus.OK.value())
                .withHeaders(
                    new Header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString()))
                .withBody("{\"passed\":" + isPassed + "}"));
  }

Then you can call this method multiple times:

@Test
void testValidationFailsSecondTime() {
    whenValidateTransactionReturn(true);
    whenValidateTransactionReturn(false);
    //
    // Test logic
    //
    // mockServer.verify(...);
}