Modern HTTP service virtualization with Hoverfly

Service virtualization brings a lightweight, automatable means of simulating external dependencies.

By Andrew Morgan
November 16, 2017
Sea Sea (source: StockSnap)

In modern software systems, it’s very common for applications to depend on third party or internal services. For example, an ecommerce site might depend on a third party payment service to process card payments, or a social network to provide authentication. These sorts of applications can be challenging to test in isolation, as their dependencies can introduce problems like:

  • Non-determinism
  • Slow and costly builds
  • Unmockable client libraries
  • Rate-limiting
  • Expensive licensing costs
  • Incompleteness
  • Slow provisioning

To get around this, service virtualization, or replacing these components with a process which simulates them, can emulate these dependencies. Unlike mocking, which replaces your application code, service virtualization lives externally, typically operating at the network level. It is non-invasive, and is essentially just like the real thing from the perspective of its consumer.

Learn faster. Dig deeper. See farther.

Join the O'Reilly online learning platform. Get a free trial today and find answers on the fly, or master something new and useful.

Learn more

Here I’ll demonstrate using using an open-source HTTP service virtualization tool called Hoverfly to simulate an API.

How Hoverfly Can Emulate Dependencies

Hoverfly is an open-source HTTP service virtualization tool that is typically used for simulating API dependencies. Written in Go, it is a forward proxy that intercepts, replaces, and modifies traffic to an external service. It’s two core modes are:

  • Capture Mode: Whilst proxying HTTP traffic through to a remote server as usual, all HTTP traffic is intercepted and stored in memory.
  • Simulate: Here, no HTTP traffic is actually proxied to the real server. Instead, a process called matching takes place to find an appropriate response for a given request. This is performed against the recorded data from capture mode, or against data which has been created manually.

Simulating our First API with Hoverfly

Imagine we have a service under test that is dependant on a third party server in order to give us the current time in GMT. Calling out to this real server might be a problem during the test runs of our service. This could be because:

  • The response is always different, so it becomes hard to write assertions.
  • There might be a cost involved in calling the service too often, so we want to avoid doing it unless we absolutely have to.
  • Our service under test might have a client library that is difficult to mock.
  • The server might be flaky, so it could produce random test failures.

For these reasons, we have decided to simulate the API using Hoverfly.

To get started, let’s download Hoverfly. Once installed, we can open a terminal and start an instance with the hoverctl command:

$ hoverctl start
Hoverfly is now running

+------------+------+
| admin-port | 8888 |
| proxy-port | 8500 |
+------------+------+

This will have spawned two processes: the core Hoverfly proxy, and the administrative UI. We can also issue a status command at any time to check the health of these processes, and how the proxy is operating:

$ hoverctl status

+------------+----------+
| Hoverfly   | running  |
| Admin port |     8888 |
| Proxy port |     8500 |
| Mode       | simulate |
| Middleware | disabled |
+------------+----------+

We first need to put Hoverfly into capture mode. During this mode, we can call the current time API through Hoverfly and it will store the HTTP request and response in memory. This is how we can produce the data to be used in our simulation:

$ hoverctl mode capture
Hoverfly has been set to capture mode

Following this, we can curl an API through Hoverfly in order to record it. We pass the exact API request that our service makes in production, because it is the one we want to record and simulate:

$ curl http://time.jsontest.com --proxy http://localhost:8500
{
   "time": "09:30:30 PM",
   "milliseconds_since_epoch": 1506634230519,
   "date": "09-28-2017"
}

Because we are in capture mode, the HTTP request and response was passed through Hoverfly as usual, only Hoverfly intercepted and stored the traffic going in both directions. We can check the logs to validate this:

$ hoverctl logs
…
INFO[2017-09-28T23:30:29+02:00] request and response captured
…

Offline Testing for Our Simulation

For the next stage, let’s disconnect from the Internet. By being offline we will guarantee that our simulation is functional and we aren’t accidentally calling the real server. Once that’s done, we can move into simulate mode and then try curling the API again:

$ hoverctl mode simulate
Hoverfly has been set to simulate mode with a matching strategy of 'strongest'
$ curl http://time.jsontest.com --proxy http://localhost:8500
{
   "time": "09:30:30 PM",
   "milliseconds_since_epoch": 1506634230519,
   "date": "09-28-2017"
}

As you can see, we get exactly the same response as last time, only this time Hoverfly is responding instead of the real service. It is simply taking the request made to it, matching it against the recorded request, and returning the associated recorded response.

Now our service under test can now depend on our simulation instead of the real API. This solves a lot of our testing problems, such as:

  • The response will always be the same, meaning we can write a static assertion.
  • We save money by running a local simulation as opposed to calling the real server.
  • No mocking. This means that we that we avoided any complexity that could come from mocks, and also that we test all the way to the service boundary by making real network calls.
  • The flakiness is gone because our simulation is reliable.

Conclusion

I’ve shown you the simplest end-to-end example of recording and replaying an API endpoint with Hoverfly. This has allowed us to replace an unreliable test dependency with a reliable, deterministic simulation.

Of course, I’ve avoided more advanced concepts. For example, the simulation we created lives in memory so isn’t reusable across instances. We can look at this and more in later articles:

  • Understanding a simulation JSON file
  • Advanced matching concepts, such as partial matching and scoring
  • Matching on different criteria, such as JSONPath and XPath
  • Dynamically generating responses based on request data
  • Simulating stateful workflows
  • Fault injection

But what we have now should be enough to get you started with a lightweight, automatable means of simulating external dependencies.

Post topics: Web Programming
Share: