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 UserAction
s, 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 PresentationModel
s, which two children:
- the former has got
Leonardo da Vinci
as the display name and an associatedUserActionProvider
with two actions, havingShow bio
andShow 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 PresentationModel
s 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).