Go WireMock with the Testcontainers Go module!

Oleg Nenashev
Developer Advocate and Community Builder
July 19, 2023

We are happy to announce our latest experimental project - the new WireMock module for Testcontainers Go. This module allows you to provision the WireMock server as a standalone container within Golang unit tests. It brings the full power of WireMock Java to the modern Golang ecosystem, including cloud native applications and many Golang-based developer utilities.

Just as a teaser, this is a real-time recording of a API mock test in Golang with WireMock and its Testcontainers Go module. Just two seconds on a commodity machine!

WireMock and Golang

WireMock is widely adopted beyond the JVM ecosystem thanks to many implementations, integrations and extensions created by its hundreds of contributors. There are a number of projects in the WireMock Ecosystem that specifically target Golang use-cases. The most prominent examples are:

  • Go WireMock - a Golang client for the WireMock Administrative REST API, by Ruslan Isamukhametov. It allows interacting with WireMock standalone instances and WireMock Cloud, offering a compatible API
  • gRPC WireMock project - experimental gRPC adapter implemented in Java and running as a proxy in front of WireMock. This project was created by @Adven27 and then adopted to the WireMock community. A native gRPC extension is on our roadmap (GitHub issue)

Why Testcontainers Go?

Testcontainers is a popular tool for integration testing with containers, eliminating the need for pre existing environments. It provides lightweight, throwaway container instances of common databases, web browsers, developer services, or anything that can run in a Docker container. The Testcontainers framework has many implementations, including Java and Golang. You can find more information on its website.

The Testcontainers Golang implementation is one of the most powerful Testcontainers implementations providing many advanced features including on-demand image builds from test definitions, container suspension between runs, support for Docker Compose to use multiple container instances as a system-under-test, and more. By leveraging Testcontainers as a platform for WireMock, we can provide Golang developers with excellent developer experiences for API mocking with WireMock, and this is what we are experimenting with in this project..

Testcontainers and WireMock

I initially started this experiment for my talk about WireMock8s - WireMock deployments on Kubernetes, aka “WireMock8s” as I called it in my CNCF Switzerland Meetup talk. The idea was to enable developers to verify their Golang applications, and hence a small prototype was born.

Getting started

If you are interested in trying out the projects, please be our guest! We will appreciate any feedback and suggestions.

Prerequisites. The module supports the official WireMock Docker images 2.35.0-1 or above. Custom images are supported too as long as they follow the same CLI and API structure. It supports Golang versions 1.17 or above, so all modern Golang projects should be compatible with it.

Quick Start

If you have the Golang development environment ready to go and do not want a step by step guide, you can just clone the project’s repository in wiremock/wiremock-testcontainers-go/ and run “go build” and then “go test”. There is also a quickstart example that follows the guide below. Any pull requests will be welcome ;-)

Step 1. Create your test project

Create the go.mod file with the following content:

module wiremock.org/testcontainers-go-quickstart

go 1.19

require (
    github.com/pkg/errors v0.9.1
    github.com/wiremock/wiremock-testcontainers-go v1.0.0-alpha-5
)

Step 2. Create the test file

Create a quickstart_test.go file with the package name. Add dependencies we will need for this demo, and also create the test stub:

package testcontainers_wiremock_quickstart

import (
  "context"
  . "github.com/wiremock/wiremock-testcontainers-go"
  "testing"
)

func TestWireMock(t *testing.T) {
    // Our future work will be here
}

Step 3. Create the test resource

For our demo, we will need to expose a test WireMock Mapping. Create the hello-world.json file with the following content:

{
  "request": {
    "method": "GET",
    "url": "/hello"
  },

  "response": {
    "status": 200,
    "body": "Hello, world!"
  }
}

Step 4. Add Testcontainers initialization

In the func TestWireMock(t *testing.T) snippet, add the following code:

// Create Container
ctx := context.Background()
container, err := RunDefaultContainerAndStopOnCleanup(ctx,
    WithMappingFile("hello", "hello-world.json"),
)
if err != nil {
    t.Fatal(err)
}

Step 5. Add HTTP Request

Now, we will need to send an HTTP request to our test API:

func TestWireMock(t *testing.T) {
    // ... Previous initialization code

    // Send a simple HTTP GET request to the mocked API
    statusCode, out, err := SendHttpGet(container, "/hello", nil)
    if err != nil {
        t.Fatal(err, "Failed to get a response")
    }

    // ... Validation will be here
}

We will also need a utility method for sending requests (API will be added in the next releases):

func SendHtpGet (url string, endpoint string) (int, string, error) {
req, err := http. NewRequest (http MethodGet, url+endpoint, nil)
if err != nil {
return -1, "", errors.Wrap (err, "unable to complete Get request")
}
res, err := http. DefaultClient.Do(req)
if err != nil {
return -1, "", errors.Wrap(err, "unable to complete Get request")
}
out, err := io. ReadAll (res .Body)
if err != nil {
return -1, "", errors.Wrap (err, "unable to read response data")
}
return res. StatusCode, string(out), nil
}

Step 6. Verify the response

Now, add the verification logic that will check correctness of the WireMock response:

func TestWireMock(t *testing.T) {
    // ... Previous initialization code

    // ... Previous HTTP request send code

    // Verify the response
    if statusCode != 200 {
        t.Fatalf("expected HTTP-200 but got %d", statusCode)
    }

    if string(out) != "Hello, world!" {
        t.Fatalf("expected 'Hello, world!' but got %v", string(out))
    }
}

Step 7. Run the tests!

And that’s it! Once you run “go test” to verify the project. You will get the following output: 

Go test execution log

You can keep extending the integration tests in your project by adding more tests that provision the WireMock container. To optimize the container usage efficiency and test execution times, follow the tips in the Testcontainers Go documentation.

Our wishlist

This module is just an alpha version that gets the basic WireMock functionality available to Golang developers. A lot more needs to be done before we would consider the module for Testcontainers Go module in the future. A few items that we have on the list:

  • Alignment with go-wiremock that implements a WireMock Standalone client interacting with the service or with WireMock Cloud via REST API. it would be great to have seamless organization   
  • Better integrations with WireMock features, especially extension management and the record and playback features
  • Better support of gRPC in WireMock and its Testcontainers module for Go. It might be done via the existing grpc-wiremock proxy or, as we want, through the native gRPC extension which is on our roadmap (GitHub issue)

All the features listed above are considered but not scheduled at the moment. Your feedback and contributions is something that we would appreciate to properly prioritize the story and assess its value for the end users. So...

Contributing!

As for any other WireMock project, we are looking for contributions! Check out the open issues in the wiremock/wiremock-testcontainers-go repository, and please feel free to submit a pull request with any patch you have in mind.

References

/

Latest posts

Have More Questions?