Testing Microservices with Docker and Docker Compose
Overview
The current post talks about local testing of micro-service interactions in Docker Compose environment and stubbing some of these micro-services in stubby4j, an HTTP/1.1, HTTP/2 and WebSockets API stub server. The tutorial emphasizes the usage of stubby4j in order to demonstrate how it can help with testing of micro-services.
Docker Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.
https://stubby4j.com is a tool for stubbing external systems in both Docker and non-containerized environments for integration, contract and behavior testing. stubby4j allows you to configure (i.e.: to stub) various failure/success scenarios with simple HTTP responses, as well as complex HTTP responses. See stubby4j key features for more information.
Introduction
When developing and testing microservices, it is good practice to focus on testing interactions of the service with its direct upstream/downstream dependencies.
- Testing that a service makes requests with expected headers and/or parameters,
- Testing that the service exposes the right endpoints,
- Testing that the service handles success/failure responses as expected and knows how to gracefully handle unexpected failures; or
- Testing that the service interacts properly with external APIs.
In order to perform such tests, you would have to fake your indirect dependencies. Faking your dependencies can be easily achieved with stubby4j. It helps to address the use cases outlined below (this is by no means an exhaustive list).
- Simulate responses from a real server and don’t care (or cannot) to send requests over the network
- Stub out external web services (when developing & testing locally) in a Docker based micro-service architecture
- Verify that your application behaves as expected upon WebSocket server pushes
- Verify that your code makes HTTP/1.1 or HTTP/2 (over TLS) requests with all the required parameters and/or headers
- Avoid negative productivity impact when an external API you depend on doesn’t exist or isn’t complete
- Simulate edge cases and/or failure modes for your web service that the real remote API won’t reliably produce
- Inject faults, where after X good responses on the same URI your web service gets a bad one
- Verify that your code makes HTTP requests with all the required parameters and/or headers
- Verify that your code correctly handles HTTP response error codes
There are a number of use cases where you’d want to use HTTP stub server in your development/QA environment. If you are a Software Engineer
/Test Engineer
/QA
, then the above should hit close to home for you.
In this tutorial we will learn how to test microservices running in Docker environment by leveraging the Docker Compose and dependency stubbing with stubby4j
.
Approach
We use docker compose to spin up two local Docker containers in an isolated environment using the definitions in the local docker-compose.yaml
Flight Booking
web service (starts on0.0.0.0:8080
). A REST-ful web service that fetches some information about a fake flight booking which consists from fake user account and fake user reservation information. The service exposes a single endpoint/api/v1/flights/1
and relies on two downstream web services for theAccount
andReservation
information.
Please note thatAccount
andReservation
services do not exist. The idea here is to assume that they are still being developed and thus need to be stubbed for our local testing in the meanwhile.stubby4j
, an HTTP stub server (starts on0.0.0.0:8882
). The endpoints of the two web servicesAccount
andReservation
thatFlight Booking
consumes are stubbed in stubby4j.
The main idea behind the current exercise is to test the behavior of the Flight Booking
service when the /api/v1/flights/1
API is invoked and its downstream dependencies (i.e.: the stubbed Account
and Reservation
services) produce the following HTTP response codes:
Stubbing dependencies
stubby4j
stubs defined in the following YAML config stubby4j-yaml-config/stubs.yaml:
The Account
service specific stub are defined in stubby4j-yaml-config/include-account-service-stubs.yaml:
while the Reservation
service specific stubs are defined in stubby4j-yaml-config/include-reservation-service-stubs.yaml:
When creating stub definitions, you can start by crafting a simple HTTP response by hand inside the YAML config. Unfortunately this method of stubbing makes it harder to define realistic responses and probably won’t scale as your project grows.
If you have a dump of a realistic HTTP response from a real blackened in a local file, stubby4j
can load it via the file
YAML property are runtime, i.e.: stubby4j-yaml-config/include-reservation-service-stubs.yaml
Alternatively, you can use Record & Replay or Request proxying stubby4j
features to record an HTTP response from a real backend for automatic replaying. This method of stubbing can scale better with your project development lifecycle.
To understand in more depth the stubby4j
configuration options beyond the current tutorial, see the HTTP Endpoint configuration HOWTO for more information.
Building & Running
Building
From root folder with stubby4j configuration run $ docker-compose up
. In another terminal you can query the logs from the command line or if running a Docker for Desktop GUI, you can select the logs from an active container.
Explanation
The command builds the Flight Booking
image using the local Dockerfile (this can take a few minutes, please be patient) and fetches pre-built stubby4j
Docker image from https://hub.docker.com/r/azagniotov/stubby4j
Running
Make a cURL (or in Postman / web browser) request:
$ curl -X GET "http://localhost:8080/api/v1/flights/1"
IMPORTANT: Docker on MacOS Caveats
If your cURL requests to
http://localhost:8080/api/v1/flights/1
fails with Connection refused, it means that the mapping of host machine port8080
to the Docker container port8080
did not work. You may need to setup port forwarding for port8080
inVirtualBox
:Settings
=>Network
=>Adapter 1 (NAT)
=>Advanced
=>Port Forwarding
.
Flight Booking service responses
Flight Booking
responds with 200 OK
& the following JSON response (use-case #1 in the aforementioned table):
{
"reservation" : "p5e20A9hSi2IsV0soVpafQ==",
"name" : "Leanne Graham",
"phone" : "1-770-736-8031 x56442",
"basePrice" : "USD10.00",
"taxes" : "EUR19.62"
}
Flight Booking
responds with 500 Internal Server Error
& the following JSON response (use-case #2 in the aforementioned table):
{"code": 500, "message": "Server.ValidationException"}
Flight Booking
responds with 500 Internal Server Error
& the following JSON response (use-case #3 in the aforementioned table):
{"code": 500, "message": "null"}
Flight Booking
responds with 500 Internal Server Error
& the following JSON response (use-case #4 in the aforementioned table):
{"code": 500, "message": "Internal Server Error"}
Flight Booking
responds with 500 Internal Server Error
& generic Tomcat error due to socket read timeout (use-case #5 in the aforementioned table). This is an example of a use case which Flight Booking
service does not handle gracefully - request read timeout.
Explanation
Upon a GET
request to the aforementioned http://localhost:8080/api/v1/flights/1
the following data is returned from the two stubbed Account
and Reservation
services:
- The
Account
service endpoint/api/v1/accounts/1
always responds with the HTTP status200 OK
and the same HTTP JSON response defined in stubby4j-yaml-config/json/account.service.expected.success.response.json - The
Reservation
service endpoint/api/v1/reservations/1
responds with different HTTP responses on each invocation ofhttp://localhost:8080/api/v1/flights/1
. There are a total of five different HTTP responses thatstubby4j
loops over when rendering responses for/api/v1/reservations/1
:200 OK
,400 Bad Request
,403 Forbidden
and two500 Internal Server Error
with different JSON response bodies defined under stubby4j-yaml-config/json.
Logs
At runtime, the stubby4j
application logs are emitted into stubby4j-yaml-config/logs/stubby4j.log
. The logs contain debug information from matching incoming HTTP requests to the defined stubs.
Conclusion
In this tutorial, we’ve learnt how to test microservices locally running in a Docker environment by leveraging Docker Compose and stubby4j
, an HTTP stub server.
We also saw how to stub multiple HTTP responses with different behavior under the same stubbed URI defined in defined in stubby4j-yaml-config/include-reservation-service-stubs.yaml.
To understand in more depth the stubby4j
capabilities and the available features, see https://stubby4j.com and HTTP endpoint configuration HOWTO for more information.
The source code for this tutorial is available over on GitHub.
Oh, did I mention that stubby4j supports API stubbing over HTTP/2 and also WebSocket protocols? :)