Using Maven:
<dependency>
<groupId>com.innogames</groupId>
<artifactId>junit5-scenario-builder</artifactId>
<version>1.0.3</version>
<scope>test</scope>
</dependency>
Using Gradle:
testImplementation "com.innogames:junit5-scenario-builder:1.0.3"
If you only want to use the Scenario Builder without the JUnit extension, you can use
com.innogames:scenario-builder
instead.
The GivenScenario
holds all information about the scenario that you want to build. The idea is that you just have to
configure this object in tests. The actual creation of entities happens in the background.
Everything inside this class is specific to your application. That means you have to create your own class that
implements the GivenScenario
interface.
Here's an example implementation:
// The "root" scenario that can be configured in tests.
// It must implement the GivenScenario interface
public class GivenAppScenario implements GivenScenario {
// In this scenario, you can configure users that should be
// created by the Scenario Builder
private final List<GivenUser> users = new ArrayList<>();
public List<GivenUser> getUsers() {
return users;
}
// You should provide a fluent API to increase the
// readability of your tests!
public GivenAppScenario withUser(Consumer<GivenUser> userConsumer) {
var givenUser = new GivenUser();
userConsumer.accept(givenUser);
this.users.add(givenUser);
return this;
}
}
// It is recommended to use the "Given"-prefix on all classes that
// are used inside the GivenScenario to make the intention clear.
public class GivenUser {
// You should set default values that are used when these
// fields are not configured in tests
private String username = "testUser";
private String password = "test";
// ... getters for username and password here
public GivenUser withUsername(String username) {
this.username = username;
return this;
}
public GivenUser withPassword(String password) {
this.password = password;
return this;
}
}
While "Given" objects only hold the scenario configuration, "Builder Parts" are there to actually create all entities.
The GivenScenario example above contains users, so you have to define a ScenarioBuilderPart
that creates those users.
public class UserBuilderPart implements ScenarioBuilderPart<GivenAppScenario> {
@Override
public int getOrder() {
// Builder parts are executed in the order that is defined
// here (from lowest to highest).
return 1;
}
@Override
public void build(GivenAppScenario givenScenario) {
// This method receives the whole GivenScenario object, but you should
// just pick the information that is needed for this specific builder part.
givenScenario.getUsers().forEach(givenUser -> {
// ... create User entitiy here and store it in the database.
});
}
}
Lastly, you have to create the Extension class for Junit. Here you define the available Builder Parts and tell the
Scenario Builder how to create your GivenScenario
object.
public class AppScenarioExtension extends ScenarioExtension<GivenAppScenario> {
@Override
protected Collection<ScenarioBuilderPart<GivenAppScenario>> getBuilderParts(ExtensionContext extensionContext) {
// Returns all builder parts.
// If you use Spring Framework and your builder parts are Spring Beans, you can load them here via
// `SpringExtension.getApplicationContext(extensionContext).getBeansOfType(ScenarioBuilderPart.class)`
return List.of(
new UserBuilderPart()
);
}
@Override
protected GivenAppScenario createGivenScenario(ExtensionContext extensionContext) {
// Here you just create a new instance of the GivenScenario class.
// This method is called per test execution and you can get information about
// the running test from the passed extensionContext if needed.
return new GivenAppScenario();
}
}
Finally, you can add the extension via @ExtendWith
to your tests. You have two options to access the Scenario Builder:
If you add a parameter of type ScenarioBuilder
to your test methods, it will be injected automatically.
@ExtendWith(AppScenarioExtension.class)
class LoginIntegrationTest {
@Test
public void testLogin(ScenarioBuilder<GivenAppScenario> scenarioBuilder) {
scenarioBuilder.build(scenario -> scenario
.withUser(user -> user
.withUsername("Christian")
.withPassword("MySecretPassword")
)
);
// Usually you would call a login endpoint here with username and password
// and assert that the login was successful.
}
}
See also Custom Scenario Builder if you want to reduce the verbosity a bit.
If you have a base test class, you can implement the ScenarioAware
interface to inject the ScenarioBuilder instead
of passing it as parameter in each test.
class UserIntegrationTest extends BaseIntegrationTest {
@Test
public void testLogin() {
scenarioBuilder.build(scenario -> scenario
.withUser(user -> user
.withUsername("Christian")
.withPassword("MySecretPassword")
)
);
// Usually you would call a login endpoint here with username and password
// and assert that the login was successful.
}
}
@ExtendWith(AppScenarioExtension.class)
public abstract class BaseIntegrationTest implements ScenarioAware<GivenAppScenario> {
protected ScenarioBuilder<GivenAppScenario> scenarioBuilder;
@Override
public void setScenarioBuilder(ScenarioBuilder<GivenAppScenario> builder) {
scenarioBuilder = builder;
}
}
It is very common that you need the entities or IDs that were generated by the Scenario Builder later in the test. For example if you want to test a user profile endpoint and this endpoints requires a userId:
@Test
public void testProfile() {
// A reference holder object which is empty at this point
var userRef = new Ref<UserEntity>();
scenarioBuilder.build(scenario -> scenario
.withUser(user -> user
// Tell the Scenario Builder that it should store
// the created user entity in the reference object
.ref(userRef)
.withUsername("Christian")
)
);
// To call an endpoint like "/profile/<userId>" you can now get
// the generated userId via `userRef.get().getId()`
assertNotNull(userRef.get().getId());
}
To accomplish this:
- The
GivenUser
class must extend fromEntityRefHolder
. - The
UserBuilderPart
must store the entity in theGivenUser
object after creation.
Read more about it in the Entity References section or look at the example implementation in the test folder.