Testing with Hoverfly and Java Part 6: JSON and JsonPath matchers

Previously we used the XML and Xpath Hoverfly matchers.
On this blog we shall focus on rules that assist us with the data exchanged using Json.

The default Json matcher will compare the Json submitted with the Json expected. This means that the submitted Json shall be validated for all the elements and their value. New lines or any extra spaces as long as they don’t change the information that the JSON carries, will not prevent the request from being a success.

Let’s put our initial configuration that will make the Json match.

 


    @BeforeEach
    void setUp() {
        var simulation = SimulationSource.dsl(service("http://localhost:8085")
                .post("/json")
                .body(RequestFieldMatcher.newJsonMatcher("{\"document\":\"document-a\"}"))
                .willReturn(success(SUCCESS_RESPONSE, "application/json"))
                .post("/json/partial")
                .body(RequestFieldMatcher.newJsonPartialMatcher("{\"document\":\"document-a\"}"))
                .willReturn(success(SUCCESS_RESPONSE, "application/json"))
                .post("/jsonpath")
                .body(RequestFieldMatcher.newJsonPathMatch("$.document[1].description"))
                .willReturn(success(SUCCESS_RESPONSE, "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();
    }

    

In our first example we will try to match the Json of our request with the Json expected.

    @Test
    void testJsonExactMatch() {
        var client = HttpClient.newHttpClient();

        var exactRequest = HttpRequest.newBuilder()
                .uri(URI.create("http://localhost:8085/json"))
                .POST(HttpRequest.BodyPublishers.ofString("   {\"document\":    \"document-a\"}"))
                .build();

        var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body)
                .join();

        Assertions.assertEquals(SUCCESS_RESPONSE, exactResponse);
    }

Also let’s make sure there is going to be a failure on an extra element.

    @Test
    void testJsonNoMatch() {
        var client = HttpClient.newHttpClient();

        var exactRequest = HttpRequest.newBuilder()
                .uri(URI.create("http://localhost:8085/json"))
                .POST(HttpRequest.BodyPublishers.ofString("{\"doc2\":\"value\", \"document\":\"document-a\"}"))
                .build();

        var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
                .join();

        Assertions.assertEquals(502, exactResponse.statusCode());
    }

Now let’s see the non exact matcher.

    @Test
    void testJsonPartialMatch() {
        var client = HttpClient.newHttpClient();

        var exactRequest = HttpRequest.newBuilder()
                .uri(URI.create("http://localhost:8085/json/partial"))
                .POST(HttpRequest.BodyPublishers.ofString("{\"doc2\":\"value\", \"document\":\"document-a\"}"))
                .build();

        var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body)
                .join();

        Assertions.assertEquals(SUCCESS_RESPONSE, exactResponse);
    }

So far we checked matching the whole payload. Let’s try the Jsonpath approach. The example below does match.

    @Test
    void testJsonPathMatch() {
        var client = HttpClient.newHttpClient();

        var exactRequest = HttpRequest.newBuilder()
                .uri(URI.create("http://localhost:8085/jsonpath"))
                .POST(HttpRequest.BodyPublishers.ofString("{\"document\":[{\"description\":\"description-1\"},{\"description\":\"description-2\"}]}"))
                .build();

        var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body)
                .join();

        Assertions.assertEquals(SUCCESS_RESPONSE, exactResponse);
    }

But the example below won’t match


    @Test
    void testJsonPathNoMatch() {
        var client = HttpClient.newHttpClient();

        var exactRequest = HttpRequest.newBuilder()
                .uri(URI.create("http://localhost:8085/jsonpath"))
                .POST(HttpRequest.BodyPublishers.ofString("{\"document\":[{\"description\":\"description-1\"}]}"))
                .build();

        var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
                .join();

        Assertions.assertEquals(502, exactResponse.statusCode());
    }

That’s it we did use the Json and JsonPath matchers for the Json based data!

Testing with Hoverfly and Java Part 5: XML and Xpath matchers

