Integration Test Support

Apache Causeway builds on top of Spring Boot’s integration testing support, in particular using @SpringBootTest. This configures and bootstraps the Apache Causeway runtime, usually running against an in-memory database.

On top of that, we usually:

To explain further, let’s walk through of the SimpleApp starter app.

Maven Configuration

Dependency Management

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

pom.xml
<parent>
    <groupId>org.apache.causeway.app</groupId>
    <artifactId>causeway-app-starter-parent</artifactId>
    <version>3.1.0</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.causeway.core</groupId>
            <artifactId>causeway-core</artifactId>
            <version>3.1.0</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>

In addition, add an entry for the BOM of all the testing support libraries:

pom.xml
<dependencyManagement>
    <dependencies>
        <dependency>
        	<groupId>org.apache.causeway.testing</groupId>
	        <artifactId>causeway-testing</artifactId>
            <scope>import</scope>
            <type>pom</type>
            <version>2.1.0</version>
        </dependency>
    </dependencies>
</dependencyManagement>

Dependencies

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

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

As a convenience, this dependency also brings in the Fakedata libraries. If not required this can always be explicitly excluded.

We also highly recommend using Fixture Scripts to manage the setup of the "given" state:

pom.xml
<dependencies>
    <dependency>
        <groupId>org.apache.causeway.testing</groupId>
        <artifactId>causeway-testing-fixtures-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 CausewayModuleTestingIntegTestSupportApplib module:

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

Bootstrapping

We typically put the bootstrapping of Apache Causeway into a superclass, one for each Maven module. This allows an individual "slice" of the application to be tested, rather than as a monolith.

So, for the module-simple module, we have:

SimpleModuleIntegTestAbstract.java
@SpringBootTest(
        classes = SimpleModuleIntegTestAbstract.AppManifest.class
)
@TestPropertySource({
        CausewayPresets.H2InMemory_withUniqueSchema,
        CausewayPresets.DataNucleusAutoCreate,
        CausewayPresets.UseLog4j2Test,
})
public abstract class SimpleModuleIntegTestAbstract extends CausewayIntegrationTestAbstractWithFixtures {

    @Configuration
    @Import({
        CausewayModuleCoreRuntimeServices.class,
        CausewayModuleSecurityBypass.class,
        CausewayModuleJdoDataNucleus5.class,
        CausewayModuleTestingFixturesApplib.class,
        SimpleModule.class                      (1)
    })
    public static class AppManifest {
    }
}
1 references just the SimpleModule

while in the webapp module we have:

ApplicationIntegTestAbstract.java
@SpringBootTest(
        classes = ApplicationIntegTestAbstract.AppManifest.class
)
@TestPropertySource({
        CausewayPresets.H2InMemory_withUniqueSchema,
        CausewayPresets.DataNucleusAutoCreate,
        CausewayPresets.UseLog4j2Test,
})
@ContextConfiguration
public abstract class ApplicationIntegTestAbstract extends CausewayIntegrationTestAbstract {

    @Configuration
    @Import({
        CausewayModuleCoreRuntimeServices.class,
        CausewayModuleJdoDataNucleus5.class,
        CausewayModuleSecurityBypass.class,
        CausewayModuleTestingFixturesApplib.class,
        ApplicationModule.class                 (1)
    })
    public static class AppManifest {
    }
}
1 references the top-level ApplicationModule

You can see that these are very similar, what’s different is the module referenced.

They both also force an in-memory database, with JDO/DataNucleus ORM configured to autocreate the database schema.

Faster bootstrapping

By default integration tests are run in "production" deployment mode. With the causeway.core.meta-model.introspector.mode= set to its default value, this results in full introspection of the Apache Causeway metamodel.

While this does have the benefit that the metamodel will be validated, the downside is that the test takes longer to bootstrap.

To bootstrap lazily, set the property to 'lazy'. This can be done by adding:

@TestPropertySource(CausewayPresets.IntrospectLazily)

Typical Usage

