Writing Integration tests for WSO2 IOT server

Integration testing plays an important role in software testing and it is where we verify that each module of the software functions as it is supposed to be. In WSO2 IOT server, we have written an adequate amount of test cases to cover all the out-of-box functionalities and also provided the extensibility to write customized test cases to cover add-on features. This document will give an introduction on how does the integration tests have been implemented and how can they be extended to cover add-on functionalities.

TestNg has been used as the underlying backbone of the Carbon test automation framework which provides a powerful test execution control mechanism. The framework extensively cater to write test classes with a composition of JMeter and TestNg annotations.

TestNG framework is used to test the functionalities of the IOT server what are mainly known as core CDMF features, such as device enrolment, operations management, notification management etc.

What follows up in this article will guide you through the basics of writing and executing a simple TestNg test case.

Executing testNg test cases

In IOT server source code, you will find all the integration tests residing at the following location of product-iots repository.

product-iots/modules/integration/integration-tests

All the integration test classes are located in src/main subdirectory and the test resources such as testNg.xml file, java keystores and Jmeter scripts are located in the src/resources subdirectory. TestNg engine reads the testng.xml and then executes all the test cases that have been mentioned in the file.

There are several ways to execute the tests. If you want to execute all the test classes at once, you can simply type mvn install in the command line inside the /integration-tests directory. This will execute all the test classes as mentioned in the testng.xml file. If there is only one test class needs to be executed, you can comment out the other test cases from the testng.xml and then run the mvn install command. Optionally, you can execute a particular test class by typing the following command.

mvn surefire:test -Dtest=<name of the test class>
I.e.: mvn surefire:test -Dtest=AndroidEnrollment

Writing a simple test case

Here is a quick overview of some annotations available in TestNG that we will be using in this scenario. You can find a complete list of annotations here.

  • @BeforeClass — The annotated method will run only once before the first test method in the current class is invoked. In the context of WSO2, you can use this annotation to obtain security tokens for admin services, configure the server and configure any services.
  • @Test — Marks a class or a method as part of the test. Actual implementation of the test goes here. You may programatically invoke the service and retrieve data and assert them.
  • @AfterClass — The annotated method will be run only once after all the test methods in the current class have been run. Clear any server configuration made and reset everything to the last configuration to use by the next test in the suit.

Load the integration module to the IDE and create a new package named “SimpleIOTTest” within the tests-integration module. Then create a Java class named SimpleIOTTest and copy the following code. Here, we will be obtaining the licence code for Android Device enrollment and assert whether the response HTTP code is 200.


package org.wso2.iot.integration.SimpleIOTTest;
import junit.framework.Assert;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.net.util.Base64;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;
import org.wso2.carbon.automation.engine.context.TestUserMode;
import org.wso2.carbon.automation.engine.context.beans.User;
import org.wso2.carbon.automation.test.utils.http.client.HttpResponse;
import org.wso2.iot.integration.common.Constants;
import org.wso2.iot.integration.common.OAuthUtil;
import org.wso2.iot.integration.common.RestClient;
import org.wso2.iot.integration.common.TestBase;
public class SimpleIOTTest extends TestBase {
private RestClient client;
private final String APPLICATION_JSON = "application/json";
private final String CONFIG_MGT_ENDPOINT = "/api/device-mgt/android/v1.0/configuration/";
private final String LICENSE_ENDPOINT = "license";
private final String SC_OK = "200";
@Factory(dataProvider = "userModeProvider")
public SimpleIOTTest(TestUserMode testUserMode) {
this.userMode = testUserMode;
}
@BeforeClass(alwaysRun = true)
public void initTest() throws Exception {
super.init(userMode);
String tenantDomain = automationContext.getContextTenant().getDomain();
backendHTTPSURL = automationContext.getContextUrls().getWebAppURLHttps().replace("9443", String.valueOf(Constants
.HTTPS_GATEWAY_PORT)).replace("/t/" + tenantDomain , "");
User currentUser = getAutomationContext().getContextTenant().getContextUser();
byte[] bytesEncoded = Base64
.encodeBase64((currentUser.getUserName() + ":" + currentUser.getPassword()).getBytes());
String encoded = new String(bytesEncoded);
accessToken = OAuthUtil.getOAuthTokenPair(encoded, backendHTTPSURL, backendHTTPSURL, currentUser.getUserName(),
currentUser.getPassword());
accessTokenString = "Bearer " + accessToken;
this.client = new RestClient(backendHTTPSURL, APPLICATION_JSON, accessTokenString);
}
@Test(description = "Test get android license.")
public void testGetLicense() throws Exception {
HttpResponse response = client.get(CONFIG_MGT_ENDPOINT + LICENSE_ENDPOINT);
Assert.assertEquals(SC_OK, response.getResponseCode());
}
}

