Your tests assume that JBoss is up and running

As a Java EE developer I tend to mix unit tests with integrations tests, usually using Arquillian. But sometimes, for obscure reasons, Arquillian cannot do the job (let’s say the packaging of the application to test is too obscur). So I want to deploy my application, and then, execute some tests. But what I really want is my tests to be executed if and only if the web application is deployed, if not, the tests are skipped. In other words, I want my Jenkins to automatically fail if there is no JBoss. For that I can use the JBoss HTTP management API and the JUnit assumptions.

Use Case

Testing a web application on JBossLet’s say I have a Java EE application with a simple HTML page (index.html), a servlet (MyServlet.java) and a REST endpoint (MyRESTEndpoint.java). Once packaged in a WAR (sampleJavaEEJBossUtil.war) and deployed on JBoss, I want to check all the different URLs and HTTP response code (i.e. is the servlet deployed and running, is the REST endpoint up and running…).

So this is the code of this very simple servlet

@WebServlet(urlPatterns = "/myservlet")
public class MyServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            response.getWriter().println("Hello from the REST endpoint");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

And this is the code that uses JAX-RS 2.0 client API to test the servlet just by pinging the URL (I could have done something more clever, but that’s not the point of this post.

public class URLTest {

    @Test
    public void checksTheServletIsDeployed() throws Exception {

        Client client = ClientBuilder.newClient();
        WebTarget target = client.target("http://localhost:8080/sampleJavaEEJBossUtil").path("myservlet");
        assertEquals(Response.Status.OK.getStatusCode(), target.request(MediaType.TEXT_PLAIN).get().getStatus());
    }
}

I execute the test while the application is deployed and JBoss is up and running, and the test is green. As you can imagine, if I shutdown JBoss or undeploy the application, the test fail. In this case, what I really want, is to ignore the test.

JBoss Admin CLI

So let’s say the web application is deployed, JBoss is up and running, everything is fine… let’s check all that with the JBoss CLI. You will find this command line interface under $JBOSS_HOME/bin/jboss-cli.sh. When you execute it, you connect to JBoss and enter a few commands to get the status of the server and other components (i.e. datasource, JMS factories…). The following commands check that JBoss is up and running and also that our sampleJavaEEJBossUtil.war is deployed:

$ ./jboss-cli.sh
You are disconnected at the moment. Type 'connect' to connect to the server or 'help' for the list of supported commands.
[disconnected /] connect
[standalone@localhost:9990 /] :read-attribute(name=server-state)
{
    "outcome" => "success",
    "result" => "running"
}
[standalone@localhost:9990 /] /deployment=sampleJavaEEJBossUtil.war:read-attribute(name=status)
{
    "outcome" => "success",
    "result" => "OK"
}

JBoss Admin REST API

On top of the command line interface, JBoss also has a REST interface for administration. So all the commands you type on CLI have an HTTP equivalent. So to know if JBoss is up and running and if the sampleJavaEEJBossUtil.war is deployed we can just go to a browser and type the following URLs:

http://localhost:9990/management?operation=attribute&name=server-state
http://localhost:9990/management/deployment/sampleJavaEEJBossUtil.war?operation=attribute&name=status

Notice that the JBoss administration APIs are under http://localhost:9990/management. Then, it’s just a matter of passing certain paths (e.g. deployment) or query parameters (e.g. operation=attribute)). If you type these URLs in a browser, you will also notice that you need to login as an administrator (first you need to create a user/password with the $JBOSS_HOME/bin/add-user.sh utility).

JBoss Administration APIs need DIGEST Authentication

So what if you don’t want to use a browser but instead use the JAX-RS 2.0 client API to connect to it? Let’s first use curl to see what it tells us.

~$ curl http://localhost:9990/management -v
* About to connect() to localhost port 9990 (#0)
* Connected to localhost (127.0.0.1) port 9990 (#0)
> GET /management HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:9990
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Connection: keep-alive
< WWW-Authenticate: Digest realm="ManagementRealm",domain="/management",algorithm=MD5
< Content-Length: 77

As you can see on the output on line number 11, the JBoss administration APIs need DIGEST authentication. So, supposing that the user and password are admin/admin, you need to enter the following:

~$ curl --digest 'http://admin:admin@localhost:9990/management' -v
* About to connect() to localhost port 9990 (#0)
* Connected to localhost (127.0.0.1) port 9990 (#0)
* Server auth using Digest with user 'admin'
> GET /management HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:9990
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Connection: keep-alive</pre>
< WWW-Authenticate: Digest realm="ManagementRealm",domain="/management",algorithm=MD5
< Content-Length: 77
<
* Ignoring the response-body
* Issue another request to this URL: 'http://admin:admin@localhost:9990/management'
* Connected to localhost (127.0.0.1) port 9990 (#0)
* Server auth using Digest with user 'admin'
> GET /management HTTP/1.1
> Authorization: Digest username="admin", realm="ManagementRealm", uri="/management", algorithm="MD5"
> User-Agent: curl/7.30.0
> Host: localhost:9990
> Accept: */*
>
< HTTP/1.1 200 OK
< Connection: keep-alive
< Authentication-Info: nextnonce="mJnypxK1UwINMTQwNTgwNDA2MTMzMHie0+SV9V15urC9I8n075w="
< Content-Type: application/json; charset=utf-8
< Content-Length: 2189
<
* Connection #0 to host localhost left intact

As you can see, when using DIGEST authentication, the output is much more verbose and the outcome is a return code 200 (instead of just 401). So now we have the two important ingredients to access the API with REST : we know how the URLs look like, and we know we need to use DIGEST authentication. So let’s see how to do it.

JAX-RS Client API to Access the JBoss Management API

JAX-RS 2.0 has a very nice client API. If we want to check if JBoss is up and running, what we need to do is access the URL http://localhost:9990/management?operation=attribute&name=server-state and check that it returns "running". With JAX-RS this is as simple as writing :

public static boolean isJBossUpAndRunning() {

    Client client = ClientBuilder.newClient();
    WebTarget target = client.target("http://localhost:9990/management").queryParam("operation", "attribute").queryParam("name", "server-state");
    Response response = target.request(MediaType.APPLICATION_JSON).get();
    return response.getStatus() == Response.Status.OK.getStatusCode() && response.readEntity(String.class).contains("running");
}

But unfortunately, this won’t work because it will return a 401 (Unauthorized). We need to authenticate using the DIGEST scheme. How do we do that in JAX-RS 2.0? Well, we can’t :o( The only trick I found (thanks to Bill Burke and Arun Gupta) is using the Apache HTTP Client and an extension of RESTEasy. So in the code below, the getClient method returns an authenticated HttpClient which is then used to access the JBoss administration REST APIs.

public class JBossUtil {

  private static ResteasyClient getClient() {
    // Setting digest credentials
    CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
    UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("admin", "admin");
    credentialsProvider.setCredentials(AuthScope.ANY, credentials);
    HttpClient httpclient = HttpClientBuilder.create().setDefaultCredentialsProvider(credentialsProvider).build();
    ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpclient, true);

    // Creating HTTP client
    return new ResteasyClientBuilder().httpEngine(engine).build();
  }

  public static boolean isJBossUpAndRunning() {

    WebTarget target = getClient().target("http://localhost:9990/management").queryParam("operation", "attribute").queryParam("name", "server-state");
    Response response = target.request(MediaType.APPLICATION_JSON).get();
    return response.getStatus() == Response.Status.OK.getStatusCode() && response.readEntity(String.class).contains("running");
  }
}

Here, I’ve simplified the code (no exception handling) and I’m only showing the method isJBossUpAndRunning. But you can check the full JBossUtil class on GitHub, I’ve created other methods.

The Test Class

Coming back to our use case, we now have all the pieces of the puzzle. We know how to check that JBoss is up and running (we could even check that the web application is actually deployed) and we can use the JUnit Assumptions. The following test will only be executed if and only if JBoss is up and running. If not, the test will automatically be ignored, thanks to the Assume API.

public class URLTest {

    @Test
    public void checksTheServletIsDeployed() throws Exception {
        Assume.assumeTrue(JBossUtil.isJBossUpAndRunning());

        Client client = ClientBuilder.newClient();
        WebTarget target = client.target("http://localhost:8080/sampleJavaEEJBossUtil").path("myservlet");
        assertEquals(Response.Status.OK.getStatusCode(), target.request(MediaType.TEXT_PLAIN).get().getStatus());
     }
}

Conclusion

Unit testing is important, but when you execute your code within a container, integration tests are also very useful. Arquillian solves most of the problems and simplifies integration testing, but other technics can also be used. The main ambition of our build system is to pass all the tests… or ignore some. With JUnit assumptions and some REST APIs, our tests can be knowledgeable and executed only if the container is up and running, if the web application is deployed, or if the datasource is actually enabled. Download the code, give it a try, and let me know what you think.

And thanks to Bill Burke, Arun Gupta, Alexis Hassler and Christos Vasilakis for their help and tips.

References

Categories: Java

Tagged as: ,

1 Comment »

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s