Let’s now drill down and look at SimpleObject_IntegTest, which (not surprisingly) is the integration test that exercises the SimpleObject class:

SimpleObject_IntegTest.java
@Transactional                                                                  (1)
public class SimpleObject_IntegTest extends SimpleModuleIntegTestAbstract {     (2)

    SimpleObject simpleObject;

    @BeforeEach                                                                 (3)
    public void setUp() {
        // given
        simpleObject = fixtureScripts.runPersona(SimpleObject_persona.FOO);     (4)
    }

    //...
}
1 The Spring @Transactional annotation means that any changes made in the database will be rolled back automatically. There’s further discussion below.
2 inherits from the module-level abstract class (which in turn will be annotated with @SpringBootTest, see bootstrapping earlier).
3 uses JUnit 5
4 uses a fixture script to setup the database. The FixtureScripts domain service is inherited from the superclass.

Testing a property

For example, let’s test the SimpleObject#name property:

SimpleObject.java
import lombok.Getter;
import lombok.Setter;

@DomainObject()
public class SimpleObject
    ...
    @Getter @Setter
    @Name private String name;
    ...
}

Properties are visible but unmodifiable by default We therefore have two tests for each of these facts.

Non-editable properties is change from Causeway v1.x. Use the causeway.applib.annotation.domain-object.editing configuration property to pverride.

By convention, we group tests into nested static classes, so the corresponding integration test is:

SimpleObject_IntegTest.java
public class SimpleObject_IntegTest extends SimpleModuleIntegTestAbstract {
    ...
    public static class name extends SimpleObject_IntegTest {

        @Test
        void accessible() {
            // when
            final String name = wrap(simpleObject).getName();   (1)

            // then
            assertThat(name).isEqualTo(simpleObject.getName()); (2)
        }

        @Test
        void not_editable() {

            // expect
            assertThatThrownBy(()->{                            (3)

                // when
                wrap(simpleObject).setName("new name");
            }).isInstanceOf(DisabledException.class);
        }
    }
}
1 we use the WrapperFactory to access the domain object.

This will check that property (for access) is visible to the current user.

2 we use AssertJ for assertions.
3 attempting to set the name on the property (through the wrapper) is disallowed. We detect this by catching a DisabledException (using AssertJ's assertThatThrownBy(…​)).

Testing an action

The way that the simple app allows the name to be updated is through an updateName action:

SimpleObject.java
@DomainObject()
public class SimpleObject
    ...
    public static class UpdateNameActionDomainEvent extends SimpleObject.ActionDomainEvent {}
    @Action(...
            domainEvent = UpdateNameActionDomainEvent.class)                (1)
    public SimpleObject updateName(
            @Name final String name) {                                      (2)
        setName(name);                                                      (3)
        return this;
    }
    public String default0UpdateName() {                                    (4)
        return getName();
    }
    ...
}
1 an event will be emitted whenever the action is interacted with
2 (the @Name annotation is discussed later)
3 the business functionality to be tested.
4 (we normally test default methods using unit tests).

The corresponding integration test is:

SimpleObject_IntegTest.java
public class SimpleObject_IntegTest extends SimpleModuleIntegTestAbstract {
    ...
    public static class updateName extends SimpleObject_IntegTest {

        @DomainService                                                      (1)
        public static class UpdateNameListener {

            @Getter
            List<SimpleObject.UpdateNameActionDomainEvent> events = new ArrayList<>();

            @EventListener(SimpleObject.UpdateNameActionDomainEvent.class)
            public void on(SimpleObject.UpdateNameActionDomainEvent ev) {
                events.add(ev);
            }
        }

        @Inject
        UpdateNameListener updateNameListener;

