Unit Test Support

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

Maven Configuration

Dependency Management

If your application inherits from the Apache Isis starter app (org.apache.isis.app:isis-app-starter-parent) then that will define the version automatically:

pom.xml
<parent>
    <groupId>org.apache.isis.app</groupId>
    <artifactId>isis-app-starter-parent</artifactId>
    <version>2.0.0-M6</version>
    <relativePath/>
</parent>

Alternatively, import the core BOM. This is usually done in the top-level parent pom of your application:

pom.xml
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.apache.isis.core</groupId>
            <artifactId>isis-core</artifactId>
            <version>2.0.0-M6</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>

In addition, add a section for the BOM of all the testing support libraries:

pom.xml
<dependencyManagement>
    <dependencies>
        <dependency>
        	<groupId>org.apache.isis.testing</groupId>
	        <artifactId>isis-testing</artifactId>
            <scope>import</scope>
            <type>pom</type>
            <version>2.0.0-M6</version>
        </dependency>
    </dependencies>
</dependencyManagement>

Dependencies

In the domain module(s) of your application, add the following dependency:

pom.xml
<dependencies>
    <dependency>
        <groupId>org.apache.isis.testing</groupId>
        <artifactId>isis-testing-unittestsupport-applib</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

We also highly recommend using the Fakedata library for random values in your tests:

pom.xml
<dependencies>
    <dependency>
        <groupId>org.apache.isis.testing</groupId>
        <artifactId>isis-testing-fakedata-applib</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

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.

Update AppManifest

In your application’s AppManifest (top-level Spring @Configuration used to bootstrap the app), import the IsisModuleTestingUnitTestSupportApplib module:

AppManifest.java
@Configuration
@Import({
        ...
        IsisModuleTestingUnitTestSupportApplib.class,
        ...
})
public class AppManifest {
}

(In fact, this is optional; the classes in the unit test support library can be used outside of a running application).

PojoTester

The PojoTester utility automates the testing of getters and setters.

As getters and setters are so simple, the intention of automating their testing is not to discover defects (though if there are unintentional side-effects, then these will be found). Instead, the rationale of testing getters and setters is to increase code coverage. Any substantial gap away from 100% would therefore due to significant functionality not having tests (as opposed to merely getters and setters not being tested).

The class has built-in support for primitives and most of the other value types already recognised by the framework, and provides a mechanism to allow example instances for any application-specific types.

For example, given a class:

Customer.java
@Getter @Setter
public class Customer {
    private int custNum;
    private String name;
    private Date registeredOn;
    private Address address;
    private List<Order> orders;
}

then all of the getters and setters can be exercised using:

Customer.java
public class Customer_Test {

    @Test
    void exercise_getters_and_setters() {
        PojoTester.create()
            .usingData(Address.class, Address.class)    (1)
            .usingData(Order.class, Order.class)        (1)
            .exercise();
    }
}
1 provides the compile-time and run-time types of the referenced objects. The runtime-type must provide a no-arg constructor.

In many cases (as here) the same type can be used for both parameters.

There are several overloads of the usingData(…​) method, one of which accepts a PojoTester.DatumFactory instance. This interface provides a more flexible way to provide arbitrary datum instances for a specified type.

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>();
    ...
}

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("com.mycompany.myapp.modules");
        withLoggingTo(System.out);
    }
}

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.

Comparable

TODO ComparableContractTest_compareTo

Value Objects

The ValueTypeContractTestAbstract 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() {      (1)
        return Arrays.asList(new BigInteger("1"), new BigInteger("1"));
    }

    @Override
    protected List<BigInteger> getObjectsWithDifferentValue() { (2)
        return Arrays.asList(new BigInteger("2"));
    }
}
1 Returns a list of different instances that are expected to have the same value (according to equals and hashCode).
2 Returns a list of instances that are different from each other and also from the instance(s) provided in the getObjectsWithSameValue() method. As here, it is often sufficient to provide a single instance (as it is compared against the instances provided by getObjectsWithSameValue().

XML Marshalling Support

Apache Isis' unit testing support provides the JaxbMatchers while the core applib also provides the useful JaxbUtil class.

These can be useful for example if you have example XML-serialized representations of the SOAP requests and response payloads and want to use these within your tests.

JMock Extensions

The JMock extensions are deprecated; 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.

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())));
    }
}
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.

Utilities classes

The unittestsupport library also includes a number of miscellaneous utilities classes: