Deep understanding of Mock technology and basic use of Wire Mock framework

Posted by Craig_H on Thu, 09 Dec 2021 05:22:08 +0100

1, Key concepts

  1. spy: it monitors the calling process without forwarding capability. It mainly monitors the calling process (similar to packet capture and F12)
  2. stub: returns the implementation of a fixed value. It cannot be changed dynamically in the test (it means that it cannot be changed dynamically according to the real value). It is relatively rigid (similar to Charles' map local function, without going through the back end, similar to a baffle)
  3. Proxy: use the proxy protocol to forward the request and return the real content, which can be forwarded, monitored, or even modified (similar to Charles' rewrite function, forward the request to the real service. After the service returns the response, make some modifications to the response and forward it to the front end)
  4. Fake: a fake implementation is used to replace the real implementation, but some shortcuts are made in the implementation (for example, if a service fails in a large cluster, it takes a long time to repair. At this time, a simple version of logic can be written to replace it, which is usually done by development)
  5. Mock: it is dynamically created by the mock library and can provide functions similar to spy, stub and proxy. Mock is a special kind of fake, which emphasizes controllability
  • mock on stub: returns fixed value data directly
  • mock on proxy: use proxy to forward and modify the returned data

2, Application scenario

1, stub application scenario:

  • Moco: https://github.com/dreamhead/moco
  • The lightweight stub framework is started with the command line to facilitate operation on the server

2, fake application scenario:

  • H2 Database Engine: lightweight memory Sql, which does not occupy resources. It is an api of JDBC, and its operation mode is consistent with mysql and so on. This framework can be used if it is in the commissioning phase or the focus is not on the db layer.

3, mock application scenario:

  • Wire Mock: at present, it is a popular mock framework. The community is very active and the writing method is very elegant. Its functions are similar to Charles, but it is more flexible because it can be coded
  • Official website: http://wiremock.org/
  • Let's focus on the basic configuration and use of this framework

1. Introduce dependency

  <groupId>org.example</groupId>
    <artifactId>wiremock_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-runner</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-launcher</artifactId>
            <version>1.7.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-console-standalone</artifactId>
            <version>1.7.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.github.tomakehurst</groupId>
            <artifactId>wiremock-jre8</artifactId>
            <version>2.31.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>net.lightbody.bmp</groupId>
            <artifactId>browsermob-core</artifactId>
            <version>2.1.5</version>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>4.4.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M5</version>
                <configuration>
                    <includes>
                        <include>**</include>
                    </includes>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>3.0.0-M5</version>
            </plugin>
        </plugins>
    </build>

2. Basic demo

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer;
import org.junit.jupiter.api.Test;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;

public class Demo_01_Base {
    @Test
    public void start() {
        int port = 8089;
        // Instantiate wirmockServer object
        WireMockServer wireMockServer = new WireMockServer(
                wireMockConfig()
                        // Configure port number
                        .port(port)
                        // Configure global response template
                        .extensions(new ResponseTemplateTransformer(true))
        );
        // Start mock service
        wireMockServer.start();
        // You need to set the port number again, otherwise an error will be reported
        configureFor(port);
        // Configure a stub in the mock service, similar to Charles' mapLocal function
        stubFor(get(urlEqualTo("/some/thing"))
                // Configure the returned body, header and statusCode
                .willReturn(
                        aResponse()
                                .withStatus(200)
                                .withHeader("content-Type", "application/json")
                                .withBody("this is my first wiremock demo!")
                ));
        System.out.println("http://localhost:" + port);
        // Wait for 10s. If you don't wait, the service will be stopped directly and can't be started
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // wiremock reset
        WireMock.reset();
        // wiremock stop (you can't call it next time if you don't stop)
        wireMockServer.stop();
    }
}

3. Configuration of common request bodies

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer;
import org.junit.jupiter.api.Test;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;

public class Demo_02_RequestMatching {
    @Test
    public void start() {
        int port = 8090;
        // Instantiate wirmockServer object
        WireMockServer wireMockServer = new WireMockServer(
                wireMockConfig()
                        // Configure port number
                        .port(port)
                        // Configure global response template
                        .extensions(new ResponseTemplateTransformer(true))
        );
        // Start mock service
        wireMockServer.start();
        // You need to set the port number again, otherwise an error will be reported
        configureFor(port);
        /**
         *   Configure a stub in the mock service, similar to Charles' mapLocal function
         *   These configurations are "and" relationships, which must be satisfied before the request can succeed
         */

        // The path of the url is equal to "everything"
        stubFor(any(urlPathEqualTo("everything"))
                //Match rules through header
                .withHeader("Accept", containing("xml"))
                //Matching rules through cookie s
                .withCookie("session", matching(".*12345.*"))
                //Matching rules through QueryParam
                .withQueryParam("search_term", equalTo("WireMock"))
                //Match rules through withBasicAuth
                .withBasicAuth("jeff@example.com", "jeffteenjefftyjeff")
                //Matching rules through RequestBody
                .withRequestBody(matchingJsonPath("$.a", equalTo("1")))
                .willReturn(aResponse().withStatus(200)
                        .withHeader("Content-Type", "text/plain")
                        .withBody("pass!")));
        System.out.println("http://localhost:" + port);
        // Wait for 10s. If you don't wait, the service will be stopped directly and can't be started
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // wiremock reset
        WireMock.reset();
        // wiremock stop (you can't call it next time if you don't stop)
        wireMockServer.stop();
    }
}


Do a test

public class Demo_02_Test_RequestMatching {
    @Test
    void testRequestMatching() {
        given().log().all()
                .auth().preemptive().basic("jeff@example.com", "jeffteenjefftyjeff")
                .header("Accept", "xml")
                .cookie("session", "123456")
                .body("{\"a\":1,\"b\":2}")
                .queryParam("search_term", "WireMock")
                .when()
                .post("http://localhost:8099/everything").
                then().log().all()
                .extract();
    }
}

Request succeeded!

  • View the management background of wireMock: ip:port/__admin

    4. Configuration of common responders
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer;
import org.junit.jupiter.api.Test;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;

/**
 * Common response configurations
 */
public class Demo_03_Response {
    @Test
    public void start() {
        int port = 8091;
        // Instantiate wirmockServer object
        WireMockServer wireMockServer = new WireMockServer(
                wireMockConfig()
                        .port(port)
                        .extensions(new ResponseTemplateTransformer(true))
        );
        // Start mock service
        wireMockServer.start();
        configureFor(port);
        stubFor(get(urlEqualTo("/some/thing"))
                // Configure the returned body, header and statusCode
                .willReturn(
                        aResponse()
                                .withStatus(200)
                                .withHeader("content-Type", "application/json")
                                .withBody("this is my first wiremock demo!")
                ));
        // ok() indicates that the returned response status code is 200
        stubFor(delete("/fine")
                .willReturn(ok()));
        // Return response status code + body
        stubFor(get("/fineWithBody")
                .willReturn(ok("body")));
        // The returned response body is in json format
        stubFor(get("/returnJson")
                .willReturn(okJson("{\"status\":\"success\"}")));
        // Request redirection
        stubFor(get("/redirect")
                .willReturn(temporaryRedirect("/new/place")));
        // Unauthenticated
        stubFor(get("/unauthorized")
                .willReturn(unauthorized()));
        // Configure response status code
        stubFor(get("/statusCode")
                .willReturn(status(418)));

        System.out.println("http://localhost:" + port);
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // wiremock reset
        WireMock.reset();
        // wiremock stop (you can't call it next time if you don't stop)
        wireMockServer.stop();
    }
}

5. Matching priority

/**
 * Priority matching:
 * 1,Match high priority
 * 2,Then match according to the url
 */
public class Demo_04_Priority {
    @Test
    public void start() {
        int port = 8093;
        // Instantiate wirmockServer object
        WireMockServer wireMockServer = new WireMockServer(
                wireMockConfig()
                        .port(port)
                        .extensions(new ResponseTemplateTransformer(true))
        );
        // Start mock service
        wireMockServer.start();
        configureFor(port);
        // Match specific
        stubFor(get(urlEqualTo("/some/thing")).atPriority(2)
                // Configure the returned body, header and statusCode
                .willReturn(
                        aResponse()
                                .withStatus(200)
                                .withHeader("content-Type", "application/json")
                                .withBody("this is my first wiremock demo!")
                ));
        // Pass through using regular
        stubFor(get(urlMatching("/some/.*")).atPriority(12)
                .willReturn(
                        aResponse()
                                .withStatus(401)
                                .withBody("match any")
                ));
        // Bottom logic
        stubFor(any(anyUrl()).atPriority(1)
                .willReturn(
                        aResponse()
                                .withStatus(402)
                                .withBody("no match")
                ));
        System.out.println("http://localhost:" + port);
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // wiremock reset
        WireMock.reset();
        // wiremock stop (you can't call it next time if you don't stop)
        wireMockServer.stop();
    }
}

6. Recording and playback

wireMock also provides an interface for recording and playback. The function is similar to the save response in Charles

  • Specific operation:
  1. Enter web address: http://ip:port/__admin/recorder/

  2. Enter the URL you want to record in this interface, and then click the recording option

  3. Open a new page and enter http://ip:port , it will automatically route to the URL you entered. At this time, you can do some operations on the page

  4. Click stop to stop recording

    At this time, you will see the mapping file in the resources directory, which contains the recorded requests

  5. We can modify the contents

    Then restart and access http://ip:port , you can mock successfully

  6. Proxy: use the proxy protocol to forward the request and return the real content

/**
 * Forward the request and return the real content using the proxy protocol
 */
public class Demo_05_Proxy {
    @Test
    public void start() {
        int port = 8021;
        // Instantiate wirmockServer object
        WireMockServer wireMockServer = new WireMockServer(
                wireMockConfig()
                        // Configure port number
                        .port(port)
                        // Configure global response template
                        .extensions(new ResponseTemplateTransformer(true),
                                // Create a new response processor
                                new ResponseTransformer() {
                                    // Replace the text in the response
                                    @Override
                                    public Response transform(Request request, Response response, FileSource fileSource, Parameters parameters) {
                                        return Response.Builder.like(response)
                                                .body(response.getBodyAsString().replace("Other Utilities","My proxy Utilities"))
                                                .build();
                                    }

                                    @Override
                                    public String getName() {
                                        return "proxy demo";
                                    }
                                })
        );
        // Start mock service
        wireMockServer.start();
        configureFor(port);
        // Configure a stub in the mock service
        stubFor(get(urlMatching("/.*"))
                .willReturn(
                        aResponse()
                                // Set agent
                                .proxiedFrom("https://httpbin.ceshiren.com")
                ));
        System.out.println("http://localhost:" + port);
        try {
            Thread.sleep(10000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // wiremock reset
        WireMock.reset();
        // wiremock stop (you can't call it next time if you don't stop)
        wireMockServer.stop();
    }
}

After startup, access the ip:port and the modification is successful!

Topics: Front-end Testing mock