Skip to main content

Using Onion Specs to build models for tests

The onion-spec is a set of helper classes that can be used to define models to be used in tests. This chapter will explain some of the features of the specs.

You can build a model using the TestModelBuilder class

private final TestModelBuilder specBuilder = new TestModelBuilder();

private ComponentComposite baseModel() {
return specBuilder.buildModel(
component("testComp",
dataElement("City")));
}

To build a Component model, you can use the buildModel(spec) method:

specBuilder.buildModel(
component("testComp",
dataElement("City")));

You can also build a Component and select a DataElement with the buildModelAndFind(spec, selector) method:

specBuilder.buildModelAndFind(
component("testComp",
dataElement("City")),
dataElement("testComp::City"));

Alternatively, you can pass an ApplicationSpec to build an Application model:

specBuilder.buildModel(
application("testApp",
component("testComp",
dataElement("City"))));

Spec types

There are 3 types of specs:

  • ElementSpecs define a model for an element. They always have the same structure: <elementSpec>(<specification>, <childSpecs>...).
  • AttributeSpecs define a specific attribute of an element. They are created with set(<attributeName>, <value>)
  • DataRefSpecs define a link to another element. They are created with dataRef(<linkName>, <reference>)

To make this clearer, we'll use the example of a taskElement:

A taskElement with name 'MailReceiver':

taskElement("MailReceiver")

Now let's set the packageName attribute with an AttributeSpec:

taskElement("MailReceiver",
set("packageName", "net.demo"))

Next, we can define a link to a targetElement:

dataElement("MailReceiver",
set("packageName", "net.demo"),
dataRef("targetElement", "testComp::Mail"))

The notation used here to define the Mail dataElement is a functional key. It references the element by adding the name of the component. In the same way, we can reference a field with testComp::Mail::subject

DataElements, Fields and Finders

A dataElement can contain fields and finders. To reduce the complexity, fields and finders have a shorthand syntax:

dataElement("City",
field("postcode: String"),
field("country: Country[Ln01]"),
field("/population: Long"),
finder("findByPostcodeEq"))

In this example we have:

  • a value field postcode with valueFieldType String
  • a link field country with target element Country and linkFieldType Ln01
  • a calculated field (recognized by the /-prefix) population with valueFieldType Long
  • a finder findByPostcodeEq with field-operator-pair postcode-eq, which is added based on the finder's name

DataProjections

To define a dataProjection, you can add referenceFields and calculatedFields. CalculatedFields will have a syntax similar to the value-fields. ReferenceFields should contain the name of the fields they are referencing.

dataElement("City",
field("postcode: String"),
field("country: Country[Ln01]"),
dataProjection("AreaInfo",
referenceField("postcode"),
calculatedField("size: Long")))

DataCommands

DataCommands can have connectorFields which are defined the same as regular fields, except they cannot be calculated.

dataElement("City",
field("postcode: String"),
field("country: Country[Ln01]"),
dataCommand("addDistrict",
set("hasTargetInstance", true),
connectorField("name: String"),
connectorField("location: Location[Ln01]")))