Using the Facade Pattern to More Easily Test static
or Complex Classes
Something that came up recently with some cross-organisation colleagues is how to better test a complex interface, such as the AWS SDK in a unit test.
It's something I've come up against before, both with complex clients like AWS SDK's S3Client
, but also with libraries like RestAssured or SLF4J's MDC
. And each time, the Facade design pattern comes up as being a nice solution.
Let's say that we want to write a unit test to verify that the following API request is sent using Rest Assured:
import java.util.UUID;
import static io.restassured.RestAssured.given;
public class ApiClient {
public Object doCall() {
return RestAssured.given()
.header("correlation-id", UUID.randomUUID().toString())
.get("http://example.com/api");
}
}
Unfortunately we'll notice that given()
is a static
method which we cannot test - at least without something evil like PowerMock, which we should really avoid where possible.
One of the easiest ways to solve this - which I use every time I start to work with Rest Assured - is to create a RequestSpecificationFactory
, which is a facade around RestAssured's given
method:
import static io.restassured.RestAssured.given;
import io.restassured.specification.RequestSpecification;
public class RequestSpecificationFactory {
public RequestSpecification newRequestSpecification() {
return given();
}
}
There are other things we can do here, like be able to apply common configuration, or allow constructing the RequestSpecificationFactory
with a set of Filter
s to apply to each method, but the key thing here is that we create a class that extracts the static
calls, or calls that may require a lot of setup, or cannot be easily unit tested, and we make it injectable:
import java.util.UUID;
public class ApiClient {
private final RequestSpecificationFactory factory;
public ApiClient() {
this(new RequestSpecificationFactory());
}
/*
Test only, but could alternatively be for all use cases, and we require a
<code>RequestSpecificationFactory</code> to be injected
*/
ApiClient(RequestSpecificationFactory factory) {
this.factory = factory;
}
public Object doCall() {
return factory
.newRequestSpecification()
.header("correlation-id", UUID.randomUUID().toString())
.get("http://example.com/api");
}
}
Now we can provide this RequestSpecificationFactory
instance to be injected via the constructor, allowing for easy unit testing, as well as giving us the ability to i.e. use the same instance of the RequestSpecificationFactory
everywhere.