Fork me on GitHub

Introduction

An experimental test framework for UI.

The fine-grain decomposition of entities that is typical of the SteelBlue DCI architecture tends to make test code longer, because it is necessary to navigate through a few references to obtain (and assert) object attributes. For instance, while in plain code to access the display name of an object is usually as simple as:

assertThat(entity.getDisplayName()).isEqualTo("Leonardo da Vinci");

with DCI it becomes:

assertThat(pmL.as(_Displayable_).getDisplayName()).isEqualTo("Leonardo da Vinci");

Indeed a good test probably should be a bit more complex because the Displayable role might not be not available, so its presence should be first probed. A small set of tools in the style of AssertJ make it possible to simplify code. For instance, asserting the display name of an entity is as simple as:

assertThat(pmL).hasDisplayable().withDisplayName("Leonardo da Vinci");

or even:

assertThat(pmL).hasDisplayName("Leonardo da Vinci");

and the proper diagnostics is produced which clearly explains whether the role is missing or the display name is not the expected one.

UserActionProvider

The advantage of the ad-hoc test framework is much more evident when more complex relationships are probed. For instance the following code:

assertThat(pmL).hasUserActionProvider()
               .withActionCount(2)
               .withNextItemSatisfying(i -> i.hasDisplayName("Show bio"))
               .withNextItemSatisfying(i -> i.hasDisplayName("Show picture gallery"));

is quite compact and asserts that pm1 is associated to a UserActionProvider role, which in turn has got two UserActions, with the Show bio and Show picture gallery display names.

Composite

The code below:

assertThat(pm2).hasCompositeOf(_PresentationModel_)
               .withItemCount(2)
               .withNextItemSatisfying(i -> i
                       .hasDisplayName("Leonardo da Vinci")
                       .hasUserActionProvider()
                       .withActionCount(2)
                       .withNextItemSatisfying(j -> j.hasDisplayName("Show bio"))
                       .withNextItemSatisfying(j -> j.hasDisplayName("Show picture gallery")))
               .withNextItemSatisfying(i -> i
                       .hasDisplayName("Michelangelo Buonarroti")
                       .hasUserActionProvider()
                       .withActionCount(2)
                       .withNextItemSatisfying(j -> j.hasDisplayName("Show bio"))
                       .withNextItemSatisfying(j -> j.hasDisplayName("Show picture gallery")));

ensures that pm2 has got a Composite role of PresentationModels, which two children:

  • the former has got Leonardo da Vinci as the display name and an associated UserActionProvider with two actions, having Show bio and Show picture gallery display names.
  • the latter has got Michelangelo Buonarroti as the display name and an the same action of the previous item.

Indeed, the fact that usually all the children in a composite PresentationModel are provided with the same actions lead to redundancy in test code; common assertions can be enforced by using the withEachItemSatisfying() method, and the test shortens:

assertThat(pm2).hasCompositeOf(_PresentationModel_)
               .withItemCount(2)
               .withEachItemSatisfying(i -> i
                       .hasUserActionProvider()
                       .withActionCount(2)
                       .withNextItemSatisfying(j -> j.hasDisplayName("Show bio"))
                       .withNextItemSatisfying(j -> j.hasDisplayName("Show picture gallery")))
               .withNextItemSatisfying(i -> i.hasDisplayName("Leonardo da Vinci"))
               .withNextItemSatisfying(i -> i.hasDisplayName("Michelangelo Buonarroti"));

Aggregate

The code below:

assertThat(pm3).hasAggregateOf(_PresentationModel_)
               .withExactItemNames("firstName", "lastName", "birthDate")
               .withNextItemSatisfying(i -> i.hasDisplayName("Michelangelo"))
               .withNextItemSatisfying(i -> i.hasDisplayName("Buonarroti"))
               .withNextItemSatisfying(i -> i.hasDisplayName("March 6, 1475"));

ensures that pm3 has got an Aggregate role of PresentationModels associated to names firstName, lastName and birthDate, with three items whose display names are Michelangelo, Buonarroti and March 6, 1475.

withEachChildSatisfying() can be used with Aggregate too, as in the previous example.

MockSystemRoleFactory

This class is a mock implementation of SystemRoleFactory useful in tests, for simple cases, when one doesn't want to set up the whole class scanning infrastructure. It must be created by registering triples made of a datum class, a role class and a factory function that instantiates the role; the function argument is the owner of the role instances to create.

SystemRoleFactory.reset();
SystemRoleFactory.set(MockSystemRoleFactory.newInstance()
                                           .with(Painter.class, Displayable.class, o -> List.of(Displayable.of((o.getName())))));

Painter is an As-capable class such as:

    @RequiredArgsConstructor @Getter
    static class Painter implements As
      {
        @Delegate
        private final As asSupport = As.forObject(this);

        @Nonnull
        private final String name;
      }

the followed code will work in tests:

final var painter = new Painter("Michelangelo Buonarroti");
final var displayName = painter.as(_Displayable_).getDisplayName();
assertThat(painter).hasDisplayName("Michelangelo Buonarroti");
SystemRoleFactory.reset(); // don't wreck other tests

Note the SystemRoleFactory.set() and SystemRoleFactory.reset() calls used to install and uninstall the factory; they should be used before and after each test (typically in methods annotated with TestNG @BeforeMethod/@AfterMethod or equivalent).

Javadoc

Javadoc