        @Test
        public void can_be_updated_directly() {

            // given
            updateNameListener.getEvents().clear();

            // when
            wrap(simpleObject).updateName("new name");                      (2)
            transactionService.flushTransaction();

            // then
            assertThat(wrap(simpleObject).getName()).isEqualTo("new name"); (3)
            assertThat(updateNameListener.getEvents()).hasSize(5);          (4)
        }
    }
1 defines a domain service to detect the domain events emitted when interacting with the action (through the wrapper).

This domain service is not part of the production code base, but is detected automatically thanks to @ComponentScan on the SimpleModule.

2 interact with the action through the wrapper
3 verify the object was updated correctly
4 verify that the domain events were emitted.

Since this was a successful interaction, there will have been 5 events, one for each of the event phases (hide, disable, validate, executing, executed)

Testing a type meta-annotation

As was pointed out earlier, the name parameter to updateName() has the @Name meta-annotation. This meta-annotation also defines some business rules, through @Property and @Parameter:

Name.java
@Property(mustSatisfy = Name.NoExclamationMarks.class, maxLength = Name.MAX_LEN)
@Parameter(mustSatisfy = Name.NoExclamationMarks.class, maxLength = Name.MAX_LEN)
@ParameterLayout(named = "Name")
...
public @interface Name {

    int MAX_LEN = 40;

    class NoExclamationMarks extends AbstractSpecification2<String> {

        @Override
        public TranslatableString satisfiesTranslatableSafely(final String name) {
            return name != null && name.contains("!")
                    ? TranslatableString.tr("Exclamation mark is not allowed")
                            : null;
        }
    }
}

We test this by checking the wrapper throws an InvalidException:

SimpleObject_IntegTest.java
public class SimpleObject_IntegTest extends SimpleModuleIntegTestAbstract {
    ...
    public static class updateName extends SimpleObject_IntegTest {
        ...
        @Test
        public void failsValidation() {

            // expect
            InvalidException cause = assertThrows(InvalidException.class, ()->{

                // when
                wrap(simpleObject).updateName("new name!");
            });

            // then
            assertThat(cause.getMessage(), containsString("Exclamation mark is not allowed."));
        }
    }

Some of the methods being called here are inherited from the superclass, so let’s look at that next.

CausewayIntegrationTestAbstract / CausewayIntegrationGwtAbstract

For convenience the framework provides the CausewayIntegrationTestAbstract and CausewayIntegrationGwtAbstract classes, either of which can be used as a base class for integration tests.

These classes that provides the wrap(…​) method (as see in the previous section). It also provides a number of other convenience methods:

  • wrap() - as just mentioned. Also has an alias of w().

  • unwrap() - to unwrap.

  • mixin() - to instantiate a mixin around a domain object. Also has an alias of m().

  • wrapMixin() - which naturally enough wrap()s the result of a mixin(). This has an alias of wm().

This class also provides a number of injected domain services:

Wrapper Factory

The WrapperFactory service is responsible for wrapping a domain object in a dynamic proxy, of the same type as the object being proxied. The role of this wrapper is to simulate the UI.

It does this by allowing through method invocations that would be allowed if the user were interacting with the domain object through one of the viewers, but throwing an exception if the user attempts to interact with the domain object in a way that would not be possible if using the UI. The WrapperFactory uses ByteBuddy to perform its magic.

The mechanics are as follows:

  1. the integration test calls the WrapperFactory to obtain a wrapper for the domain object under test. This is usually done in the test’s setUp() method.

  2. the test calls the methods on the wrapper rather than the domain object itself

  3. the wrapper performs a reverse lookup from the method invoked (a regular java.lang.reflect.Method instance) into the Apache Causeway metamodel

  4. (like a viewer), the wrapper then performs the "see it/use it/do it" checks, checking that the member is visible, that it is enabled and (if there are arguments) that the arguments are valid

