The base packages provide test support for using base components or basic Java features. The support is always defined as JUnit 5 extensions that follow some concrete pattern.
In general the base testfixtures provide an annotation that can be used to annotate a test or test class that needs
a specific test support feature. The name of the annotations should always start with @With.... By doing so, it is
easy to see which test support is used in a test class. A concrete example for such an annotation is
@WithSystemOut. The annotation should be used in tests that somehow need to access the system out stream. While the
details of functionallity of the @WithSystemOut annotation can be found in the corresponding chapter the following
points are a good sample to show how the general pattern works:
- A test class is annotated with
@WithSystemOutwill run in isolation. That means that no other test will be executed in parallel. - A test class is annotated with
@WithSystemOutwill have access to the system out stream by additional support of the@WithSystemOutannotation.
As you can see such annotation can be used to define a specific test support feature. Each of that annotations define a JUnit extension that is added to the annotated test. In general the annotations / extensions should be compatible to each other. That means that it should be possible to use multiple annotations on a test class.
Next to that some extensions provide additional functionalities. That is normally provides by an additional API. In most
cases an instance of a facade to that api is provided by the extension. The facade can be injected into the test class.
To do so we use the common jakarta.inject.Inject annotation. Whenever a test extension in the base layer provides such
an API it is documented in the corresponding javadoc / chapter. When using the @WithSystemOut annotation a
SystemOutProvider can be injected in the test to access the system out stream. A sample of such test can look like
that:
@WithSystemOut
class MyTest {
@Inject
SystemOutProvider systemOutProvider;
@Test
void test() {
System.out.println("Hello World");
assertThat(systemOutProvider.getLines().toList()).contains("Hello World");
}
}The following sections describe the concrete extensions that are provided by the base layer.
The @WithSystemOut annotation can be used to redirect the system out stream to a buffer. The buffer can be accessed
by the SystemOutProvider that is injected (@Inject) into the test class. The SystemOutProvider provides a getLines() method
that returns a Stream<String> that emits all lines that are written to the system out stream. All annotated tests are
executed in isolation (see org.junit.jupiter.api.parallel.Isolated).
The @WithSystemError annotation can be used to redirect the system error stream to a buffer. The buffer can be accessed
by the SystemErrProvider that is injected (@Inject) into the test class. The SystemErrProvider provides a getLines() method
that returns a Stream<String> that emits all lines that are written to the system error stream. All annotated tests are
executed in isolation (see org.junit.jupiter.api.parallel.Isolated).
The @WithContext, @WithGlobalContext, @WithThreadLocalContext annotation are documented in the chapter of
the Context API.
The @WithTestExecutor annotation is used to inject a TestExecutor instance into a test. Each test using
@WithTestExecutor runs in isolation, ensuring that multiple tests do not share the same underlying thread pool
concurrently. This isolation helps prevent side effects between tests due to shared resources.
@WithTestExecutor
public class MyConcurrentTest {
// Your concurrent test methods here...
}@WithTestExecutor
public class TestExecutorExample {
@Inject
TestExecutor testExecutor;
// more code ...
@Test
void testWithConfig() {
// given
final List<Runnable> runnables =
IntStream.range(0, 20).mapToObj(this::createRunnable).toList();
testExecutor.executeAndWait(runnables);
}
}