What this is about
Let’s say we have a web application that we need to test. It might be a web service or web site. We have to test its behavior when one of its dependencies (for example another web service) has issues or returns something unexpected.
So we need that dependency to behave the way that we need for our dependency tests. Also it would be great if we could change its behavior programmatically for every test.
Usually it’s almost impossible for most cases especially if that dependency is an external web service.
In this case we need to use mocks - we temporary replace the dependency with a mock service that can simulate dependency’s behavior.
Stubby4j. What we need to do
- Run the Stubby4j mock service - see How to run Stubby4j
- Figure out how to make requests to the dependency go to the Stubby4j mock service - out of scope for this post
- Implement the Stubby4j client to configure the mock service programmaticaly before running tests
- Write tests for the target service that is using that dependency.
Stubby4j Client
First, we need to create some classes that describe our stubs meaning what particular request (or pattern) should trigger that particular response.
Every Stub should contain the discription, id, request and response fields.
Stub
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@JsonSerialize(include= JsonSerialize.Inclusion.NON_NULL)
public class Stub {
private String description;
private String uuid;
private StubRequest request;
private StubResponse response;
public Stub(StubRequest request, StubResponse response) {
this.request = request;
this.response = response;
}
public Stub(String description, String uuid, StubRequest request, StubResponse response) {
this.description = description;
this.uuid = uuid;
this.request = request;
this.response = response;
}
// Getters ad setters
...
}
StubRequest
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.List;
import java.util.Map;
@JsonSerialize(include= JsonSerialize.Inclusion.NON_NULL)
public class StubRequest {
private List<String> method;
private String url;
private String post;
private Map<String, String> headers;
private Map<String, String> query;
public StubRequest(List<String> method, String url, String post, Map<String, String> headers, Map<String, String> query) {
this.method = method;
this.url = url;
this.post = post;
this.headers = headers;
this.query = query;
}
public StubRequest method(List<String> method) {
this.method = method;
return this;
}
public StubRequest url(String url) {
this.url = url;
return this;
}
public StubRequest post(String post) {
this.post = post;
return this;
}
public StubRequest headers(Map<String, String> headers) {
this.headers = headers;
return this;
}
public StubRequest query(Map<String, String> query) {
this.query = query;
return this;
}
// Getters
...
}
StubResponse
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.Map;
@JsonSerialize(include= JsonSerialize.Inclusion.NON_NULL)
public class StubResponse {
private Map<String, String> headers;
private int latency;
private int status;
private String body;
public StubResponse(Map<String, String> headers, int latency, int status, String body) {
this.headers = headers;
this.latency = latency;
this.status = status;
this.body = body;
}
public StubResponse headers(Map<String, String> headers) {
this.headers = headers;
return this;
}
public StubResponse latency(int latency) {
this.latency = latency;
return this;
}
public StubResponse status(int status) {
this.status = status;
return this;
}
public StubResponse body(String body) {
this.body = body;
return this;
}
// Getters
...
}
Stubby4j Admin service interface
You can configure Stubby4j stubs through REST calls. This is the interface
import javax.ws.rs.*;
import java.util.List;
public interface StubbyAdmin {
@DELETE
@Path("/")
void deleteAll();
@DELETE
@Path("/{uuid}") // Shouldn't be numeric
void deleteByUUID(@PathParam("uuid") String uuid);
@POST
@Path("/")
@Consumes("application/json")
void addStub(List<Stub> stubs);
}
Stubby4j Admin client
StubbyAdmin getStubbyAdmin() {
return ClientBuilder.createNewHttpClient()
.target(STUBBY_URL + ":8889")
.proxy(StubbyAdmin.class);
Dependency and Error Tests
Let’s assume that we need to test the login method of the Auth Service. When the login(username, password) method is called then the Auth Service makes a call to User service to check if the login and password are correct.
So the User service is a dependency for the Auth Service.
Dependency throws Server Error 500 example
@Test
public void serverErrorResponseFromUserService() {
Stub stub = new Stub(
"500 error from User API", "uuid-12345",
new StubRequest(Arrays.asList("POST"), "/api/user/verify", null, null, null),
new StubResponse(null, 200, 500, null)
);
stubbyAdmin.addStub(Arrays.asList(stub));
// End of Stub Setup
Response response = authServiceClient.login("admin", "password");
// Verify the expected behavior here
...
stubbyAdmin.deleteByUUID("uuid-12345");
}
You can see that we can clean the stubs after the test by calling deleteByUUID(..). You can also delete all stubs by calling deleteAll().
You may also find these posts interesting: