Automated tests are a very useful way of improving code quality. They provide a context for development, documentation, and an on-going check of code correctness. These benefits give you the confidence to build on and refactor code knowing that it will continue to work as designed. This chapter covers the application testing facilities provided by Fabric3. Some familiarity with Maven, JUnit and the concepts of mock objects are assumed.
Unit Testing
Java-based components in SCA are in most cases simple POJOs with optional annotations. This means that this guide has little to add to the already well-covered topics of test-driven development and unit testing. It is important to note, however, that even with Fabric3's strong support for integration testing (examined below), it remains easier to find and fix a whole class of bugs by running lightweight tests as opposed to a (slower) test-harness.
Integration Testing
Fabric 3 integration tests ensure that component implementations and composites provide the expected functionality and interact with other services and runtime resources in the expected way. They are referred to as 'itests'.
While Fabric 3 does provide a lightweight standalone runtime environment, the recommended way to drive automated integration tests is through the specialized Maven plug-in. This handles the creation of an embedded Fabric3 runtime, deployment, and test execution. Fast runtime bootstrap and test execution makes iterative development easier. Since the Maven plug-in provides the same execution environment as the standalone server, component behavior verification can be done with little overhead. Use of the Maven plug-in also allows integration tests to be initiated in exactly the same way on development machines and the build server.
The Maven plug-in is provided by the 'fabric3-itest' mojo which has a single goal 'test'. This goal is bound to the 'integration-test' phase of the Maven life cycle which means that integration tests are run after application packaging and ordinary unit tests. A basic configuration looks like this:
<build> <defaultGoal>verify</defaultGoal> <plugins> <plugin> <groupId>org.codehaus.fabric3</groupId> <artifactId>fabric3-itest-plugin</artifactId> <configuration> <runtimeVersion>RELEASE</runtimeVersion> </configuration> <executions> <execution> <goals> <goal>test</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
Integration Test Example
The example illustrates the use of Fabric3 ITest plugin for testing a simple SCA service. The service will be injected into a JUnit test case using the SCA Java programming model, and the test case will invoke the injected service in the ITest host environment. The example uses:
- Service interface
- Service implementation
- Test class
- Service composite
- Test composite
- Maven project descriptor
The figure below shows the structure of the project:
/pom.xml /src/main/java/HelloWorld.java /src/main/java/HelloWorldImpl.java /src/main/resources/helloWorld.composite /src/test/java/HelloWorldITest.java /src/test/resources/itest.composite
Creating the Service Interface, Implementation and Composite
The service interface and component implementation are as follows:
{public interface HelloWorld { String sayHello(String name); } public class HelloWorldImpl implements HelloWorld { public String sayHello(String name) { return "Hello " + name; } }
The component is configured in the following composite:
<composite name="HelloComposite" targetNamespace="urn:fabric3.org:sample" ...> <component name="HelloWorldComponent"> <implementation.java class="...HelloWorldImpl"/> </component> </composite>
Setting up the Integration Test
The test for the service is written as a standard JUnit test which is configured as an SCA component and placed under /src/test/java. This means the service being tested can be injected into the test case using SCA:
public class HelloWorldITest extends TestCase { @Reference protected HelloWorld helloWorld; public void testSayHello() { assertEquals({"Hello, Foo", helloWorld.sayHello("Foo")); } } The test case is then configured in a special test composite that includes the application composite. The test composite should be names "TestComposite" with a target namespace of "urn:fabric3.org" and placed under /src/test/resource: {code:xml} <composite name="TestComposite" targetNamespace="urn:fabric3.org" ...> <component name="HelloWorldTest"> <f3:junit class="...HelloWorldITest"{color}/> <reference name="helloWorld" target="HelloWorldComponent"/> </component> <include name="HelloWorldComposite/> </composite> h3.The Maven POM The Maven POM must specify the SCA API and JUnit as dependencies. It must also configure the Fabric3 ITest plugin: {code:xml} <project ...> <dependencies> <dependency> <groupId>org.codehaus.fabric3.spec</groupId> <artifactId>sca-api-r1.0</artifactId> <version>0.2.1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> </dependency> </dependencies> <build> <defaultGoal>verify</defaultGoal> <plugins> <plugin> <groupId>org.codehaus.fabric3</groupId> <artifactId>fabric3-itest-plugin</artifactId> <executions> <execution> <goals> <goal>test</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
Running the Tests
The tests are run executing mvn integration-test. This will boot the Fabric3 runtime, deploy the test composite, and call all test operations on configured JUnit components. Test results will then be collocated and reported. The following output will be generated when the plugin is run:
[INFO] [fabric3-itest:test {execution: default}] [INFO] Starting Fabric3 Runtime ... [INFO] JMX management extension installed [INFO] JMX connector started on port 1199 [INFO] iTestContribution installed [INFO] Composite {urn:fabric3.org}FunctionTestHarnessComposite deployed [INFO] Executing tests... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running HelloWorldTest Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.032 sec Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] Stopping Fabric3 {color:#831b92}Runtime{color} ... [INFO] ---------------------------------------------------------- [INFO] BUILD SUCCESSFUL [INFO] ---------------------------------------------------------- [INFO] Total time: 12 seconds
End-to-End Integration Tests
It is also possible to conduct end-to-end integration tests by binding a JUnit component reference to a transport. The previous example could be modified to invoke the HelloWorldService over JMS (the JMS profile will need to be installed as detailed in the next section):
<composite name="TestComposite" targetNamespace="urn:fabric3.org" ...> <component name="HelloWorldTest"> <f3:junit class="...HelloWorldITest"{color}/> <reference name="helloWorld" target="HelloWorldComponent"> <binding.jms> <destination jndiName="Queue"/> </binding.jms> </reference> </component> <include name="HelloWorldComposite/> </composite> h2.Using Profiles and Extensions The ITest plugin supports the full range of Fabric3 extensions and profiles such as JTA, JMS, Web Services and REST. Profiles and extensions are enabled using the <profiles> and <extensions> elements respectively: {code:xml} <plugin> <plugin> <groupId>org.codehaus.fabric3</groupId> <artifactId>fabric3-itest-plugin</artifactId> <version>${fabric3.version}</version> <configuration> <runtimeVersion>${fabric3.version}</runtimeVersion> <profiles> <profile> <groupId>org.codehaus.fabric3</groupId> <artifactId>profile-jms</artifactId> <version>${fabric3.version}</version> </profile> </profiles> <extensions> <extension> <groupId>org.codehaus.fabric3</groupId> <artifactId>some-extension</artifactId> <version>${fabric3.version}</version> </extension> </extensions> </configuration> <executions> <execution> <goals> <goal>test</goal> </goals> </execution> </executions> </plugin> </plugins>
Configuring the Maven Runtime
The Maven runtime systemConfig is specified inline as part of the plugin <systemConfig> element in a CDATA section. It is the same format as the other Fabric3 runtime systemConfig.xml files:
<plugin> <plugin> <groupId>org.codehaus.fabric3</groupId> <artifactId>fabric3-itest-plugin</artifactId> <version>${fabric3.version}</version> <configuration> <runtimeVersion>${fabric3.version}</runtimeVersion> <systemConfig> <![CDATA[ <config> <thread.pool size="100"/> </config> ]]> </systemConfig> </configuration> ..... </plugin> </plugins>
Working with Easymock
When you write service based applications, services seldom function in isolation. Services may depend on other services for implementing a cohesive piece of functionality. And, of course, dependency from the implementation of one service to another would be through well-defined service contracts. In the previous chapter we saw these dependencies in the SCA world are expressed using references.
When you unit test a service implementation, you may or may not have the implementation of other services it depend on. For example, the implementation of the dependency may come from the same composite, in which case, when you unit test the composite all the dependencies would have been catered for. In other scenarios, implementations of the dependencies may come from extrenal composites, in which case, the references would have been promoted. In such scenarios, you may want to mock those references and verify the behaviour of the composites in terms of the references being called expected number of times.
Fabric3 provides service mocking using the mock implementation using Easymock, which we will cover in this section.
Adding Mock Functionality to Hello World
In this section we will have a look at how service mocking can be used in the hello world example. Let us say each time the sayHello method is called, the component implementation will log the call using a monitor service, whose service contract is shown below:
public interface HelloWorldMonitor {
void onSayHello(String name);
}
The code for the service implementation class has now changed to incorporate the call to the above service:
import org.osoa.sca.annotations.Reference;
public class HelloWorldImpl implements HelloWorld {
@Reference protected HelloWorldMonitor monitor;
public String sayHello(String name) {
monitor.onSayHello(name);
return "Hello, " + name;
}
}
Let us say the monitor service comes from a different composite, so our service composite promotes the reference as shown below, so that it can be provided in the context in which the composite will be used as a component.
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0" name="HelloWorldComposite" targetNamespace="urn:helloWorld">
<service name="helloWorldService" promote="HelloWorldComponent"/>
<reference name="monitor" promote="HelloWorldComponent/monitor"/>
<component name="HelloWorldComponent">
<implementation.java class="HelloWorldImpl"/>
</component>
</composite>
Now, when we test the above composite the monitor reference will have to be provided. However, in our integration test, rather than using a real implementation of the monitor service, we use a mock implementation using Fabric3 mock support.
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0"
xmlns:f3="urn:fabric3.org"
name="HelloWorldTestComposite"
autowire="true">
<component name="HelloWorldTest">
<f3:junit class="HelloWorldITest"/>
<reference name="helloWorld" target="HelloWorldComposite"/>
<reference name="monitor" target="MockComponent/HelloWorldMonitor"/>
</component>
<component name="HelloWorldComposite">
<implementation.composite name="helloWorldComposite" scdlResource="helloWorld.composite" />
<reference name="monitor" target="MockComponent/HelloWorldMonitor"/>
</component>
<component name="MockComponent">
<f3:implementation.mock>
HelloWorldMonitor
</f3:implementation.mock>
</component>
</composite>
The implementation type, implementation.mock like junit belongs to the Fabric3 namespace and is an implementation provided by Fabric3 to support mocking service references in integration tests. The implementation takes a list of token separated fully-qualified names of interfaces that need to be mocked.
Note: When autowire is switched on you dont need to explictly specify all the references and target them. This would make the composite less verbose.
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0"
xmlns:f3="urn:fabric3.org"
name="HelloWorldTestComposite"
autowire="true">
<component name="HelloWorldTest">
<f3:junit class="HelloWorldITest"/>
</component>
<component name="HelloWorldComposite">
<implementation.composite name="helloWorldComposite" scdlResource="helloWorld.composite" />
</component>
<component name="MockComponent">
<f3:implementation.mock>
HelloWorldMonitor
</f3:implementation.mock>
</component>
</composite>
Now in the test code, we can use the Easymock API to verify the right number of calls are made to the monitor service by the HelloWorldImpl component being tested.
import org.osoa.sca.annotations.Reference;
import junit.framework.TestCase;
import org.easymock.EasyMock;
import org.easymock.IMocksControl;
public class HelloWorldITest extends TestCase {
@Reference protected HelloWorld helloWorld;
@Reference protected IMocksControl control;
@Reference protected HelloWorldMonitor monitor;
public void testSayHello() {
control.reset();
monitor.onSayHello("Fred");
control.replay();
assertEquals("Hello, Fred", helloWorld.sayHello("Fred"));
control.verify();
}
}
Before you can run the test, you need to modify the POM to add dependency on Easymock and also enable the Fabric3 Easymock extension,
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>helloworld</groupId>
<artifactId>helloworld</artifactId>
<packaging>jar</packaging>
<version>0.1</version>
<name>Hello World Test</name>
<dependencies>
<dependency>
<groupId>org.codehaus.fabric3.spec</groupId>
<artifactId>sca-api-r1.0</artifactId>
<version>0.2.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
<build>
<defaultGoal>verify</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
<compilerArgument>-g</compilerArgument>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<excludes>
<exclude>**/*ITest.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.fabric3</groupId>
<artifactId>fabric3-itest-plugin</artifactId>
<configuration>
<extensions>
<dependency>
<groupId>org.codehaus.fabric3</groupId>
<artifactId>fabric3-mock</artifactId>
<version>RELEASE</version>
</dependency>
</extensions>
<shared>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>2.2</version>
</dependency>
</shared>
</configuration>
<executions>
<execution>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Configuring Security Performing Security Tests
JUnit components may be configured with a context for invocations. For example, possible to configure authentication information, which will allow a subject for each test invocation to be set automatically based on that information:
<component name="SecureServiceTest">
<f3:junit class="org.fabric3.policy.security.SecureServiceTest">
<configuration>
<username>foo</username>
<password>bar</password>
</configuration>
</f3:junit>
<reference name="secureRoleService" target="SecureRolesService"/>
</component>
<component name="SecureRolesService">
<implementation.java class="org.fabric3.policy.security.SecureRolesServiceImpl"/>
</component>
The Maven POM is configured with the following:
<systemConfig>
<![CDATA[
<config>
<users>
<user>
<username>foo</username>
<password>bar</password>
<roles>
<role>role1</role>
<role>role2</role>
</roles>
</user>
</users>
</config>
]]>
</systemConfig>
This provides an easy way to test components that require authentication and/or authorization.
Add Comment