Automated Smoke Testing with SSL

Smoke testing is the verification of functionality in a live system, be that development, test or production. Many of us are familiar with deploying an application, and clicking around to be sure nothing is obviously broken. Is the application running? Am I connecting to the database correctly? But there are many more holes that form as an application grows, and integration testing becomes critical to catching them.

Automating these smoke tests make our lives easier by allowing you to run all of the tests on every deployment. My Dropwizard application has a RESTful API, which I then write a Client API adapter in Java to call those endpoints.

The Client API is useful both for smoke testing, and for providing to client applications of an API to ease their integration. It should bundle into its own jar, as slim as possible, referencing another shared repository for the data model as the main application.

Example

REST Controller
The controller sets up the GET for a User by ID.

@Path("/api/user")
@Produces(MediaType.APPLICATION_JSON)
public class UserController {
@GET
@Path("{id}")
public User get(@PathParam("id") int id) { // ...

Client API
The API class will call an endpoint in a specified environment using a Jersey Client.

public class UserAPI {
    private final String baseUrl; // set in constructor
    private ObjectMapper mapper = new ObjectMapper();
    
    public User get(int id) {
    // create the Client
    Client client = getClient(host);
    // create a Builder
    Builder builder = client.resource(baseUrl + "/api/user/" + id).accept(MediaType.APPLICATION_JSON);
    // execute the GET
    ClientResponse response = builder.get(ClientResponse.class);
    // process for error/success, parse value

Smoke Test
The smoke test is a simple unit test, designed to use the API to call the running application. The baseUrl in this case should be configured, or set at runtime with a System property (-DtargetEnv=https://test:8473 with Maven)

public class UserAPITest {
    // make API, host should be configurable
    UserAPI userAPI = new UserAPI("https://localhost:8473");
    
    @Test
    public void testGetUser() {
        User user = userAPI.get(1);
        assertNotNull(user);
    }
}

Seems pretty straightforward right? Well there is one critical function you will need to have your smoke test work for http and https environments (see Dropwizard Configuration), and it will be used by all API classes — getClient:

protected Client getClient() {
    ClientConfig config = new DefaultClientConfig();
    
    try {
    // TrustManager will accept any certificate
    TrustManager[] trustAllCerts = { new X509TrustManager() {
        @Override public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
        
        @Override public void checkClientTrusted(X509Certificate[] certs, String authType) {}
        
        @Override public void checkServerTrusted(X509Certificate[] certs, String authType) {}
    }};
    
    // context will use the trust manager
    SSLContext ctx = SSLContext.getInstance("SSL");
    ctx.init(null, trustAllCerts, new SecureRandom());
    
    // uses the context configured for the ClientConfig
    config.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES,
    new HTTPSProperties(new HostnameVerifier() {
        @Override
        public boolean verify(String s, SSLSession sslSession) {
            // whatever your matching policy states
            return true;
        }
    }, ctx));
    
    } catch (Exception e) {
        log.error("Failed to setup the SSL handler", e);
    }
    
    Client client = Client.create(config);
}

Now you have a Client capable of calling an SSL application and accept a locally signed certificate.

Errors Overcome

Automated smoke testing with SSL environments can create a couple common problems:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching localhost

This occurs when the certificate sent from the REST endpoint is not accepted by the API

java.lang.IllegalStateException: SSLContextImpl is not initialized

This occurs when the SSLContext init function is not called before the Client is used.

Happy testing!