  5. if the business rule checks pass, then the underlying member is invoked. Otherwise an exception is thrown.

The type of exception depends upon what sort of check failed. It’s straightforward enough: if the member is invisible then a HiddenException is thrown; if it’s not usable then you’ll get a DisabledException, if the args are not valid then catch an InvalidException.

wrapper factory

Wrapping and Unwrapping

Wrapping a domain object is very straightforward; simply call WrapperFactory#wrap(…​).

For example:

Customer customer = ...;
Customer wrappedCustomer = wrapperFactory.wrap(wrappedCustomer);

Going the other way — getting hold of the underlying (wrapped) domain object — is just as easy; just call WrapperFactory#unwrap(…​).

For example:

Customer wrappedCustomer = ...;
Customer customer = wrapperFactory.unwrap(wrappedCustomer);

If you prefer, you also can get the underlying object from the wrapper itself, by downcasting to WrappingObject and calling __causeway_wrapped() method:

Customer wrappedCustomer = ...;
Customer customer = (Customer)((WrappingObject)wrappedCustomer).__causeway_wrapped());

We’re not sure that’s any easier (in fact we’re certain it looks rather obscure). Stick with calling unwrap(…​)!

Using the wrapper

As the wrapper is intended to simulate the UI, only those methods that correspond to the "primary" methods of the domain object’s members are allowed to be called. That means:

  • for object properties the test can call the getter or setter method

  • for object collections the test can call the getter to access the contents.

    For this to work properly, the collection should be marked as read-only, generally globally using the causeway.applib.annotation.domain-object.editing configuration property.
  • for object actions the test can call the action method itself.

As a convenience, we also allow the test to call any default…​(),choices…​() or autoComplete…​() method. These are often useful for obtaining a valid value to use.

What the test can’t call is any of the remaining supporting methods, such as hide…​(), disable…​() or validate…​(). That’s because their value is implied by the exception being thrown.

The wrapper does also allow the object’s title() method or its toString() , however this is little use for objects whose title is built up using the @Title annotation. Instead, we recommend that your test verifies an object’s title by calling TitleService#titleOf(…​) method.

Firing Domain Events

As well as enforcing business rules, the wrapper has another important feature, namely that it will cause domain events to be fired.

The walk through of SimpleApp above showed how this works.

Manual Teardown

The Spring @Transactional annotation means that any changes made in the database will be rolled back automatically. Normally this is what we want. For more on this topic, see the Spring docs.

Sometimes you may want to commit to the database to verify state, eg using Spring’s @Commit annotation. In these cases, you’ll need to manually tear down the database contents afterwards, in readiness for the next test.

This can be done using the fixture library’s ModuleWithFixtures interface:

ModuleWithFixtures.java
public interface ModuleWithFixtures {
    default FixtureScript getRefDataSetupFixture() {        (1)
        return FixtureScript.NOOP;
    }
    default FixtureScript getTeardownFixture() {            (2)
        return FixtureScript.NOOP;
    }
}
1 Optionally each module can define a FixtureScript which holds immutable "reference data".
2 Optionally each module can define a tear-down FixtureScript, used to remove the contents of all of the operational/transactional entities (but ignoring reference data fixtures).

This should be implemented by module classes, eg as is the case for SimpleModule.

SimpleModule.java
@Configuration
@Import({})
@ComponentScan
public class SimpleModule implements ModuleWithFixtures {

    @Override
    public FixtureScript getTeardownFixture() {
        return new TeardownFixtureAbstract() {
            @Override
            protected void execute(ExecutionContext executionContext) {
                deleteFrom(SimpleObject.class);
            }
        };
    }
    ...
}

The ModuleWithFixturesService aggregates all these setup and teardown fixtures, honouring the module dependencies:

ModuleWithFixtures.java
@Service
public class ModuleWithFixturesService {
    ...
    public FixtureScript getRefDataSetupFixture() { ... }
    public FixtureScript getTeardownFixture() { ... }
    ...
}

Thus, the setup fixture runs the setup for the "inner-most" "leaf-level" modules with no dependencies first, whereas the teardown fixture runs in the opposite order, with the "outer-most" "top-level" modules torn down first.

Hints-n-Tips

Using JDO/DataNucleus

When running integration tests through the IDE, and if using the JDO/DataNucleus ORM, then make sure the module(s) with entities have been enhanced first.

If using IntelliJ, we recommend creating a run Maven configuration that runs datanucleus:enhance, and then pinning the configuration once run as a tab in the "Debug" window for easy access.

pin enhance run configuration