-
Language:
English
-
Language:
English
Red Hat Training
A Red Hat training course is available for Red Hat Fuse
13.10. Useful Tips for Creating JUnit Tests
13.10.1. Using Harmcrest to Assert
Hamcrest is a framework for writing matcher objects. It allows you to define match rules declaratively. Use Hamcrest’s
assertThat
construct and the standard set of matchers, both of which you can statically import:
import static org.hamcrest. MatcherAssert .assertThat; import static org.hamcrest. Matchers .*;
Hamcrest comes with a library of useful matchers, such as:
- Core
- anything: Always matches, useful if you do not want to know what the object under test is
- describedAs: Decorator for adding custom failure description
- is: Decorator to improve readability
- Logical
- allOf: Matches if all matchers match, short circuits (like && in Java)
- anyOf: Matches if any matchers match, short circuits (like || in Java)
- not: Matches if the wrapped matcher does not match and vice versa
- Object
- equalTo: Test object equality using Object.equals
- hasToString: Test Object.toString
- instanceOf, isCompatibleType: Test type
- notNullValue, nullValue: Test for null
- sameInstance: Test object identity
- Beans
- hasProperty: Test JavaBeans properties
- Collections
- array: Test an array’s elements against an array of matchers
- hasEntry, hasKey, hasValue: Test a map contains an entry, key or value
- hasItem, hasItems: Test a collection contains elements
- hasItemInArray: Test an array contains an element
- Number
- closeTo: Test floating point values are close to a given value
- greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo: Test ordering
- Text
- equalToIgnoringCase: Test string equality ignoring case
- equalToIgnoringWhiteSpace: Test string equality ignoring differences in runs of whitespace
- containsString, endsWith, startsWith: Test string matching
13.10.2. Invoking a Component Service
In order to invoke a component service, you must inject an invoker for certain ServiceOperation. When injecting a service operation, specify it in
[service_name].[operation_name]
notation.
import org.switchyard.test.Invoker; ... @RunWith(SwitchYardRunner.class) @SwitchYardTestCaseConfig(mixins = CDIMixIn.class) public class ExampleServiceTest { @ServiceOperation("ExampleService.submitOperation") private Invoker submitOperation; @Test public void testOK() throws Exception { ParamIn testParam = new ParamIn() .set...(...); ParamOut result = submitOperation .sendInOut(testParam) .getContent(ParamOut.class); Assert.... } @Test public void testForFault() throws Exception { ParamIn testParam = new ParamIn() .set...(...); try{ // This method invocation should throw a fault ParamOut result = submitOperation .sendInOut(testParam) .getContent(ParamOut.class); Assert.fail } catch (InvocationFaultException ifex){ Assert.... // Assert for correct type of exception } }
An invocation to a service operation can throw a
InvocationFaultException
whenever the method throws a fault. So catching this exception is similar to validating for the fault being thrown.
You can:
- Check against original exception by checking the type of the InvocationFaultException:
ifex.isType(MyOriginalException.class)
- Use the JUnit functionality of setting the expected exception in the test:
@Test(expected=org.switchyard.test.InvocationFaultException.class)
13.10.3. SwitchYardTestKit Utility Methods
TestKit provides the following set of utility methods to ease validations and some common operations that are performed on test classes:
- Access to underlying
- getTestInstance
- getActivators
- getDeployment
- getServiceDomain
- createQName
- Service manipulation
- registerInOutService
- registerInOnlyService
- removeService
- replaceService
- Invocation
- newInvoker
- Transformations
- addTransformer
- newTransformer
- registerTransformer
- MixIns
- getMixIns
- getMixIn
- Dependencies
- getRequiredDependencies
- getOptionalDependencies
- Resources
- getResourceAsStream
- readResourceBytes
- readResourceString: Reads a resource (file) form the classpath
- readResourceDocument
- Configuration
- loadSwitchYardModel
- loadConfigModel
- XML Comparison
- compareXMLToResource: Compares a XML in string format with a XML file in the classpath.
- compareXMLToString
- Tracing
- traceMessages: enables message tracing for the application under test.
13.10.4. Testing Transformations in Component Service
While testing a component invocation, you can test for the appropriate transformation with additional methods on the invocation. You can do this for the input transformation, as well as for the output transformation as shown below:
... @ServiceOperation("ExampleService.submitOperation") private Invoker serviceOperationInvocation; @Test public void testForInputTransformation() throws Exception { ParamOut result = serviceOperationInvocation .inputType(QName.valueOf("{urn:com.examaple:service:1.0"}submitOperation)) .sendInOut(....) .getContent(ParamOut.class); Assert.... // Assert that result is OK, so transformation was OK } @Test public void testForOutputXMLTransformation() throws Exception { ParamIn testParam = new ParamIn() .set...(...); ParamOut result = serviceOperationInvocation .expectedOutputType(QName.valueOf("{urn:com.examaple:service:1.0"}submitOperationResponse)) .sendInOut(testParam) .getContent(Element.class); // Expect Element as transformation is for XML XMLAssert.... // Assert that result is what is expected }
You can use XMLUnit and XMLAssert from
org.custommonkey.xmlunit
to ease validations.
13.10.5. Mocking a Service, Component, or Reference
Mocking a component may be useful, so it is never invoked for the sake of a test. For this, SwitchYardTestKit provides with the ability of adding, replacing, or removing services.
// replace existing implementation for testing purposes testKit.removeService("MyService");s final MockHandler myService = testKit.registerInOnlyService("MyService"); .... // Invoke the service under test // Assert what has arrived ath the mocked service final LinkedBlockingQueue<Exchange> recievedMessages = myService.getMessages(); assertThat(recievedMessages, is(notNullValue())); final Exchange recievedExchange = recievedMessages.iterator().next(); assertThat(recievedExchange.getMessage().getContent(String.class), is(equalTo(...)));
- If you want to assert what has arrived or produced in the MockHandler, you can use the following options:
- getMessages(): This provides with the list of received messages.
- getFaults(): This provides with the list of prodced faults.
- If the service is InOut, you may need to mock a response. You can use the following options:
- forwardInToOut()
- forwardInToFault()
- replyWithOut(Object)
- replyWithFault(Object)For example:
final MockHandler mockHandler = testKit.registerInOutService("MyService"); mockHandler.forwardInToOut();
- If you want to instruct the MockHandler to wait for certain message, you can use the following options:
- waitForOkMessage()
- waitForFaultMessage()The MockHandler waits for 5 seconds by default, unless instructed to wait for a different period with setWaitTimeout(milis).
13.10.6. Mocking a Service For More Than One Method Invocation
In some cases, the service you are mocking may be called
- Twice in the context of a single unit test, or
- Multiple times for the same method, or
- Multiple times for different methods
In this case, you can register an
ExchangeHandler
with the mock, while registering and replacing the original service. The ExchangeHandler
gets the message, and contains the logic that you need to put to deal with this scenario, as shown below:
testKit.replaceService(qname, new ExchangeHandler() { @Override public void handleMessage(Exchange arg0) throws HandlerException { // Here logic to handle with messages } @Override public void handleFault(Exchange arg0) throws HandlerException { // Here logic to handle with faults } });
You can reuse this
ExchangeHandler
by making it a named class (not anonymous).
13.10.6.1. Multiple Invocations of a Single Method
In the case of multiple invocation of the same method, the ExchangeHandler keeps track of the invocation number, in case it has to answer with different messages:
testKit.replaceService(qname, new ExchangeHandler() { int call=1; @Override public void handleMessage(Exchange exchange) throws HandlerException { if (call++ == 1){ // First call // Do whatever wants to be done as result of this operation call, and return the expected output Result result = ...; / Result is return type for operation store exchange.send(exchange.createMessage().setContent(result)); }else if (call++ == 2){ // Second call // Do whatever wants to be done as result of this operation call, and return the expected output Result result = ...; / Result is return type for operation store exchange.send(exchange.createMessage().setContent(result)); }else{ throw new HandlerException("This mock should not be called more than 2 times"); } } @Override public void handleFault(Exchange exchange) throws HandlerException { // Here logic to handle with faults } });
13.10.6.2. Multiple Invocations of Different Methods
In the case of multiple invocation of different methods, the ExchangeHandler checks for operation name, to know which method is being invoked:
testKit.replaceService(qname, new ExchangeHandler() { @Override public void handleMessage(Exchange exchange) throws HandlerException { if (exchange.getContract().getProviderOperation().getName().equals("store")){ // Do whatever wants to be done as result of this operation call, and return the expected output Result result = ...; / Result is return type for operation store exchange.send(exchange.createMessage().setContent(result)); }else if (exchange.getContract().getProviderOperation().getName().equals("getId")){ // Do whatever wants to be done as result of this operation call, and return the expected output exchange.send(exchange.createMessage().setContent(1)); // This operation returns a Int }else{ throw new HandlerException("No operation with that name should be executed"); } } @Override public void handleFault(Exchange exchange) throws HandlerException { // Here logic to handle with faults } });
13.10.7. Setting Properties For a Test
You can use the PropertyMixIn property to set test properties in configurations, as shown below:
private PropertyMixIn pmi; ... pmi.set("test.property.name", "test"); pmi.set("test.property.name", Integer.valueOf(100)); ... pmi.get("test.property.name"); ...
13.10.8. Testing a Deployed Service with HTTPMixin
Use HTTPMixin to test a deployed service, as shown below:
@RunWith(SwitchYardRunner.class) @SwitchYardTestCaseConfig( scanners = TransformSwitchYardScanner.class, mixins = {CDIMixIn.class, HTTPMixIn.class}) public class WebServiceTest { private HTTPMixIn httpMixIn; @Test public void invokeWebService() throws Exception { // Use the HttpMixIn to invoke the SOAP binding endpoint with a SOAP input (from the test classpath) // and compare the SOAP response to a SOAP response resource (from the test classpath)... httpMixIn.setContentType("application/soap+xml"); httpMixIn.postResourceAndTestXML("http://localhost:18001/service-context/ServiceName", "/xml/soap-request.xml", "/xml/soap-response.xml"); } }
You can also use HTTPMixin from a main class, as shown below:
/** * Only execution point for this application. * @param ignored not used. * @throws Exception if something goes wrong. */ public static void main(final String[] ignored) throws Exception { HTTPMixIn soapMixIn = new HTTPMixIn(); soapMixIn.initialize(); try { String result = soapMixIn.postFile(URL, XML); System.out.println("SOAP Reply:\n" + result); } finally { soapMixIn.uninitialize(); } }
13.10.9. Creating an Embedded WebService to Test a Component
In situations where you wish to only test a single component, you can expose it dynamically as a WebService and invoke it, as shown below:
import javax.xml.ws.Endpoint; ... @RunWith(SwitchYardRunner.class) @SwitchYardTestCaseConfig( config = SwitchYardTestCaseConfig.SWITCHYARD_XML, scanners = {TransformSwitchYardScanner.class}, mixins = {HTTPMixIn.class}) public class CamelSOAPProxyTest { private static final String WEB_SERVICE = "http://localhost:8081/MyService"; private HTTPMixIn _http; private Endpoint _endpoint; @BeforeDeploy public void setProperties() { System.setProperty("org.switchyard.component.http.standalone.port", "8081"); } @Before public void startWebService() throws Exception { _endpoint = Endpoint.publish(WEB_SERVICE, new ReverseService()); } @After public void stopWebService() throws Exception { _endpoint.stop(); } @Test public void testWebService() throws Exception { _http.postResourceAndTestXML(WEB_SERVICE, "/xml/soap-request.xml", "/xml/soap-response.xml"); } }
13.10.10. Testing a Deployed Service with HornetQMixIn
When you need to test an application that has a JMS binding, you may want to test with the binding itself. In such cases, you can use HornetQMixIn. HornetQMixIn gets its configuration from the following two files, which must be present on the classpath for the test:
hornetq-configuration.xml
: This file contains the configuration for the HornetQ server.<configuration xmlns="urn:hornetq"> <paging-directory>target/data/paging</paging-directory> <bindings-directory>target/data/bindings</bindings-directory> <persistence-enabled>false</persistence-enabled> <journal-directory>target/data/journal</journal-directory> <journal-min-files>10</journal-min-files> <large-messages-directory>target/data/large-messages</large-messages-directory> <security-enabled>false</security-enabled> <connectors> <connector name="invm-connector"> <factory-class>org.hornetq.core.remoting.impl.invm.InVMConnectorFactory</factory-class> </connector> <connector name="netty-connector"> <factory-class>org.hornetq.core.remoting.impl.netty.NettyConnectorFactory</factory-class> <param key="port" value="5545"/> </connector> </connectors> <acceptors> <acceptor name="invm-acceptor"> <factory-class>org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory</factory-class> </acceptor> <acceptor name="netty-acceptor"> <factory-class>org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory</factory-class> <param key="port" value="5545"/> </acceptor> </acceptors> </configuration>
ImportantThecamel-netty
component is deprecated since JBoss Fuse 6.3 and will be replaced by thecamel-netty4
component in a future release of JBoss Fuse.hornetq-configuration.xml
: This file contains the definition of the connection factories, queues, and topics.<configuration xmlns="urn:hornetq"> <connection-factory name="ConnectionFactory"> <connectors> <connector-ref connector-name="invm-connector"/> </connectors> <entries> <entry name="ConnectionFactory"/> </entries> </connection-factory> <queue name="TestRequestQueue"> <entry name="TestRequestQueue"/> </queue> <queue name="TestReplyQueue"> <entry name="TestReplyQueue"/> </queue> </configuration>
To use HornetQMixIn in a test, you need to get a reference to the MixIn and use the appropriate mixin methods, as shown below:
@RunWith(SwitchYardRunner.class) @SwitchYardTestCaseConfig( config = SwitchYardTestCaseConfig.SWITCHYARD_XML, mixins = {CDIMixIn.class, HornetQMixIn.class} ) public class JmsBindingTest { private HornetQMixIn _hqMixIn; @Test public void testHelloService() throws Exception { Session session = _hqMixIn.getJMSSession(); MessageProducer producer = session.createProducer(HornetQMixIn.getJMSQueue(REQUEST_NAME)); Message message = _hqMixIn.createJMSMessage(createPayload(NAME)); producer.send(message); MessageConsumer consumer = session.createConsumer(HornetQMixIn.getJMSQueue(REPLY_NAME)); message = consumer.receive(3000); String reply = _hqMixIn.readStringFromJMSMessage(message); SwitchYardTestKit.compareXMLToString(reply, createExpectedReply(NAME)); } @Before public void getHornetQMixIn() { _hqMixIn = _testKit.getMixIn(HornetQMixIn.class); }
You can also test from a standalone client, as shown below:
public static void main(final String[] args) throws Exception { HornetQMixIn hqMixIn = new HornetQMixIn(false) .setUser(USER) .setPassword(PASSWD); hqMixIn.initialize(); try { Session session = hqMixIn.getJMSSession(); final MessageProducer producer = session.createProducer(HornetQMixIn.getJMSQueue(REQUEST_NAME)); producer.send(hqMixIn.createJMSMessage("<....>"); System.out.println("Message sent. Waiting for reply ..."); final MessageConsumer consumer = session.createConsumer(HornetQMixIn.getJMSQueue(REPLY_NAME)); Message message = consumer.receive(3000); String reply = hqMixIn.readStringFromJMSMessage(message); System.out.println("REPLY: \n" + reply); } finally { hqMixIn.uninitialize(); } }
13.10.11. Testing a Deployed Service with TransactionMixIn
You can use TransactionMixIn to test your required services with a transaction. TransactionMixIn with combination of CDIMixIn injects a UserTransaction object when required. If you need explicit access, you can use @Inject in the UserTransaction object. otherwise, it is injected in SwitchYard’s functionalities. This MixIn introduces NamingMixIn, as it is a required dependency.
@SwitchYardTestCaseConfig( config = SwitchYardTestCaseConfig.SWITCHYARD_XML, mixins = {CDIMixIn.class, TransactionMixIn.class} ) public YourClass{ .... }
This binds the following objects into the JNDI tree:
- TransactionManager: java:jboss/TransactionManager
- UserTransaction: java:jboss/UserTransaction
- TransactionSynchronizationRegistry: java:jboss/TransactionSynchronizationRegistry
If you need access to the provided objects, you can use the MixIn to get a reference, as shown below:
private TransactionMixIn transaction; .... transaction.getUserTransaction(); transaction.getTransactionManager(); transaction.getSynchronizationRegistry();
This mixin creates transactional logs in
target/tx-store
and uses Arjuna Transactions Provider (com.arjuna.ats.jta).
13.10.12. Testing With a Different SwitchYard Configuration File
You can use the following annotation on the test class and create your reduced <
switchyard-XXXX.xml
> within the test/resources
folder at the same package level as your test class:
@SwitchYardTestCaseConfig(config = "switchyard-XXXXX.xml", mixins = {.....})
13.10.13. Selectively Enabling Activators for a Test
The test framework defaults to a mode where the entire application descriptor is processed during a test run. This means all gateway bindings and service implementations are activated during each test. There are times when this may not be appropriate. So you must allow activators to be selectively enabled or disabled based on your test configuration. In the example below, SOAP bindings are excluded from all tests. This means that SOAP gateway bindings are not activated when the test framework loads the application.
@RunWith(SwitchYardRunner.class) @SwitchYardTestCaseConfig(config = "testconfigs/switchyard-01.xml" exclude="soap") public class NoSOAPTest { ... }
The example below includes only CDI bean services as defined in the application descriptor:
@RunWith(SwitchYardRunner.class) @SwitchYardTestCaseConfig(config = "testconfigs/switchyard-02.xml" include="bean") public class BeanServicesOnlyTest { ... }
You may need to add some procedures before you perform the test. The JUnit @Before operation is invoked immediately after the application is deployed. However, you can not use it if you expect something to happen before deployment.
13.10.14. Preparing Procedure for Test
JUnit @Before operation is invoked right after the application is deployed. So, you can not use @Before operation if you expect something before deployment. Use @BeforeDeploy annotation when you need to add some procedures before a test is performed.
13.10.15. Testing a Camel Binding
If you are exposing services with a camel binding, you can test it by getting the
CamelContext
and then creating a ProducerTemplate
as shown below:
@RunWith(SwitchYardRunner.class) @SwitchYardTestCaseConfig( config = SwitchYardTestCaseConfig.SWITCHYARD_XML, mixins = { CDIMixIn.class }) public class ExampleTest { private SwitchYardTestKit testKit; @Test public void testIntake() throws Exception { ServiceDomain domain = testKit.getServiceDomain(); CamelContext ctx = (CamelContext)domain.getProperty("CamelContextProperty"); ProducerTemplate producer = ctx.createProducerTemplate(); producer.sendBody("direct://HelloService", "Message content"); } }
You can test a service like the one defined below that has a camel binding:
<sca:service name="Hello/HelloService" promote="Hello/HelloService"> <sca:interface.java interface="org.jboss.example.ExampleService"/> <camel_1:binding.uri name="camel1" configURI="direct://HelloService"/> </sca:service>