Previously we worked with some of the existing Hoverfly matchers like the regex, glob and exact.
Each one serves its purpose but we might want some rules that assist us with the format of the data exchanged through our requests.

On this blog we will focus on the matchers for xml.

 

The default xml matcher will compare the xml submitted with the xml expected. This means that the submitted xml shall be validated node by node value by value. New lines or any extra spaces as long as they don’t change the content that the xml carries will not prevent the request from being a success.

Let’s put our initial configuration that will make the xml match.

	public static final String SUCCESS_RESPONSE = "<response>"
			+ "<result>success</result>"
			+ "</response>";

	private Hoverfly hoverfly;

	@BeforeEach
	void setUp() {
		var simulation = SimulationSource.dsl(service("http://localhost:8085")
				.post("/xml")
				.body(RequestFieldMatcher.newXmlMatcher("<document type=\"xml\">"
						+ "xml-request"
						+ "</document>"))
				.willReturn(success(SUCCESS_RESPONSE, "application/xml")));

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

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

So in our first example we will try to match the xml of our request with the xml expected.

	@Test
	void testXmlExactMatch() {
		var client = HttpClient.newHttpClient();

		var exactRequest = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/xml"))
				.POST(HttpRequest.BodyPublishers.ofString("  <document type=\"xml\">\n\n"
						+ "xml-request"
						+ "</document>\t"))
				.build();

		var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
				.thenApply(HttpResponse::body)
				.join();

		Assertions.assertEquals(SUCCESS_RESPONSE, exactResponse);
	}

As you see regardless of the new lines and the tabs, our request will be successful since the xml data do match.

Now let’s try to add a node to the xml.

	@Test
	void testXmlNoMatch() {
		var client = HttpClient.newHttpClient();

		var exactRequest = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/xml"))
				.POST(HttpRequest.BodyPublishers.ofString("  <document type=\"xml\">\n\n"
						+ "xml-request"
						+ "</document>\t<empty-node>ok</empty-node>"))
				.build();

		var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
				.join();

		Assertions.assertEquals(502, exactResponse.statusCode());
	}

The xml does not match thus it will fail.

Let’s focus to another problem. Since the data exchanged are dynamic, chances are that exact matches might not be possible. Also you might not need to focus on all the information submitted but just a specific section of the information exchanged. Therefore an XPath matcher becomes handy.

Will enhance the initial setup with an XPath rule.


	@BeforeEach
	void setUp() {
		var simulation = SimulationSource.dsl(service("http://localhost:8085")
				.post("/xml")
				.body(RequestFieldMatcher.newXmlMatcher("<document type=\"xml\">"
						+ "xml-request"
						+ "</document>"))
				.willReturn(success(SUCCESS_RESPONSE, "application/xml"))
				.post("/xpath")
				.body(RequestFieldMatcher.newXpathMatcher("/document/payment[amount=1]"))
				.willReturn(success(SUCCESS_RESPONSE, "application/xml"))
		);

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

If there is a document node with a payment node and the value on the amount node is 1 there will be a match
Let’s go for a positive scenario

	@Test
	void testXpathMatch() {
		var client = HttpClient.newHttpClient();

		var exactRequest = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/xpath"))
				.POST(HttpRequest.BodyPublishers.ofString("  <document type=\"xml\">\n\n"
						+ "<payment><amount>142</amount></payment>"
						+ "<payment><amount>1</amount><currency>GBP</currency></payment>"
						+ "<payment>invalid</payment>"
						+ "</document>\t"))
				.build();

		var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
				.thenApply(HttpResponse::body)
				.join();

		Assertions.assertEquals(SUCCESS_RESPONSE, exactResponse);
	}

As expected we got a match.
Let’s go for a negative scenario.

	@Test
	void testXpathNoMatch() {
		var client = HttpClient.newHttpClient();

		var exactRequest = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/xpath"))
				.POST(HttpRequest.BodyPublishers.ofString("  <document type=\"xml\">\n\n"
						+ "<payment><amount>142</amount></payment>"
						+ "<payment><amount>no-match</amount><currency>GBP</currency></payment>"
						+ "<payment>invalid</payment>"
						+ "</document>\t"))
				.build();

		var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
				.join();

		Assertions.assertEquals(502, exactResponse.statusCode());
	}

That’s it we did use the xml and xpath matchers for the xml based data. The next blog shall focus on the JSON based matchers.

Testing using TestContainers

Part of our everyday ci/cd tasks involve using containers in order for the tests to take effect.
So what if you could control the containers you use through your tests and serve your scenarios better.
Also what if you could do this in a more managed way?

Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.

You pretty much can guess what is all about. Our tests can spin up the containers with the parameters needed. We will get started by using it in our tests with Junit.

It all starts with the right dependencies. Supposing we use maven for this tutorial.

	<properties>
		<junit-jupiter.version>5.4.2</junit-jupiter.version>
		<testcontainers.version>1.15.0</testcontainers.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.testcontainers</groupId>
			<artifactId>testcontainers</artifactId>
			<version>${testcontainers.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.testcontainers</groupId>
			<artifactId>junit-jupiter</artifactId>
			<version>${testcontainers.version}</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

I shall use an example we already have with Hoverfly.
We can use Hoverfly on our tests either by running it using Java or having a Hoverfly container with the test cases preloaded.
On the previous blog Hoverfly was integrated in our tests through the Java binary.
For this blog we shall use the Hoverfly container.

Our end result will look like this.

package com.gkatzioura.hoverfly.docker;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@Testcontainers
public class ContainerBasedSimulation {

	private static final String SIMULATION_HOST_PATH = ContainerBasedSimulation.class.getClassLoader().getResource("simulation.json").getPath();

	@Container
	public static GenericContainer gcs = new GenericContainer("spectolabs/hoverfly")
			.withExposedPorts(8888)
			.withExposedPorts(8500)
			.withCommand("-webserver","-import","/var/hoverfly/simulation.json")
			.withClasspathResourceMapping("simulation.json","/var/hoverfly/simulation.json" ,BindMode.READ_ONLY);


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

}

Let’s break it down.

The @Testcontainers annotation is needed for the Jupiter integration.

@Testcontainers
public class ContainerBasedSimulation {
}

We shall use a container image that is not preloaded among the test containers available (for example Elastic Search), thus we shall use the GenericContainer class.

@Container
public static GenericContainer gcs = new GenericContainer("spectolabs/hoverfly")

Since we want to load to the container a simulation, we need to set the path to our simulation from our host machine. By using withClasspathResourceMapping we directly specify files in our classpath, for example the test resources.

			.withClasspathResourceMapping("simulation.json","/var/hoverfly/simulation.json",BindMode.READ_ONLY);

Hoverfly needs the simulation and the admin port to be exposed so we shall instruct Testcontainers to expose those ports and map them to host.

new GenericContainer("spectolabs/hoverfly")
			.withExposedPorts(8888)
			.withExposedPorts(8500)

We need to have a simulation placed on the container. By using withFileSystemBind we specify the local path and the path on the container.

...
.withFileSystemBind(SIMULATION_HOST_PATH,"/var/hoverfly/simulation.json" ,BindMode.READ_ONLY)
...

Also docker images might need to have some extra commands, therefore we shall use .withCommand, to pass the commands needed.

...
.withCommand("-webserver","-import","/var/hoverfly/simulation.json")
...

Technically we can say we are ready to go and connect to the container, however when running test containers the container is not accessible through the port specified to do the binding. After all, if tests run on parallel there is going to be a collision. So what Testcontainers do is to map the exposed port of the container to a random local port.
This way port collisions are avoided.

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

Using GenericContainer.getMappedPort(8500) we can get the port we have to use to interact with the container. Also getHost() is essential too since it won’t always direct to localhost.

Last but not least while testing if your are curious enough and do a docker ps.

docker ps 
>04a322447226        testcontainers/ryuk:0.3.0   "/app"                   3 seconds ago       Up 2 seconds        0.0.0.0:32814->8080/tcp    testcontainers-ryuk-fb60c3c6-5f31-4f4e-9ab7-ce25a00eeccc

You shall see a container running which is not the one we instructed through our unit test. The ryuk container is responsible for removing containers/networks/volumes/images by given filter after specified delay.

That’s it! We just achieved running the container we needed through our a test and we successfully migrated a previous test to one using test containers.

Testing with Hoverfly and Java Part 4: Exact, Glob and Regex Matchers

Previously we used Hoverfly among its state feature.
So far our examples have been close to an absolute request match, thus on this blog we will focus on utilising the matchers.
Having a good range of matchers is very important because most API interactions are dynamic and you can’t always predict the example. Imagine a JWT signature. You can match the body but the signature might change per environment.

 

There are three type of matchers available.

  • The exact matcher: The fields headers should be an exact match
  • The glob matcher: A match that gives the ability to using the `*`
  • The regex matcher: A matcher that requires you to search again on the internet on how to make a regex
  • XML matcher: This about matching the xml as XML node by node value by value
  • Xpath matcher: Match based on a value match through Xpath
  • JSON matcher: Match the json exactly
  • JSON partial matcher: Match if the json submitted contains the Json values specified
  • JSONPath matcher: Just like the xpath match based the on the json path submitted

Let’s start with the exact matcher.

public class ExactMatcherTests {

	private Hoverfly hoverfly;

	@BeforeEach
	void setUp() {
		var simulation = SimulationSource.dsl(service("http://localhost:8085")
				.get("/exact")
				.header("Origin", RequestFieldMatcher.newExactMatcher("internal-server"))
				.willReturn(success("{\"exact\":true}", "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 testExactMatcherSuccess() {
		var client = HttpClient.newHttpClient();
		var exactRequest = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/exact"))
				.header("Origin","internal-server")
				.build();

		var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
				.thenApply(HttpResponse::body)
				.join();

		Assertions.assertEquals("{\"exact\":true}", exactResponse);
	}

	@Test
	void testExactMatcherFailure() {
		var client = HttpClient.newHttpClient();
		var exactRequest = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/exact"))
				.build();

		var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
				.join();

		Assertions.assertEquals(502, exactResponse.statusCode());
	}

}

The failures or success come based on wether the header was matching exactly or not.

We shall use the glob match for a request parameter.


public class GlobMatcher {

	private Hoverfly hoverfly;

	@BeforeEach
	void setUp() {
		var simulation = SimulationSource.dsl(service("http://localhost:8085")
				.get("/glob")
				.queryParam("userName", RequestFieldMatcher.newGlobMatcher("john*"))
				.willReturn(success("{\"glob\":true}", "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 testGlobMatcherSuccess() {
		var client = HttpClient.newHttpClient();
		var exactRequest = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/glob?userName=johnDoe"))
				.build();

		var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
				.thenApply(HttpResponse::body)
				.join();

		Assertions.assertEquals("{\"glob\":true}", exactResponse);
	}

	@Test
	void testGlobMatcherFailure() {
		var client = HttpClient.newHttpClient();
		var exactRequest = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/glob?userName=nojohnDoe"))
				.build();

		var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
				.join();

		Assertions.assertEquals(502, exactResponse.statusCode());
	}

}

Last let’s head for the regex matcher. The regex matcher will just check for a capital letter: ([A-Z])\w+

public class RegexMatcherTests {

	private Hoverfly hoverfly;

	@BeforeEach
	void setUp() {
		var simulation = SimulationSource.dsl(service("http://localhost:8085")
				.post("/regex")
				.body(RequestFieldMatcher.newRegexMatcher("([A-Z])\\w+"))
				.willReturn(success("{\"regex\":true}", "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 testRegexMatcherSuccess() {
		var client = HttpClient.newHttpClient();
		var exactRequest = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/regex"))
				.POST(HttpRequest.BodyPublishers.ofString("Contains capital letter"))
				.build();

		var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
				.thenApply(HttpResponse::body)
				.join();

		Assertions.assertEquals("{\"regex\":true}", exactResponse);
	}

	@Test
	void testRegexMatcherFailure() {
		var client = HttpClient.newHttpClient();
		var exactRequest = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/regex"))
				.POST(HttpRequest.BodyPublishers.ofString("won't match due to capital letter missing"))
				.build();

		var exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
				.join();

		Assertions.assertEquals(502, exactResponse.statusCode());
	}

}

That’s it we did use the basic matchers for exact, glob, and regex based. The next blog shall focus on the xml based matchers.

Testing with Hoverfly and Java Part 3: State

Previously we simulated a delay scenario using Hoverfly. Now it’s time to dive deeper and go for a state based testing. By doing a stateful simulation we can change the way the tests endpoints behave based on how the state changed.

 

Hoverfly does have a state capability. State in a hoverfly simulation is like a map. Initially it is empty but you can define how it will get populated per request.

Our strategy would be to have a request that initializes the state and then specifies other requests that change that state.

public class SimulationStateTests {

	private Hoverfly hoverfly;

	@BeforeEach
	void setUp() {
		var simulation = SimulationSource.dsl(service("http://localhost:8085")
				.get("/initialize")
				.willReturn(success("{\"initialized\":true}", "application/json")
						.andSetState("shouldSucceed", "true")
				)
				.get("/state")
				.withState("shouldSucceed", "false")
				.willReturn(serverError().andSetState("shouldSucceed", "true"))
				.get("/state")
				.withState("shouldSucceed", "true")
				.willReturn(success("{\"username\":\"test-user\"}", "application/json")
						.andSetState("shouldSucceed", "false"))

		);

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

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

}

Unfortunately on the state we can only specify values in a key value fashion and not by passing a function for a key.
However with the right workaround many scenarios could be simulated.

In the example we first initialize the state and the we issue requests that behave differently based on the state, but also they do change the state.

So we expect to have a continuous first succeed and then fail mode, which can be depicted in the following test.

	@Test
	void testWithState() {
		var client = HttpClient.newHttpClient();
		var initializationRequest = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/initialize"))
				.build();
		var initializationResponse = client.sendAsync(initializationRequest, HttpResponse.BodyHandlers.ofString())
				.thenApply(HttpResponse::body)
				.join();
		Assertions.assertEquals("{\"initialized\":true}", initializationResponse);

		var statefulRequest = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/state"))
				.build();

		for (int i = 0; i < 100; i++) {
			var response = client.sendAsync(statefulRequest, HttpResponse.BodyHandlers.ofString())
					.join();

			int statusCode = i % 2 == 0 ? 200 : 500;

			Assertions.assertEquals(statusCode, response.statusCode());
		}
	}

That’s all about stateful simulation. On the next part we shall proceed on Hoverfly matchers

Testing with Hoverfly and Java Part 2: Delays

On the previous post we implemented json and Java based Hoverfly scenarios..
Now it’s time to dive deeper and use other Ηoverfly features.

A big part of testing has to do with negative scenarios. One of them is delays. Although we always mock a server and we are successful to reproduce erroneous scenarios one thing that is key to simulate in todays microservices driven world is delay.

So let me make a server with a 30 secs delay.

public class SimulateDelayTests {

	private Hoverfly hoverfly;

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

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

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

}

Let’s add the Delay Test

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

   var seconds = Duration.between(start, end).getSeconds();
   Assertions.assertTrue(seconds >= 30);
}

Delay simulation is there, up and running, so let’s try to simulate timeouts.

	@Test
	void testTimeout() {
		var client = HttpClient.newHttpClient();
		var request = HttpRequest.newBuilder()
				.uri(URI.create("http://localhost:8085/delay"))
				.timeout(Duration.ofSeconds(10))
				.build();
		assertThrows(HttpTimeoutException.class, () -&gt; {
					try {
						client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join();
					} catch (CompletionException ex) {
						throw ex.getCause();
					}
				}

		);
	}

That’s it we got delays and timeouts!
Other test scenarios should contain state which is covered on the next tutorial.