Unit Test Support

Apache Isis provides a number of helpers to help unit test your domain objects.

Contract Tests

Contract tests ensure that all instances of a particular idiom/pattern that occur within your codebase are implemented correctly. You could think of them as being a way to enforce a certain type of coding standard.

SortedSets

This contract test automatically checks that all fields of type java.util.Collection are declared as java.util.SortedSet. In other words, it precludes either java.util.List or java.util.Set from being used as a collection.

For example, the following passes the contract test:

public class Department {
    private SortedSet<Employee> employees = new TreeSet<Employee>();
    ...
}

whereas this would not:

public class SomeDomainObject {
    private List<Employee> employees = new ArrayList<Employee>();
    ...
}

If using an RDBMS for persistence then we strongly recommend that you implement this test, for several reasons:

  • first, Sets align more closely to the relational model than do Lists. A List must have an additional index to specify order.

  • second, SortedSet is preferable to Set because then the order is well-defined and predictable (to an end user, to the programmer).

    The ObjectContracts utility class substantially simplifies the task of implementing Comparable in your domain classes.

  • third, if the relationship is bidirectional then the ORM (JDO/DataNucleus) will automatically maintain the relationship.

To use the contract test, subclass SortedSetsContractTestAbstract, specifying the root package to search for domain classes.

For example:

public class SortedSetsContractTestAll extends SortedSetsContractTestAbstract {

    public SortedSetsContractTestAll() {
        super("domainapp.modules");
        withLoggingTo(System.out);
    }
}

Bidirectional

This contract test automatically checks that bidirectional 1:m or 1:1 associations are being maintained correctly (assuming that they follow the mutual registration pattern

(If using JDO/DataNucleus, then) there is generally no need to programmatically maintain 1:m relationships (indeed it may introduce subtle errors). For more details, see here.

For example, suppose that ParentDomainObject and ChildDomainObject have a 1:m relationship (ParentDomainObject#children / ChildDomainObject#parent), and also PeerDomainObject has a 1:1 relationship with itself (PeerDomainObject#next / PeerDomainObject#previous).

The following will exercise these relationships:

public class BidirectionalRelationshipContractTestAll
        extends BidirectionalRelationshipContractTestAbstract {

    public BidirectionalRelationshipContractTestAll() {
        super("org.apache.isis.core.unittestsupport.bidir",
                ImmutableMap.<Class<?>,Instantiator>of(
                    ChildDomainObject.class, new InstantiatorForChildDomainObject(),
                    PeerDomainObject.class, new InstantiatorSimple(PeerDomainObjectForTesting.class)
                ));
        withLoggingTo(System.out);
    }
}

The first argument to the constructor scopes the search for domain objects; usually this would be something like "com.mycompany.dom".

The second argument provides a map of Instantiator for certain of the domain object types. This has two main purposes. First, for abstract classes, it nominates an alternative concrete class to be instantiated. Second, for classes (such as ChildDomainObject) that are Comparable and are held in a SortedSet), it provides the ability to ensure that different instances are unique when compared against each other. If no Instantiator is provided, then the contract test simply attempts to instantiates the class.

If any of the supporting methods (addToXxx(), removeFromXxx(), modifyXxx() or clearXxx()) are missing, the relationship is skipped.

To see what’s going on (and to identify any skipped relationships), use the withLoggingTo() method call. If any assertion fails then the error should be descriptive enough to figure out the problem (without enabling logging).

Value Objects

The ValueTypeContractTestAbstract automatically tests that a custom value type implements equals() and hashCode() correctly.

For example, testing JDK’s own java.math.BigInteger can be done as follows:

public class ValueTypeContractTestAbstract_BigIntegerTest extends ValueTypeContractTestAbstract<BigInteger> {

    @Override
    protected List<BigInteger> getObjectsWithSameValue() {
        return Arrays.asList(new BigInteger("1"), new BigInteger("1"));
    }

    @Override
    protected List<BigInteger> getObjectsWithDifferentValue() {
        return Arrays.asList(new BigInteger("2"));
    }
}

JMock Extensions

the JMock extensions are deprecrated, we suggest you use Mockito instead.

If you use mocking, then usual given/when/then format gets an extra step:

  • given the system is in this state

  • expecting these interactions (set up the mock expectations here)

  • when I poke it with a stick

  • then these state changes and interactions with Mocks should have occurred.

If using JMock then the interactions (in the "then") are checked automatically by a JUnit rule. However, you probably will still have some state changes to assert upon.

Distinguish between queries vs mutators

For mock interactions that simply retrieve some data, your test should not need to verify that it occurred. If the system were to be refactored and starts caching some data, you don’t really want your tests to start breaking because they are no longer performing a query that once they did. If using JMock API this means using the allowing(..) method to set up the expectation.

On the other hand mocks that mutate the state of the system you probably should assert have occurred. If using JMock this typically means using the oneOf(…​) method.

For more tips on using JMock and mocking in general, check out the GOOS book, written by JMock’s authors, Steve Freeman and Nat Pryce and also Nat’s blog.

Apache Isis' unit test support provides JUnitRuleMockery2 which is an extension to the JMock's JunitRuleMockery. It provides a simpler API and also providing support for autowiring.

For example, here we see that the class under test, an instance of CollaboratingUsingSetterInjection, is automatically wired up with its Collaborator:

public class JUnitRuleMockery2Test_autoWiring_setterInjection_happyCase {

    @Rule
    public JUnitRuleMockery2 context = JUnitRuleMockery2.createFor(Mode.INTERFACES_AND_CLASSES);

    @Mock
    private Collaborator collaborator;

    @ClassUnderTest
    private CollaboratingUsingSetterInjection collaborating;

    @Test
    public void wiring() {
        assertThat(collaborating.collaborator, is(not(nullValue())));
    }
}

Isis also includes (and automatically uses) a Javassist-based implementation of JMock’s ClassImposteriser interface, so that you can mock out concrete classes as well as interfaces. We’ve provided this rather than JMock’s own cglib-based implementation (which is problematic for us given its own dependencies on asm).

The example tests can be found here

SOAP Fake Endpoints

No man is an island, so the saying goes, and neither are most applications. Chances are that at some point you may need to integrate your Apache Isis application to other external systems, possibly using old-style SOAP web services. The SOAP client in this case could be a domain service within your app, or it might be externalized, eg invoked through a scheduler or using Apache Camel.

While you will want to (of course) perform manual system testing/UAT with a test instance of that external system, it’s also useful to be able to perform unit testing of your SOAP client component.

The SoapEndpointPublishingRule is a simple JUnit rule that allows you to run a fake SOAP endpoint within an unit test.

SoapEndpointPublishingRule

The idea behind this rule is that you write a fake server endpoint that implements the same WSDL contract as the "real" external system does, but which also exposes additional API to specify responses (or throw exceptions) from SOAP calls. It also typically records the requests and allows these to be queried.

In its setup your unit test and gets the rule to instantiate and publish that fake server endpoint, and then obtains a reference to that server endpoint. It also instantiates the SOAP client, pointing it at the address (that is, a URL) that the fake server endpoint is running on. This way the unit test has control of both the SOAP client and server: the software under test and its collaborator.

In the test methods your unit test sets up expectations on your fake server, and then exercises the SOAP client. The SOAP client calls the fake server, which then responds accordingly. The test can then assert that all expected interactions have occurred.

So that tests don’t take too long to run, the rule puts the fake server endpoints onto a thread-local. Therefore the unit tests should clear up any state on the fake server endpoints.

Your unit test uses the rule by specifying the endpoint class (must have a no-arg constructor):

public class FakeExternalSystemEndpointRuleTest {
    @Rule
    public SoapEndpointPublishingRule serverRule =
        new SoapEndpointPublishingRule(FakeExternalSystemEndpoint.class);         (1)
    private FakeExternalSystemEndpoint fakeServerEndpoint;
    private DemoObject externalSystemContract;                                    (2)
    @Before
    public void setUp() throws Exception {
        fakeServerEndpoint =
            serverRule.getEndpointImplementor(FakeExternalSystemEndpoint.class);  (3)
        final String endpointAddress =
            serverRule.getEndpointAddress(FakeExternalSystemEndpoint.class);      (4)
        final DemoObjectService externalSystemService =                           (5)
                new DemoObjectService(ExternalSystemWsdl.getWsdl());              (6)
        externalSystemContract = externalSystemService.getDemoObjectOverSOAP();
        BindingProvider provider = (BindingProvider) externalSystemContract;
        provider.getRequestContext().put(
                BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
                endpointAddress)
        );
    }
    @Test
    public void happy_case() throws Exception {
        // given
        final Update update = new Update();                              (7)
        ...
        // expect
        final UpdateResponse response = new UpdateResponse();            (8)
        ...
        fakeServerEndpoint.control().setResponse(updateResponse);
        // when
        PostResponse response = externalSystemContract.post(update);     (9)
        // then
        final List<Update> updates =                                     (10)
            fakeServerEndpoint.control().getUpdates();
        ...
    }
}
1 specify the class that implements the endpoint (must have a no-arg constructor)
2 the SOAP contract as defined in WSDL and generated by wsdl2java
3 get hold of the fake server-side endpoint from the rule…​
4 ... and its endpoint address
5 use factory (also generated by wsdl2java) to create client-side endpoint
6 getWsdl() is a utility method to return a URL for the WSDL (eg from the classpath)
7 create a request object in order to invoke the SOAP web service
8 instruct the fake server endpoint how to respond
9 invoke the web service
10 check the fake server endpoint was correctly invoked etc.

