Testing your Liberty app is one of the most important stages in development and production. In this article I will go through how to write tests for Liberty apps, and run them using Maven, with our Secure Servlet sample. The Secure Servlet sample uses basic authentication to secure a servlet so I can show how to test both unsecured and secured applications.

Contents

In the following instructions, I provide the relevant Git and Maven commands (to run them, you’ll need to install Git and Maven) but you can use any editor or IDE and their built-in Git and Maven clients, if you prefer.

Getting started

Clone the sample.servlet.basicauth repo and change to the notests branch (this is the sample app with the tests removed so that you can add them):

git clone https://github.com/WASdev/sample.servlet.basicauth.git

cd sample.servlet.basicauth

git checkout notests

Compile and run the app:

mvn clean install liberty:run-server

In your browser, navigate to http://localhost:9080/basicauth/ to get a feel for how the application works (log in to the web page with the username user1 and password password). Stop the server before continuing: mvn liberty:stop-server.

The application is a very simple servlet with HTTP authentication added to the servlet endpoint. In order to test our application we need to verify that:

  • The secure endpoint works as intended when supplied with the correct credentials: We receive a HTTP response code of 200 (successful) and our servlet endpoint contains the expected output.
  • The secure endpoint fails when given no credentials: We receive a HTTP response code of 401 (unauthorized).
  • The secure endpoint fails when given the incorrect credentials: We receive a HTTP response code of 401 (unauthorized).
  • The secure endpoint fails when given the credentials for an unprivileged user: We receive a HTTP response code of 403 (forbidden).
  • The main landing page works as intended without credentials: We receive a HTTP response code of 200 (successful) and our servlet endpoint contains the expected output.

In the following steps, we’ll create tests to do this.

1 – Creating the test classes

To start, create a folder structure in the project for the tests: src/test/java/it.

Create two empty test classes inside the it test folder:

  • ApplicationIT.java, which will contain the main testing logic and methods
  • EndpointHelper.java, which will test the main landing page of our application by calling these methods

Create another folder called servlet in the it folder. Create a class in the servlet folder called LibertyServletTest.java, which will also call these methods to test the /servlet endpoint.

The folder structure should look like this:

You can actually name these folders and classes whatever you like. As you go through the following instructions, you can check your code against the completed files on GitHub:

2 – Setting up EndpointHelper.java

First, we’ll start writing the EndpointHelper.java class. This provides the main functionality of our tests. To start, put the following code into EndpointHelper:

package it;

import java.io.IOException;

import org.apache.http.client.ClientProtocolException;

public class EndpointHelper {
	
	public void testEndpoint(String endpoint, String expectedOutput) throws ClientProtocolException, IOException {
		String port = System.getProperty("liberty.test.port");
		String war = System.getProperty("war.context");
		System.out.println("Port: " + port + "\nWar: " + war);
        String url = "http://localhost:" + port + "/" + war + endpoint;
        System.out.println("Testing " + url);
	}
}

This code acquires the port and war variables from the pom.xml and prints them to show what will be tested. It also forms a url variable, which is given to our test methods to declare which URL we are testing – in this example, either the landing page or the secure endpoint.

3 – Calling the EndpointHelper class

To call the testEndpoint method, I added the following code into the ApplicationIT.java class:

package it;

import java.io.IOException;

import org.apache.http.client.ClientProtocolException;
import org.junit.Test;

public class ApplicationIT extends EndpointHelper {

    @Test
    public void testDeployment() throws ClientProtocolException, IOException {
        testEndpoint("/index.html", "<h1>Welcome to your Liberty Application</h1>");
    }
}

If you run mvn clean install now you should see the following output in the console:

Running it.ApplicationIT
Port: 9080
War: basicauth
Testing http://localhost:9080/basicauth/index.html
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 sec - in it.ApplicationIT

This shows that the test was able to successfully run the application and open the main web page (which does not require any credentials).

Next, add the following code to LibertyServletTest.java:

package it.servlet;

