Testing with Hoverfly and Java Part 1: Get started with Simulation Mode

These days a major problem exists when it comes to testing code that has to do with various cloud services where test tools are not provided.
For example although you might have the tools for local Pub/Sub testing, including Docker images you might not have anything that can Mock BigQuery.

This causes an issue when it comes to the CI jobs, as testing is part of the requirements, however there might be blockers on testing with the actual service. The case is, you do need to cover all the pessimistic scenarios you need to be covered (for example timeouts).

And this is where Hoverfly can help.

Hoverfly is a lightweight, open source API simulation tool. Using Hoverfly, you can create realistic simulations of the APIs your application depends on

Our first examples will have to do with simulating just a web server. The first step is to add the Hoverfly dependency.

    <dependencies>
        <dependency>
            <groupId>io.specto</groupId>
            <artifactId>hoverfly-java</artifactId>
            <version>0.12.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

Instead of using the Hoverfly docker image we shall use the Java Library for some extra flexibility.

We got two options on configuring the Hoverfly simulation mode. One is through the Java dsl and the other one is through json.
Let’s cover both.

The example below uses the Java DSL. We spin up hoverfly on 8085 and load this configuration.

class SimulationJavaDSLTests {

	private Hoverfly hoverfly;

	@BeforeEach
	void setUp() {
		var simulation = SimulationSource.dsl(service("http://localhost:8085")
				.get("/user")
				.willReturn(success("{\"username\":\"test-user\"}", "application/json")));

		var localConfig = HoverflyConfig.localConfigs().disableTlsVerification().asWebServer().proxyPort(8085);
		hoverfly = new Hoverfly(localConfig, SIMULATE);
		hoverfly.start();
		hoverfly.simulate(simulation);
	}

	@AfterEach
	void tearDown() {
		hoverfly.close();
	}

	@Test
	void testHttpGet() {
		var client = HttpClient.newHttpClient();
		var request = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/user"))
				.build();
		var res = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
				.thenApply(HttpResponse::body)
				.join();
		Assertions.assertEquals("{\"username\":\"test-user\"}",res);
	}
}

Now let’s do the same with Json. Instead of manually trying things with json we can make the code do the work for us.

var simulation = SimulationSource.dsl(service("http://localhost:8085")
			.get("/user")
			.willReturn(success("{\"username\":\"test-user\"}", "application/json")));

var simulationStr = simulation.getSimulation()
System.out.println(simulationStr);

We can get the JSON generated by the Java DSL. The result would be like this.

{
  "data": {
    "pairs": [
      {
        "request": {
          "path": [
            {
              "matcher": "exact",
              "value": "/user"
            }
          ],
          "method": [
            {
              "matcher": "exact",
              "value": "GET"
            }
          ],
          "destination": [
            {
              "matcher": "exact",
              "value": "localhost:8085"
            }
          ],
          "scheme": [
            {
              "matcher": "exact",
              "value": "http"
            }
          ],
          "query": {},
          "body": [
            {
              "matcher": "exact",
              "value": ""
            }
          ],
          "headers": {},
          "requiresState": {}
        },
        "response": {
          "status": 200,
          "body": "{\"username\":\"test-user\"}",
          "encodedBody": false,
          "templated": true,
          "headers": {
            "Content-Type": [
              "application/json"
            ]
          }
        }
      }
    ],
    "globalActions": {
      "delays": []
    }
  },
  "meta": {
    "schemaVersion": "v5"
  }
}

Let’s place this one on the resources folder of tests under the name simulation.json

And with some code changes we get exactly the same result.


public class SimulationJsonTests {

	private Hoverfly hoverfly;

	@BeforeEach
	void setUp() {
		var simulationUrl = SimulationJsonTests.class.getClassLoader().getResource("simulation.json");
		var simulation = SimulationSource.url(simulationUrl);

		var localConfig = HoverflyConfig.localConfigs().disableTlsVerification().asWebServer().proxyPort(8085);
		hoverfly = new Hoverfly(localConfig, SIMULATE);
		hoverfly.start();
		hoverfly.simulate(simulation);
	}

	@AfterEach
	void tearDown() {
		hoverfly.close();
	}

	@Test
	void testHttpGet() {
		var client = HttpClient.newHttpClient();
		var request = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/user"))
				.build();
		var res = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
				.thenApply(HttpResponse::body)
				.join();
		Assertions.assertEquals("{\"username\":\"test-user\"}",res);
	}

}

Also sometimes there is the need of combining simulations regardless they json or Java ones. This can also be facilitated by loading more that one simulations.

	@Test
	void testMixedConfiguration() {
		var simulationUrl = SimulationJsonTests.class.getClassLoader().getResource("simulation.json");
		var jsonSimulation = SimulationSource.url(simulationUrl);


		var javaSimulation = SimulationSource.dsl(service("http://localhost:8085")
				.get("/admin")
				.willReturn(success("{\"username\":\"test-admin\"}", "application/json")));

		hoverfly.simulate(jsonSimulation, javaSimulation);

		var client = HttpClient.newHttpClient();
		var jsonConfigBasedRequest = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/user"))
				.build();
		var userResponse = client.sendAsync(jsonConfigBasedRequest, HttpResponse.BodyHandlers.ofString())
				.thenApply(HttpResponse::body)
				.join();
		Assertions.assertEquals("{\"username\":\"test-user\"}",userResponse);

		var javaConfigBasedRequest = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/admin"))
				.build();
		var adminResponse = client.sendAsync(javaConfigBasedRequest, HttpResponse.BodyHandlers.ofString())
				.thenApply(HttpResponse::body)
				.join();
		Assertions.assertEquals("{\"username\":\"test-admin\"}",adminResponse);
	}

That’s it, we are pretty setup to continues exploring Hoverfly and it’s capabilities. The next blog is about delays.