Archive for July, 2010
Testing SSL (HTTPS) clients with Junit and Jetty
Wednesday, July 14th, 2010
Posted in: https, junit, ssl, unit test J2EE, java, maven 2, open source.
I recently ran into a situation where a web service client had issues when invoking a webservice via HTTPS. To reproduce, I needed a lightweight, JUnit-based test to reproduce the problem and write a regression test. Here is how I got it to work:
First, I used the basic HTTP test setup. Now however, I needed Jetty to provide a HTTPS connector. For this to work, Jetty – just like any other webserver, such as apache – needs a server certificate and a private key in a keystore. To create the keystore, I simply followed the excellent Jetty SSL guide. Namingly, I did this (using Ubuntu, see the above link for alternatives):
openssl genrsa -des3 -out jetty.key openssl req -new -x509 -key jetty.key -out jetty.crt openssl pkcs12 -inkey jetty.key -in jetty.crt -export -out jetty.pkcs12 $JAVA_HOME/bin/keytool -importkeystore -srckeystore jetty.pkcs12 -srcstoretype PKCS12 -destkeystore keystore
Once I had the keystore set up, I placed it in the src/main/resources folder (the root of the classpath, in case you’re not a maven user) and extended the HttpTestServer like so:
import java.net.URL;
import org.junit.Ignore;
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.security.SslSocketConnector;
/**
* A test server for testing SSL requests.
*
* @author Olaf Otto
*/
@Ignore
public class SslTestServer extends HttpTestServer {
private static final int HTTPS_PORT = 50443;
public SslTestServer() {
}
public SslTestServer(String mockData) {
super(mockData);
}
@Override
protected void configureServer() {
super.configureServer();
Connector secureConnector = createSecureConnector();
getServer().addConnector(secureConnector);
}
private Connector createSecureConnector() {
SslSocketConnector connector = new SslSocketConnector();
connector.setPort(HTTPS_PORT);
URL keystoreUrl = getClass().getClassLoader().getResource("keystore");
connector.setKeystore(keystoreUrl.getFile());
connector.setKeyPassword("test");
return connector;
}
public static void main(String[] args) {
SslTestServer server = new SslTestServer();
try {
server.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
(I added the @Ignore annotation as I placed the class in the test sources folder, though it’s not a test…)
You might use it in your JUnit-based test like so:
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* @author Olaf Otto
*/
public class MySslTest {
private SslTestServer _server;
@Before
public void startServer() throws Exception {
_server = new SslTestServer();
_server.start();
}
@After
public void stopServer() throws Exception {
_server.stop();
}
@Test
public void mySsltest() {
_server.setMockResponseData("<xml>My client expects this response</xml>");
// Perform tests on port 50443...
}
}
Here is the whole package as a maven 2 project (including the keystore with a self-signed certificate). Download, unzip, and give it a try (mvn test).
Lightweight testing of (webservice) HTTP clients with JUnit and Jetty
Wednesday, July 14th, 2010
Posted in: http clients, junit, test, webservices J2EE, java, maven 2, open source.
If you’re working with webservice clients you will certainly have noticed the complexity of integration-testing your webservice clients. Building webservice clients can already be quite a complex task, but providing a mock-up webservice backend delivering useful test responses is quite often just to much work, if not impossible, since many web service backends are very complex constructs. Furthermore, setting up a backend for each integration test consumes valuable build time.
As an alternative, one may simply replace the backend with a mock HTTP server that does one thing: deliver the expected webserver response – i.e. XML data – when called by the client. All you need to set this up is a HTTP server and a pre-recorded service response (quite often this comes with WS client specifications). If you don’t have this data, you can record it, for example using a proxy server in between your WS client and a real WS backend, or a tool such as wireshark.
The recorded XML response thus represents your assumptions against which you test the client. This makes your tests fast, lightweight and a lot better to understand.
Here is my Jetty-based test HTTP server:
package de.sysbsb.test;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.apache.commons.io.IOUtils.write;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.junit.Ignore;
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.HttpConnection;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.AbstractHandler;
/**
* A server for answering HTTP requests with test response data.
*
* @author Olaf Otto
*/
@Ignore
public class HttpTestServer {
public static final int HTTP_PORT = 50036;
private Server _server;
private String _responseBody;
private String _requestBody;
private String _mockResponseData;
public HttpTestServer() {
}
public HttpTestServer(String mockData) {
setMockResponseData(mockData);
}
public void start() throws Exception {
configureServer();
startServer();
}
private void startServer() throws Exception {
_server.start();
}
protected void configureServer() {
_server = new Server(HTTP_PORT);
_server.setHandler(getMockHandler());
}
/**
* Creates an {@link AbstractHandler handler} returning an arbitrary String as a response.
*
* @return never <code>null</code>.
*/
public Handler getMockHandler() {
Handler handler = new AbstractHandler() {
public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException {
Request baseRequest = request instanceof Request ? (Request) request : HttpConnection.getCurrentConnection().getRequest();
setResponseBody(getMockResponseData());
setRequestBody(IOUtils.toString(baseRequest.getInputStream()));
response.setStatus(SC_OK);
response.setContentType("text/xml;charset=utf-8");
write(getResponseBody(), response.getOutputStream());
baseRequest.setHandled(true);
}
};
return handler;
}
public void stop() throws Exception {
_server.stop();
}
public void setResponseBody(String responseBody) {
_responseBody = responseBody;
}
public String getResponseBody() {
return _responseBody;
}
public void setRequestBody(String requestBody) {
_requestBody = requestBody;
}
public String getRequestBody() {
return _requestBody;
}
public static void main(String[] args) {
HttpTestServer server = new HttpTestServer();
try {
server.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void setMockResponseData(String mockResponseData) {
_mockResponseData = mockResponseData;
}
public String getMockResponseData() {
return _mockResponseData;
}
protected Server getServer() {
return _server;
}
}
You can use it in a JUnit test like so:
package de.sysbsb.test;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.io.IOUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* @author Olaf Otto
*/
public class MyClientTest {
private HttpTestServer _server;
@Before
public void startTestServer() throws Exception {
_server = new HttpTestServer();
_server.start();
}
@After
public void stopTestServer() throws Exception {
_server.stop();
}
@Test
public void someClientTest() throws IOException {
_server.setMockResponseData("<xml><testdata>hello, world</testdata></xml>");
// or as a much more elegant alternative:
_server.setMockResponseData(getRecordedResponse("testdata.xml"));
// run your client tests, using the HttpTestServer.HTTP_PORT
}
private String getRecordedResponse(String recordedResponseFile) throws IOException {
InputStream testDataStream = getClass().getClassLoader().getResourceAsStream(recordedResponseFile);
return IOUtils.toString(testDataStream);
}
}
Here are is the HTTP test server and a JUnit test example as a maven 2 project.
5 Comments