Skip to main content

Expanders Assert

Expanders assert is a library to test Expanders and Features. It improves on the expanders-test-utils library to fix a few long-standing issues:

  • The expansionResource manifest for the project is generated ad hoc, which makes the tests more predictable.
  • The framework actively checks for classpath conflicts and gives clear error messages.

There are also improvements which make it easier to create tests with new Metamodels:

  • Expanders-assert fixes issues with creating ExpansionContexts by using the same infrastructure that is used during expansion.
  • Finding elements in your test models has been simplified by using the DataRegistry.

Setup

Add the following dependency to your expanders project:

<dependency>
<groupId>net.democritus.test</groupId>
<artifactId>expanders-assert</artifactId>
<version>1.1.0</version>
<scope>test</scope>
</dependency>

The library is based on junit 5 with AssertJ. Add these to the test dependencies with:

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.26.3</version>
<scope>test</scope>
</dependency>
</dependencies>

ExpanderTest

Each Expanders test should be annotated with @ExpanderTest. This will enable the junit 5 extensions to test the Expander.

import net.democritus.expander.junit.ExpanderTest;

@ExpanderTest
public class SampleExpanderTest {

}
ExpanderTest Class Name

By default, the test will look for an Expander with the same name as the test class minus the Test-suffix. If the Test class name does not match the Expander, you can provide the name of the Expander in the @ExpanderTest annotation.

@ExpanderTest(name = "net.democritus.expander.SampleExpander")

FeatureTest

Feature tests (both for the feature itself and the ExpanderFeatures), should be annotated with @FeatureTest.

import net.democritus.expander.junit.FeatureTest;

@FeatureTest
public class MyFeatureTest {

}
FeatureTest Class Name

The Feature (and ExpanderFeature) are resolved similarly to the Expander, based on the Test class name:

  • If the Test class matches the Feature name (e.g. MyFeatureTest for MyFeature), then it will select that feature and pick the first of its ExpanderFeature.
  • If the Test class matches the ExpanderFeature name (e.g. MyFeatureBeanExpanderTest for MyFeature.BeanExpander), then it will select that ExpanderFeature and its Feature.

It is also possible to explicitly define the Feature and Expander.

@Feature(feature = "net.democritus.feature.MyFeature")
@Feature(
feature = "net.democritus.feature.MyFeature",
expander="net.democritus.expander.ejb3.dataElement.BeanExpander")

Test Model

Every test starts with a test model. To define this model, create a method names baseModel() with the @TestModel annotation.

import net.democritus.expander.junit.ExpanderTest;

@ExpanderTest
public class SampleExpanderTest {

@TestModel(select = "testComp::City")
Spec baseModel() {
return component("testComp",
dataElement("City"));
}

}

The model is defined using Onion Specs. You can optionally define the select parameter to select an Element within the model.

TestExpansion

Each test method receives a TestExpansion instance. This object allows you to assert conditions of your Expander/Feature.

Testing Expanders

To test the results of an Expander, use testExpansion.assertThatExpander().

@ExpanderTest
public class SampleExpanderTest {

@TestModel(select = "testComp::City")
Spec baseModel() {
return component("testComp",
dataElement("City",
set("packageName", "net.demo.cities")));
}

@Test
void test_artifactPath(TestExpansion<DataElementComposite> expansion) {
/*
Test the result of the artifact path definition in the Expander XML file.
`$componentRoot.directory$` or `module.rootDirectory` will be translated to `C:/MODULE_ROOT`.
`$applicationRoot.directory` or `program.rootDirectory` will be translated to `C:/PROGRAM_ROOT`.
*/
expansion
.assertThatExpander()
.hasArtifactPath("C:/MODULE_ROOT/net/demo/cities/City.java");
}

@Test
void test_isApplicable(TestExpansion<DataElementComposite> expansion) {
/* Test isApplicable condition defined in the Expander XML file */
expansion
/* extendModel() allows you to add additional specifications to the model */
.extendModel(option("example.option"))
.assertThatExpander().isApplicable();
}

@Test
void test_isNotApplicable(TestExpansion<DataElementComposite> expansion) {
expansion.assertThatExpander().isNotApplicable();
}

@Test
void test_base(TestExpansion<DataElementComposite> expansion) {
/*
Test the base content generated by the expander.
*/
expansion.assertThatExpander().baseContent().matchesTemplate();
}

@Test
void test_anchor_fields(TestExpansion<DataElementComposite> expansion) {
/*
Test the `fields` anchor.
*/
expansion
.extendModel(
field("name: String"),
field("status: String"))
.assertThatExpander().anchor("fields")
.matchesTemplate();
}

}

Base Content

expansion.assertThatExpander().baseContent() allows you to match the content your Expander generates. To make testing more manageable, it is possible to put anchors around parts of the expander template. When testing the base content, the content between those anchors is filtered out.

Expander template
base() ::= <<
package <packageName>;
// <expanderComment>

public class <dataElement> {

@anchor:fields
// anchor:fields:start
<fields:{f|private <f.type> <f.name>;};separator="\n">
// anchor:fields:end

}
>>
Expander Test template
test_base() ::= <<
package net.demo.cities;
// @expanderComment@

public class City {
// anchor:fields:start
// anchor:fields:end
}
>>

matchesTemplate will compare the content to a template in the ExpanderTest stg file with the same name as this method (test_base in this case).

Attention

Both the expander template and expander test template are evaluated by the template engine. Because the \ character is an escape character for this engine, it is required to escape that character twice in both templates if it occurs in the artifact that is being expanded.

Example: If an escaped backslash \\ is needed in a piece of expanded code because it occurs in a string, both the expander template AND test template must double escape it as \\\\.

Anchors

The content between anchors can be tested separately with expansion.assertThatExpander().anchor("fields"), fields here being the name of the anchor.

Expander Test template
test_anchor_fields() ::= <<
private String name;
private String status;
>>

Test Features

To test Features, we use testModel.assertThatFeature().

Feature Test
@FeatureTest
public class MyFeatureTest {

@TestModel(select = "testComp::City")
Spec baseModel() {
return component("testComp",
dataElement("City"));
}

@Test
public void test_isApplicable(TestExpansion<DataElementComposite> expansion) {
/* Test condition in Feature XML. */
expansion
.extendModel(option("example.feature"))
.assertThatFeature().isApplicable();
}


@Test
public void test_isNotApplicable(TestExpansion<DataElementComposite> expansion) {
expansion.assertThatFeature().isNotApplicable();
}

}
ExpanderFeature Test
@FeatureTest
public class SampleFeatureSampleExpanderTest {

@TestModel(select = "testComp::City")
Spec baseModel() {
return component("testComp",
dataElement("City"));
}

@Test
public void test_base(TestExpansion<DataElementComposite> expansion) {
/* Test the base content of the ExpanderFeature. */
expansion.assertThatFeature().baseContent().matchesTemplate();
}

@Test
public void testImports(TestExpansion<DataElementComposite> expansion) {
/* Test imports that were added with usage statements in the mapping file.*/
expansion.assertThatFeature().imports().matchesTemplate();
}

}