CUBA Component Testing


An interesting advantage of CUBA is the ability of developing and testing components in an ordinary, non-distributed development environment even if they are later supposed to run in a client-server system based on Enterprise JavaBeans or AXIS web services. The key improvement is of course CUBA's wired mode which allows fast edit-compile-test cycles without deployment overhead and simple local debugging as supported in every mentionable IDE tool. But beside that, CUBA provides some additional features to simplify component testing

Testing Internal Interfaces

A well-known problem of EJB development is the automatic test of components with local interfaces (corresponds to internal interfaces in CUBA). According to the EJB standard, local interfaces can not be accessed outside an application server, so usually a test unit either requires an additional delegation component or must be implemented as an EJB itself. Both approaches have important disadvantages: delegates with remote interfaces loose the call-by-reference semantic of the local component, and implementing test units as EJBs makes it difficult to use common test frameworks like JUnit.
CUBA's wired mode provides a facility to expose a component's local interface to a client outside the container. Because direct lookup from a client context is not allowed, the local interface of interest must be looked up from a helper component and can simply be returned by one of its methods. Although this is of course strictly forbidden in J2EE environments, it perfectly works in CUBA's wired container for test purposes. The following code illustrates the implementation of an exposer helper component doing that job:

public class ExposerImpl extends AbstractComponent implements Exposer {
    
    public InternalIF exposeInternalComp() throws RemoteException {
        try {
            InternalIF if = (InternalIF)getContext().getComponent("internalRef");
            // never do this in a managed environment ;-)
            return if;
        }
        catch(Exception x) {
            throw new ComponentException(x.getMessage());
        }
    }
}

The directory examples/test includes a more detailed example.

Mock Injection At Runtime

When testing one particular component, it is often helpful to use mock implementations for dependent components in order to reliably reproduce suitable test data. This is especially of interest if the dependent components access remote systems like databases which potentially have a poor performance and are difficult to configure for particular test situations. In general, a component's "real" implementation could be substituted by a mock implementation specifying a different implementation class in the component's descriptor component-jar.xml. Unfortunately this is a relatively static configuration mechanism which is difficult to use in automatic tests.
As an alternative, CUBA's wired container provides a facility for replacing implementation classes programmatically at application start-up. The following code example demonstrates how to construct a WiredClientContext from an explicitely instanciated WiredContainer which is instrumented with a mock implementation for a particular component:

// Read an application descriptor the default way
WiredAppReader appReader = new WiredAppReader(null);
Object[] maps = appReader.process();

// Find the description of component with global name "hello"
ComponentMap cmap = (ComponentMap)maps[0];
WiredComponentDesc cdesc = cmap.lookup("hello");

// Replace the component descriptor by an equivalent copy,
// including the alternative implementation class
cmap.registerDescriptor(new WiredComponentDesc(cdesc, MyMock.class));

// Instanciate a WiredContainer using the modified component map
WiredContainer container = new WiredContainer(
   new PrimitiveWiredInstanceFactory(), new PrimitiveDataSourceManager(),
   cmap, (ResourceFactoryMap)maps[1], (Map)maps[2]);

// Instanciate a WiredClientContext from the container
WiredClientContext context = new WiredClientContext(container);

The directory examples/test includes the convenience class TestClientContext which encapculates this instanciation and substitution process.
When making use of dependency injection, you must consider a weekness of the EJB 3 standard which needs special treatment. As a difference to former versions of the specification, dependency injection defines an individual contract between the container and the component which is by default not expressed by a well-defined Java interface but by the component's implementation class (by its annotated setter methods and injection members). In these cases, mock implementation can only be added safely at runtime if they are derived from the component's real implementation. Because this is a quite unpleasant limitation for the design of mock classes, CUBA allows to define interface-based injection rather than classed-based. I.e. instead of annotating method implementations as injection setters, the method declarations of an interface are annotated. The component implementation class now must implement two interfaces - the one defining the business methods and the other one defining the component-container-contract. This implementation can be replaced by any other mock implementation at runtime which implements the same interfaces. The following example illustrates this feature based on code annotations:

// Define injection interface
public interface MyInjectionI {
    @Resource public setMyDB(DataSource db);
}

// Define component implementation class implementing the
// business methods and the injection methods
public class MyCompImpl implements MyCompI, MyInjectionI {
    private DataSource db;

    // Injection method implementation, must not be annotated...
    public setMyDB(DataSource db) { this.db = db; }

    // Business method implementations...
    // ...
}


Users of XML descriptors can use the optional injection-interface attribute in the component-jar.xml to express that an injection refers to that interface rather then the component's implementation class:

<resource-ref ref-name="myDB" interface="javax.sql.DataSource"
  injection-target="myDB" injection-interface="MyInjectionI"/>

Method Invocation Tracing

No matter how good a software ist tested, there are always some problems which can not easily be reproduced and analysed in a local development environment but only occur in the production system or in distributed usage. These cases often require the "poor man's debugger", i.e. instrumentation of components with diagnostic logging/tracing calls and redeployment. Detailed instrumentation has the disadvantage of poluting the code and reducing its readability. Aspect-oriented instrumentation solves the problem of code polution in the most elegant way but requires additional tools which are often difficult to use and to integrate into the development process.
Since version 3 the EJB standard (and CUBA too) provides a kind of simple aspect-oriented instrumentation mechanism by means of method interceptors. Interceptors can be specified in a component's descriptor and intercept all invocations of the component's interface methods. This feature makes it very easy to instrument a component with a method-based tracing  facility. A method interceptor is specified in a component's descriptor like this:

<?xml version="1.0" encoding="UTF-8"?>
<component-jar>
    <component>
        <component-name>MyComp</component-name>
        <external-interface>mypackage.MyCompIF</external-interface>
        <component-class>mypackage.MyComp</component-class>
        <interceptor class="tracing.Trace">
            <around-invoke method="trace"/>
        </interceptor>
     </component>
</component-jar>

The specified class must be available on the classpath and must provide the specified method with a particular signature, e.g.

public class Trace {
    public Object trace(InvocationContextI ic) throws Exception {
        logEnter(ic);
        try {
            Object result = ic.proceed();
            logLeave(ic, result);
            return result;
        }
        catch(Exception x) {
            logException(ic, x);
        }
    }
    ...
}


In Java 5 environments, interceptors can also be specified by code annotations. However, for logging purposes it is probably more convenient to keep the code unchanged when switching on and off tracing for a component.

The method interceptor facility works for EJB, wired and AXIS mode. The directory examples/test includes an example for method tracing interceptors based on Java's standard logging API. The implementation uses the logger name "cuba" and by default reports with level INFO. The log level can be changed by system property cuba.log.level supporting the values CONFIG, FINE, FINER, FINEST, INFO, SEVERE, and WARNING. Detailes concerning filters and log handlers can be configured via the JVM's general logging properties.


Home Introduction Javadoc