Then copy the following code snippet in testng.xml resides in resources directory.


<test name="Sample-Test" preserve-order="true" parallel="false">
<classes>
<class name="org.wso2.iot.integration.SimpleIOTTest.SimpleIOTTest"/>
</classes>
</test>

view raw

testng.xml

hosted with ❤ by GitHub

Finally, execute the test by simply typing mvn install on the command line inside the tests-integration directory.

Writing Axis2 Handlers

Interfering the message flow

Axis2 handler is the smallest invocation unit of the axis2 engine, which has ability to intercept into the message flow during the runtime and operate read/write operations on an incoming or outgoing message. i.e: if you need to route messages coming with specific attributes to a different end point or count the number of messages with that attribute you can simply make use of axis2 handlers. Since, handlers has that ability of intercepting to message flow and read/write to message context, inherently they are capable of suspending the message flow.

Axis2 handlers give you full authority over SOAP messages travel through. As a result of that super control, handlers can add additional headers to SOAP message, add body content and read the message header or the body.

Specially, when implementing ws-reliableMessaging handlers can be used to control the flow of the message. if the message supposed to be delivered second, arrives first it can be hold till the first message arrives and then it can be send to execution.

Inbuilt axis2 handlers

Axis2 comes with a list of inbuilt handlers to implement ws-* and other functionalities like parsing headers.

Writing an axis2 Handler

To write a handler, you either have to extend the AbstractHandler class or implement the Handler interface.

[java]/**
* Created by malintha on 9/25/14.
*/
public class MyHandler extends AbstractHandler {

private static Log audit = LogFactory.getLog(MyHandler.class);
@Override
public InvocationResponse invoke(MessageContext messageContext) throws AxisFault {
SOAPEnvelope mes = messageContext.getEnvelope();
SOAPHeader mesh = mes.getHeader();
SOAPBody mesb = mes.getBody();
OMElement bodyChild = mesb.getFirstElement();
//TODO statements
}
//This return statement defines what to be done with the message flow after this handler
//execution is finished.
return InvocationResponse.CONTINUE;

}[/java]

The return statement of the

invoke()

method defines what has to be done with the message flow after the execution of current handler is finished.

 

  • Continue: The handler thinks that the message is ready to go forward.
  • Suspend: The handler thinks that the message cannot be sent forward since some conditions are not satisfied; so the execution is suspended.
  • Abort: The handler thinks that there is something wrong with the message, and cannot therefore allow the message to go forward.

In most cases, handlers will return

InvocationResponse.CONTINUE

as the return value.

 

An axis2 phase consists of several handlers and after each handler calls InvocationResponse.CONTINUE after the execution, the message is immediately passed to the next handler

Packaging the handler

Easiest way of packaging a handler is shipping it with an axis2 module. By that way, each handler can act independently and number of handlers can be shipped.

A module is a collection of handlers along with its configurations. We can define more than one handlers within a module.

Create the following directory structure in your project.

LoggingModule

-Resources
 -META-INF
  -module.xml
-src
 -LoggingModule.java
 -LoggingHandler.java

LoggingModule.java No implementation in this class at the moment.

 