The rule can also host multiple endpoints; just provide multiple classes in the constructor:

@Rule
public SoapEndpointPublishingRule serverRule =
                new SoapEndpointPublishingRule(
                    FakeCustomersEndpoint.class,
                    FakeOrdersEndpoint.class,
                    FakeProductsEndpoint.class);

To lookup a particular endpoint, specify its type:

FakeProductsEndpoint fakeProductsServerEndpoint =
            serverRule.getPublishedEndpoint(FakeProductsEndpoint.class);

The endpoint addresses that the server endpoints run on are determined automatically. If you want more control, then you can call one of SoapEndpointPublishingRule's overloaded constructors, passing in one or more SoapEndpointSpec instances.

XML Marshalling Support

Apache Isis' unit testing support also provides helper JaxbUtil and JaxbMatchers classes. These are useful if you have exampler XML-serialized representations of the SOAP requests and response payloads and want to use these within your tests.

Maven Configuration

Apache Isis' unit test support is most easily configured through a dependency on the isis-mavendeps-testing module:

<dependency>
    <groupId>org.apache.isis.mavendeps</groupId>
    <artifactId>isis-mavendeps-unittests</artifactId>
    <scope>test</scope>                             (1)
    <type>pom</type>
</dependency>
1 Normally test; usual Maven scoping rules apply.

This will set up unit testing support. There is no need to specify the version if you inherit from from the Parent POM.

The Parent POM configures the Maven surefire plugin in three separate executions for unit tests, integ tests and for BDD specs. This relies on a naming convention:

  • unit tests, which must have the name *Test*, but excluding …​

  • integ tests, which must have the name *IntegTest*

  • BDD integ specs, which must have the name *Spec*

Classes named *Abstract* are always ignored.

Not following this convention (intermixing unit tests with integ tests) may cause the latter to fail.

If you just want to set up unit testing support, then use:

<dependency>
    <groupId>org.apache.isis.core</groupId>
    <artifactId>isis-core-unittestsupport</artifactId>
    <scope>test</scope>
</dependency>