import it.EndpointHelper;

import java.io.IOException;

import org.apache.http.client.ClientProtocolException;
import org.junit.Test;

public class LibertyServletTest extends EndpointHelper {

    @Test
    public void testDeployment() throws ClientProtocolException, IOException {
    	testEndpoint("/servlet", "getAuthType: BASIC");
    }
}

This code calls our testEndpoint method again but for the secured /servlet endpoint (which does require credentials).

4 – The sendRequest method

Now let’s start writing the code to actually perform these tests. Add the following method to EndpointHelper.java at the end of the class:

    //send HTTP request credentials, with credentials if necessary
    public HttpResponse sendRequest(String url, String requestType, String credentials) throws ClientProtocolException, IOException {
        if (credentials!=null){
            String encoding = Base64.getEncoder().encodeToString((credentials).getBytes());
            HttpPost httppost = new HttpPost(url);
            httppost.setHeader("Authorization", "Basic " + encoding);
            System.out.println("Executing secure request " + httppost.getRequestLine());
            HttpClient httpclient = HttpClientBuilder.create().build();
            HttpResponse response = httpclient.execute(httppost);
            return response;
        }
        else
        {
            HttpPost httppost = new HttpPost(url);
            System.out.println("Executing request " + httppost.getRequestLine());
            HttpClient httpclient = HttpClientBuilder.create().build();
            HttpResponse response = httpclient.execute(httppost);
            return response;
        }

    }

This method creates and sends our HTTP request to the endpoint. It returns an HttpResponse object containing the status code and text content, which we can verify against our expected results as part of our testing. If credentials are present when the method is called, it adds them to the request.

We’ve just added a number of new classes, so add the following imports at the top of the file:

import java.util.Base64;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClientBuilder;

5 – Finishing the EndpointHelper main method

Now let’s outline the methods to finish the main method, testEndpoint. First, we need to determine if the request is secure or not. For this article, I’ve added a hardcoded if statement because, for all secure requests, the expectedOutput of the servlet begins with getAuthType: BASIC (you can easily change this to a variable for more complex applications).

If we’re testing an unsecured endpoint, we don’t have to perform the numerous security tests and so we only need one method in our else statement, just to check we’re getting the expected output.

If we are testing a secured endpoint, we need to test for the correct output with the correct credentials. We also need to test the secured endpoint in alternative scenarios: with no credentials; with incorrect credentials (not corresponding to any account); with correct credentials but for the wrong account, which lacks the privileges needed to access the secured endpoint. I have outlined four methods within the if statement, one for each test.

Each method needs a url variable passed in. The two tests we expect to succeed, testCredentials and testUnsecure, need the expectedOutput variable passed in so we can check what’s on the page.

