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 Vincias the display name and an associatedUserActionProviderwith two actions, havingShow bioandShow picture gallerydisplay names. - the latter has got
Michelangelo Buonarrotias 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).
SteelBlue :: Test