/**
* Created by malintha on 9/25/14.
*/
public class LoggingModule implements Module {
public void init(ConfigurationContext configurationContext, AxisModule axisModule) throws AxisFault {}
public void engageNotify(AxisDescription axisDescription) throws AxisFault {}
public void shutdown(ConfigurationContext configurationContext) throws AxisFault {}
public void applyPolicy(org.apache.neethi.Policy policy, AxisDescription axisDescription) throws AxisFault {}
public boolean canSupportAssertion(org.apache.neethi.Assertion assertion) {
return false;
}
}

LoggingHandler.java

 


public class LoggingHandler extends AbstractHandler implements Handler {
private static final Log log = LogFactory.getLog(LogHandler.class);
private String name;
public String getName() {
return name;
}
public InvocationResponse invoke(MessageContext msgContext) throws AxisFault {
log.info(msgContext.getEnvelope().toString());
return InvocationResponse.CONTINUE;
}
public void revoke(MessageContext msgContext) {
log.info(msgContext.getEnvelope().toString());
}
public void setName(String name) {
this.name = name;
}
}

“module.xml” contains the deployment configurations for a particular module. It contains details such as the Implementation class of the module (in this example it is the “LoggingModule” class and various handlers that will run in different phases). The “module.xml” for the logging module will be as follows:


<module name="logging" class="LoggingModule">
<InFlow>
<handler name="InFlowLogHandler" class="LogHandler">
<order phase="loggingPhase" />
</handler>
</InFlow>
<OutFlow>
<handler name="OutFlowLogHandler" class="LogHandler">
<order phase="loggingPhase"/>
</handler>
</OutFlow>
<OutFaultFlow>
<handler name="FaultOutFlowLogHandler" class="LogHandler">
<order phase="loggingPhase"/>
</handler>
</OutFaultFlow>
<InFaultFlow>
<handler name="FaultInFlowLogHandler" class="LogHandler">
<order phase="loggingPhase"/>
</handler>
</InFaultFlow>
</module>

view raw

module.xml

hosted with ❤ by GitHub

 

  • InFlow – Represents the handler chain that will run when a message is coming in.
  • OutFlow – Represents the handler chain that will run when the message is going out.
  • OutFaultFlow – Represents the handler chain that will run when there is a fault, and the fault is going out.
  • InFaultFlow – Represents the handler chain that will run when there is a fault, and the fault is coming in.

Then you can modify the axis2.xml in order to introduce LoggingModule to the axis2 engine. Here we have defined a custome phase named LoggingPhase here.


<phaseOrder type="outflow">
<!– user can add his own phases to this area –>
<phase name="OperationOutPhase"/>
<phase name="loggingPhase"/>
<!–system predefined phases–>
<!–these phases will run irrespective of the service–>
<phase name="PolicyDetermination"/>
<phase name="MessageOut"/>
</phaseOrder/>
<phaseOrder type="INfaultflow">
<!– user can add his own phases to this area –>
<phase name="OperationInFaultPhase"/>
<phase name="loggingPhase"/>
</phaseOrder>
<phaseOrder type="Outfaultflow">
<!– user can add his own phases to this area –>
<phase name="OperationOutFaultPhase"/>
<phase name="loggingPhase"/>
<phase name="PolicyDetermination"/>
<phase name="MessageOut"/>
</phaseOrder>

view raw

axis2.xml

hosted with ❤ by GitHub

services.xml


<service name="MyServiceWithModule">
<description>
This is a sample Web service with a logging module engaged.
</description>
<module ref="logging"/>
<parameter name="ServiceClass" locked="xsd:false">userguide.example2.MyService</parameter>
<operation name="echo">
<messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
</operation>
<operation name="ping">
<messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
</operation>
</service>

view raw

services.xml

hosted with ❤ by GitHub

After the module is built, you may place the .jar or .mar (rename the jar file) and keep in your library dir WSO2, (repository/components/lib).