Add the following code to the testEndpoint method (within EndpointHelper.java, below System.out.println("Testing " + url);:

        if (expectedOutput.equalsIgnoreCase("getAuthType: BASIC")){
            testCredentials(url, expectedOutput);
            testNoCredentials(url);
            testWithWrongPassword(url);
            testWrongUserId(url);
        }
        else {
            testUnsecure(url, expectedOutput);
        }

If you’re using an IDE, each of the lines that mentions the tests will display errors because we haven’t written the tests yet. That’s what we’ll do next.

6 – Writing the tests

Let’s write some tests!

6.1 – Our first test!

Below our testEndpoint method, create the testCredentials method:

	//test secure endpoint with credentials
	public void testCredentials(String url, String expectedOutput) throws ClientProtocolException, IOException {
	    System.out.println("Test secure endpoint with credentials started");
	    HttpResponse response = sendRequest(url, "GET", "user1:password");
	    testWithResponse(response, expectedOutput);
        System.out.println("Test secure endpoint with credentials finished");
	}

I’ve added System.out.println statements to make the logs as clear as possible in the event of any failures. This code creates an HttpResponse object, response, to store the data we retrieve from our request, and calls our sendRequest method with the credentials. The credentials are currently hardcoded into the method but can be changed to variables if required.

6.2 – Helper methods

In each of our test methods, we test the response class by either:

  • Expecting the request to be successful and testing that the output is equal to expectedOutput
  • Expecting the request to fail and testing that we receive the appropriate HTTP response code

To avoid writing lots of duplicate code, let’s create two more methods in the EndpointHelp.java class (before the HttpResponse method) that perform these actions. The following code acquires the HTTP status code and checks that it is 200 (success). It then acquires the response and checks it against the expectedOutput:

public void testWithResponse(HttpResponse response, String expectedOutput) throws UnsupportedOperationException, IOException
	{
        int responseCode = response.getStatusLine().getStatusCode();
        assertTrue("Incorrect response code: " + responseCode,
                   responseCode == 200);
        String responseString = IOUtils.toString(response.getEntity().getContent());
        assertTrue("Incorrect response, response is: " + responseString + "Expected: " + expectedOutput,
                   responseString.contains(expectedOutput));
}

The following code acquires the HTTP status code and checks that it is equal to the relevant code through the expectedResponseCode variable.

	public void testWithoutResponse(int expectedResponseCode, HttpResponse response)
	{
	    int responseCode = response.getStatusLine().getStatusCode();
        assertTrue("Incorrect response code (expected " + expectedResponseCode + "): " + responseCode,
                   responseCode == expectedResponseCode);
}

For example, in the case of testCredentials we expect the test to pass and display the correct output so we use the testWithResponse class. When we go on to test with incorrect credentials we are testing for a response code only and so we use the testWithoutResponse class.

We’ve now introduced the assertTrue class and IOUtils, so add the following imports:

import static org.junit.Assert.assertTrue;
import org.apache.cxf.helpers.IOUtils;

6.3 – Finishing the tests

Let’s now add the remaining four test methods:

	//test secure endpoint with no credentials
	public void testNoCredentials(String url) throws ClientProtocolException, IOException {
	    System.out.println("Test secure endpoint with no credentials started");
	    HttpResponse response = sendRequest(url, "GET", null);
	    testWithoutResponse(401, response);
        System.out.println("Test secure endpoint with no credentials finished");
	}

	//test secure endpoint with incorrect credentials
	public void testWithWrongPassword(String url) throws ClientProtocolException, IOException {
	    System.out.println("Test secure endpoint with incorrect credentials started");
	    HttpResponse response = sendRequest(url, "GET", "notAUser:notAPassword");
	    testWithoutResponse(401, response);
        System.out.println("Test secure endpoint with incorrect credentials finished");
	}

	//test secure endpoint with valid but unprivileged credentials
	public void testWrongUserId(String url) throws ClientProtocolException, IOException {
	    System.out.println("Test secure endpoint with valid but unprivileged credentials started");
	    HttpResponse response = sendRequest(url, "GET", "user2:password");
	    testWithoutResponse(403, response);
        System.out.println("Test secure endpoint with valid but unprivileged credentials finished");
	}

	//test without credentials
	public void testUnsecure(String url, String expectedOutput) throws ClientProtocolException, IOException {
	    System.out.println("Test without credentials started");
	    HttpResponse response = sendRequest(url, "GET", null);
	    testWithResponse(response, expectedOutput);
        System.out.println("Test without credentials finished");
	}

Running mvn install now successfully tests your application before running the application, and notifies you of any failures! Run mvn install liberty:run-server to install and run the application.

Of course, this article has only shown how you would write tests for a simple secure servlet application. However, you can apply this to any application.

If you use the Liberty App Accelerator to generate your project, the project includes some basic tests to which you can add others in the same way. The generated project comes with two additional test classes:

  • it/TestApplicationOnBluemix.java, which is a test class designated for Bluemix
  • test/TestApplication.java, which is empty and you can use it for your own tests tests

You can follow the principles of this example (minus the security element) using our servlet sample, which provides a similar but more compact project.

Join The Discussion

Your email address will not be published. Required fields are marked *