Easy API simulation with Hoverfly JUnit Rule
In order to be able to regularly release an application, your automated tests must be set up to give you fast and reliable feedback loop. If bugs are only found during a long and expensive multi-service or end-to-end test run, it can be a hinderance to fast delivery. Unfortunately I have often seen this problem in development environments: a huge suite of clunky, flaky and slow end-to-end tests which test the full functionality of the application as opposed to being more lightweight and reflecting basic user journeys. This produces the “ice cream cone” anti-pattern of test coverage, where unit tests aren’t providing the kind of coverage and feedback they need to.
There are many reasons for this - developer laziness, a lack of understanding of a build’s importance by the business - or more technical issues such as the difficulties involved in stubbing the architectural boundaries of an application or service. You can’t know that everything works together until everything is deployed together.
Following on from the previous blog post, I’ve created a Hoverfly JUnit Rule which enables you to quickly and reliably simulate an external API in your Java unit / integration tests.
Hoverfly is a lightweight proxy which can be used to “capture” and “virtualize” (and thus simulate) external services. It also allows you to use custom middleware to modify requests and responses on demand, allowing for easier testing of resilience patterns in your environment. It’s written in Go, so as well as being lightweight and fast, it has minimal operational overhead - it cross compiles into a single binary which is both API and CLI driven, and is thus straight forward to integrate throughout your continuous delivery pipeline.
The JUnit rule is a Java native language binding for Hoverfly, allowing you to leverage it in your Java tests. The source code is available on GitHub.
Quick Start
The rule is available in Maven Central:
<groupId>io.specto</groupId>
<artifactId>hoverfly-junit</artifactId>
<version>0.2.2</version></pre>
Hoverfly stores request-to-response mappings as json. This .json is either created by Hoverfly as it “captures” communication with an external service, or it can be created manually. The .json can be exported from and imported into Hoverfly.
You can declare the rule as follows:
@Rule
public HoverflyRule hoverflyRule = HoverflyRule.inSimulationMode("test-service.json");
The rule takes a convention over configuration approach, meaning this is all you need to do to get it working. Behind the scenes it will:
- Spin up and tear down Hoverfly before and after the rule.
- Load the .json into it’s database.
- Block until Hoverfly has started.
- Extract and execute the correct Hoverfly binary based on the underlying OS (The rule is not currently on a major release as there are likely to be edge cases in this area).
- Boot Hoverfly on unused ports to guarantee it won’t clash with anything.
- Set the proxy host and port so any http requests from the Java client will go through Hoverfly.
An example test may look as follows:
public class HoverflyRuleTest {
@Rule
public HoverflyRule hoverflyRule = HoverflyRule.inSimulationMode("test-service.json");
private RestTemplate restTemplate;
@Before
public void setUp() {
restTemplate = new RestTemplate();
}
@Test
public void shouldBeAbleToMakeABooking() throws URISyntaxException {
// Given
final RequestEntity<String> bookFlightRequest = RequestEntity.post(new URI("http://www.my-test.com/api/bookings"))
.contentType(APPLICATION_JSON)
.body("{\"flightId\": \"1\"}");
// When
final ResponseEntity<String> bookFlightResponse = restTemplate.exchange(bookFlightRequest, String.class);
// Then
assertThat(bookFlightResponse.getStatusCode()).isEqualTo(CREATED);
assertThat(bookFlightResponse.getHeaders().getLocation()).isEqualTo(new URI("http://localhost/api/bookings/1"));
}
}
CAPTURE MODE
Another way that the rule can be used is for capturing interactions with a real external service. This means we can write our tests against the real service, eventually breaking the dependency once we are happy with our implementation code. By starting it in capture mode, all requests will be proxied and recorded, the output of which will be written to your “src/test/resources” resources directory.
@Rule
public HoverflyRule hoverflyRule = HoverflyRule.inCaptureMode("recorded-simulation.json");
Rather than going through a tedious process of setting up stubs (which can take a long time and be error prone), or even manual testing, we can just write our tests against the real thing, recording all the interactions. Once it’s green, all we need to do is switch back into simulate mode, knowing that Hoverfly will respond with our previously recorded data - behaving as much like the real service as it can do. An example workflow with Github OAuth2 can be found here.
In Conclusion
Hoverfly has value across all tiers of testing, from unit to end-to-end testing. Here we have focused primarily on Java Unit / Integration testing, and because data can easily imported into Hoverfly as .json it shows how interoperable it can be.
Please feel free to visit the project on GitHub if you wish to contribute!