One of the things I like best about the option to write stubs for service virtualization in code is that by doing so, you’re able to store them in and access them through the same version control system (Git, for example) as your production code and your automated tests. I was excited when I read a blog post on the SpectoLabs website announcing that they had added a Java DSL to their most recent Hoverfly release. I’ve been keeping up with Hoverfly as a product for a while now, and it’s rapidly becoming an important player in the world of open source service virtualization solutions.
This Java DSL is somewhat similar to what WireMock offers, in that it allows you to quickly create stubs in your code, right when and where you need them. This blog post will not be a comparison between Hoverfly and WireMock, though. Both tools have some very useful features and have earned (and are stil earning) their respective place in the service virtualization space, so it’s up to you to see which of these best fits your project.
Instead, back to Hoverfly. Let’s take a look at a very basic stub definition first:
@ClassRule public static HoverflyRule hoverflyRule = HoverflyRule.inSimulationMode(dsl( service("www.afirststub.com") .get("/test") .willReturn(success("Success","text/plain")) ));
The syntax used to create a stub is pretty straightforward, as you can see. Here, we have defined a stub that listens at http://www.afirststub.com/test and returns a positive response, defined using the success() method, which boils down to Hoverfly returning an HTTP response with a 200 status code. The response further contains a response body with a string Success as its body and text/plain as its content type. By replacing these values with other content and content type values, you can easily create a stub that exerts the behaviour required for your specific testing needs.
As you can see, a Hoverfly stub is defined using the JUnit @ClassRule annotation. For those of you that use TestNG, you can manage the Hoverfly instance (the Hoverfly class is included in the hoverfly-java dependency) in @Before and @After classes instead.
We can check that this stub works as intended by writing and running a simple REST Assured test for it:
@Test public void testAFirstStub() { given(). when(). get("http://www.afirststub.com/test"). then(). assertThat(). statusCode(200). and(). body(equalTo("Success")); }
Since Hoverfly works as a proxy, it can return any data you specify, even for existing endpoints. This means that you don’t need to change existing configuration files and endpoints in your system under test when you’re running your tests, no matter whether you’re using an actual endpoint or the Hoverfly stub representation of it. A big advantage, if you ask me.
Consider the following (utterly useless) use case: the endpoint http://ergast.com/api/f1/drivers/max_verstappen.json returns data for the Formula 1 driver Max Verstappen in JSON format (you can click the link to see what data is returned). Assume we want to test what happens when the permanentNumber changes value from 33 to, say, 999, we can simply create a stub that listens at the same endpoint, but returns different data:
@ClassRule public static HoverflyRule hoverflyRule = HoverflyRule.inSimulationMode(dsl( service("ergast.com") .get("/api/f1/drivers/max_verstappen.json") .willReturn(success("{\"permanentNumber\": \"999\"}", "application/json")) ));
Note that I removed all other data that is returned by the original endpoint for brevity and laziness. Mostly laziness, actually. Again, a simple test shows that instead of the data returned by the real endpoint, we now get our data from the Hoverfly stub:
@Test public void testStubFakeVerstappen() { given(). when(). get("http://ergast.com/api/f1/drivers/max_verstappen.json"). then(). assertThat(). body("permanentNumber",equalTo("999")); }
Apart from being quite useless, the example above also introduces an issue with defining stubs that return larger amounts of JSON data (or XML data, for that matter): since JSON is not really well supported out of the box in Java (nor is XML), we could potentially end up with a large and unwieldy string with lots of character escaping for larger response bodies. Luckily, Hoverfly offers a solution for that in the form of object (de-)serialization.
Assume we have a simple Car POJO with two fields: make and model. If we create an instance of that Car object like this:
private static Car myCar = new Car("Ford", "Focus");
and we pass this to the stub definition as follows:
@ClassRule public static HoverflyRule hoverflyRule = HoverflyRule.inSimulationMode(dsl( service("www.testwithcarobject.com") .get("/getmycar") .willReturn(success(json(myCar))) ));
then Hoverfly will automatically serialize the Car object instance to JSON, which we can visualize by creating another REST Assured test and having it log the response body to the console:
@Test public void testStubGetCarObject() { given(). when(). get("http://www.testwithcarobject.com/getmycar"). then(). log(). body(). and(). assertThat(). body("make",equalTo("Ford")); }
When run, this test generates the following console output, indicating that Hoverfly successfully serialized our Car instance to JSON:
{ "make": "Ford", "model": "Focus" }
Note that the getters of the POJO need to be named correctly for this to work. For example, the getter for the make field needs to be called getMake(), or else the object will not be serialized.
The final Hoverfly feature that I’d like to demonstrate is the ability to simulate error flows by returning bad requests. This can be done simply as follows:
@ClassRule public static HoverflyRule hoverflyRule = HoverflyRule.inSimulationMode(dsl( service("www.badrequest.com") .get("/req") .willReturn(badRequest()) ));
and can be verified by checking the status code corresponding with a bad request, which is HTTP 400, with a test:
@Test public void testStubBadRequest() { given(). when(). get("http://www.badrequest.com/req"). then(). assertThat(). statusCode(400); }
Similar to the Hoverfly product in general, its Java DSL is still under construction. This post was written based on version 0.3.6 and does not reflect newer versions. I had a bit of trouble getting the code to run, initially, but the SpectoLabs team have been very responsive and helpful in resolving the questions I had and the issues I encountered.
As an end note, please be aware that the Java DSL we’ve seen in this post is just one way of using Hoverfly. For a complete overview of the features and possibilities provided by the tool, please take a look at the online documentation.
A Maven project featuring all of the examples and tests in this post can be downloaded here. Tip: you’ll need to set your Java compiler compliance level to 1.8 in order for the code to compile and run correctly.
Very useful article. Thanks a lot. Have you tried also to create service for any POST? I’m trying, but some error (412) still occurred. Also java docu on hoverfly website is a bit confusing…
Hey Roman,
thanks! I haven’t tried it out for a POST method, but I’ll try and do so right away to see what happens.
I’ve tried using this definition:
service("www.trypost.com")
.post("/post")
.willReturn(success("{\"permanentNumber\": \"999\"}", "application/json"))
and this test succeeds:
@Test
public void testStubPost() {
given().
when().
post("http://www.trypost.com/post").
then().
assertThat().
statusCode(200);
}
OK, now it works. My problem was with fake SSL, but after updating @Test, it works… Thanks!
@Test
public void testStubPost() {
given().config(RestAssured.config()
.sslConfig(new SSLConfig().relaxedHTTPSValidation()))
.param(“username”, “fakeuser”)
.param(“password”, “fakepass”)
.when()
.post(“https://www.trypost.com/post”)
.then()
.assertThat()
.statusCode(200);
}
however:
this is in log:
{“destination”:”www.trypost.com”,”error”:”key \”fc7751e92fff7f418d7b75b0ff0753e7\” not found \n”,”key”:”fc7751e92fff7f418d7b75b0ff0753e7″,”level”:”warning”,”method”:”POST”,”msg”:”Failed to retrieve response from cache”,”path”:”/post”,”query”:””,”time”:”2017-02-16T12:11:52+01:00″}
Excellent! You’re welcome.
Pingback: Java Web Weekly, Issue 164 | Baeldung
Pingback: Java Testing Weekly 8 / 2017
I want to make stub of SOAP request. Response reflects some data from request. Lets say if I am passing 123 as a one of the tag of phone number in request, then in response I want 123 as phone number. How can I get response as per request?
Hey Sagar,
that’s an excellent question. I’m not sure if you can capture request element values and reuse them easily at the moment, but let me check with the SpectoLabs guys. I’ll get back to you ASAP.
Hi Sagar,
just got a response from the guys at SpectoLabs and they confirmed that at the moment, this is not possible. You are welcome to open an issue on the Hoverfly Java GitHub page, though.
Thanks for the quick response.
I have already opened this issue on the link you provided 3 days back, waiting for reply from them. I need in one of my project and its important for me to implement it.
Lets hope for positive reply from them
I’ve emailed you another way of contacting the guys at Specto. They’re normally very responsive.