Sunday, June 6, 2010

Testing BlazeDS remote objects with soapUI

Testing never was an easy thing. I am following TDD approach for at least last 5-6 years and really excited about it. But for me, TDD is not only unit testing. It is whole set of testing techniques I find appropriate for particular project (unit tests, integration tests, performance tests, ...). Recently I discovered excellent tool - soapUI. It has a bunch of useful features but the one I would like to cover today is testing BlazeDS services using AMF protocol.

Before we start with code snippets, let's copy BlazeDS libraries to bin/ext folder of soapUI installation:
- commons-codec-1.3.jar
- commons-httpclient-3.0.1.jar
- commons-logging.jar
- flex-messaging-common.jar
- flex-messaging-core.jar
- flex-messaging-opt.jar
- flex-messaging-proxy.jar
- flex-messaging-remoting.jar

Among other very cool features, soapUI supports Groovy as a scripting language which is just awesome. So all my examples will be in Groovy. Let's start with necessary part: creating connection and aliasing services.
import flex.messaging.io.amf.ASObject;
import flex.messaging.io.amf.client.AMFConnection;
import flex.messaging.messages.CommandMessage;
import flex.messaging.util.Base64.Encoder;
import flex.messaging.messages.Message;
import flex.messaging.io.amf.ASObject;

def clientId = "soapUI." +  UUID.randomUUID().toString();
def amfConnection = new AMFConnection();
amfConnection.instantiateTypes = false

amfConnection.connect(  "http://localhost:8080/server/messagebroker/amf" );   
amfConnection.addAmfHeader( Message.FLEX_CLIENT_ID_HEADER, clientId );

// Create remote object aliases
amfConnection.registerAlias( "testService", "com.example.remoteobjects.TestFacade" );
Having connection established, we are ready to call service methods of any aliased remote objects. Here is a code snippet to call service method foo() which has no parameters.
// Calling service method without arguments
def result = amfConnection.call( "testService.foo" );
And here is a code snippet to call service method foo() which accepts one parameter of type Person.
// Calling service method with object as argument
def person = new ASObject( "com.example.Person" );
person["name"]= "John Smith" ;

result = amfConnection.call( "testService.foo", person );
There's one issue which I've omitted for a moment. If you have security enabled for channels, you must proceed with authentication before calling any services. It's quite simple to do:
def credentials = encodeToBase64( username ) + ":" + encodeToBase64( password );

CommandMessage c = new CommandMessage();
c.setHeader( Message.FLEX_CLIENT_ID_HEADER, clientId );
c.setOperation( CommandMessage.LOGIN_OPERATION );
c.setDestination( "auth" );
c.setBody( encodeToBase64( credentials ) );      
amfConnection.call( null, c );

def encodeToBase64( final byte[] bytes ) {
    Encoder encoder = new Encoder( bytes.length );
    encoder.encode( bytes );
    return encoder.drain();     
}
When we are done, let's be a good citizens and close connection:
amfConnection.close();
Again, if security for channels is enabled, do logout before closing connection:
CommandMessage c = new CommandMessage();  
c.setHeader( Message.FLEX_CLIENT_ID_HEADER, clientId );
c.setOperation( CommandMessage.LOGOUT_OPERATION );
c.setDestination( "auth" );   
amfConnection.call( null, c );
Having such a script, soapUI allows you to create load test based on it. It also support quite complicated scenarios with many scripts involved and parameters passed from one to another. There is very good blog which contains tons of very useful information how to use soapUI for different kind of testing.