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

2

7

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.

Mercurial answered 14/4, 2021 at 11:54 Comment(2)
@DV82XL Hey sorry for the delay, i needed to solve many other things at work, but i used your answer until i found in the documentation the "Times" parameter for exactly when i needed. Thank you for the help. I posted the official documentation way to approach this problem for future developers.Mercurial
Great, glad you solved your problem! Just to be clear, did my solution work? It looks like we're doing the same thing, except I wrapped mockServer.when().respond() in a method and you added Times.exactly(1). I was under the impression when Times.exactly() is omitted, it defaults to 1, which would make our two answers identical.Clementinaclementine
M
8

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}"));
Mercurial answered 3/5, 2021 at 8:4 Comment(2)
We can use Times.once(), if expect to run mockServer with this response exactly 1 time.Detoxicate
For some reason, @ Springboottest turns out to be better alternative in some cases.Bina
C
1

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(...);
}

Clementinaclementine answered 16/4, 2021 at 0:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.