JUnit5-Extensions
Useful features for testing with JUnit 5: autogenerating tests with test templates, for reflections, resource bundles, serializable.
Table fo contents
Minimum requirements
- Java 8
- Maven 3.2.5
- JUnit 5.3
Using in your project
Add in your pom.xml these modifications
<dependencies>
...
<!-- other dependencies -->
...
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>name.bychkov</groupId>
<artifactId>junit5-annotations</artifactId>
<version>0.5.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>name.bychkov</groupId>
<artifactId>junit5-tests</artifactId>
<version>0.5.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
...
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<dependenciesToScan>
<!-- run junit-tests from dependency -->
<dependency>name.bychkov:junit5-tests</dependency>
</dependenciesToScan>
</configuration>
</plugin>
</plugins>
</build>
Notes:
- maven-surefire-plugin must have version >= 2.22.0
Features
Important common note: all annotations are defined with RetentionPolicy SOURCE and used in compile-time. After using they are discarded by compiler and absent in compiled (*.class) code.
Parameterized Constructors
One of the most used feature of JUnit 5 - parameterized tests. There are many ways to specify parameters for such test and check with one test many conditions. Powerful thing. But @ParameterizedTest
and all @*Source
annotations can be applied only to methods, not to constructors. @ParameterizedConstructor
and a set of @*Source
annotations eliminate this inconvenience. With @ParameterizedConstructor
you can create test-classes and use it as templates for tests.
Example
public abstract ServerTest extends org.glassfish.jersey.test.JerseyTest {
@ParameterizedConstructor
@MethodSource(value="testFactories")
public ServerTest(TestContainerFactory testContainerFactory) {
super(testContainerFactory);
}
public static Collection<TestContainerFactory> testFactories() {
return Arrays.asList(new JacksonJsonTestProvider(), new MoxyJsonTestProvider(), new JettisonMappedJsonTestProvider());
}
@Test
public void test1() {
...
}
@Test
public void test2() {
...
}
}
During executing this test class will be instantiated for each parameter value and (default behaviour) for each test-method. In the example above ServerTest will be instantiated 3 (parameters count) * 2 (test-methods count) = 6 times. But this default behaviour can be changed with @TestInstance(Lifecycle.PER_CLASS)
applied to class declaration or with engine configuration. In this case ServerTest will be instantiated 3 (parameters count) times.
Notes to usage @ParameterizedConstructor
:
1) test-class must be defined with abstract
modifier. Thanks to this test-methods of this class are not considered as usual test-methods. Without default constructor (constructor without parameters) attempt to their execution as usual tests will be ended with error.
2) some annotations (but not all) of usual tests are applicable to tests with parameterized constructor. They are @Disabled
, @TestInstance
and methods annotated with @BeforeAll
, @BeforeEach
, @AfterEach
and @AfterAll
.
More samples
You can find yet another example of usage this annotation here.
Safely work with reflections
In current version JUnit5-Extensions supports automatically creation of JUnit5 test for controlling existence/accessability of constructors/fields/methods of Java-classes through Reflection.
Problem description
Suppose you have a code in your project
void clearCachedSettings() throws Exception {
Field mavenSettings = MavenSettings.class.getDeclaredField("mavenSettings");
mavenSettings.setAccessible(true);
mavenSettings.set(null, null);
}
How can you be sure, that in next version MavenSettings
yet contains the field with name mavenSettings
?
This code is very fragile. Smells bad, do you agree? Yes, of course, this is excellent example of anti-pattern. But in some situations you cannot avoid Java Reflections.
Solution
What can you do? Modify sample code so:
@CheckField(targetClass=MavenSettings.class, value="mavenSettings")
void clearCachedSettings() throws Exception {
Field mavenSettings = MavenSettings.class.getDeclaredField("mavenSettings");
mavenSettings.setAccessible(true);
mavenSettings.set(null, null);
}
Next time when you will assembly your project with annotation creates JUnit5-test that will control existence of field with name mavenSettings
in class MavenSettings
. If field not exists, JUnit5-test fails.
You can use annotations @CheckField
, @CheckFields
, @CheckMethod
and @CheckConstructor
to autocreate junit-tests.
More samples
You can find yet another example of usage this annotations of JUnit5-Extensions here: examples/reflections.
Safely work with resource bundles
Problem description
If your application supports multiple languages, you know about Resource Bundles. Localized strings are language-specific and stored in *.properties (since Java 9 in *.xml too) files and have a view
key1=value1
key2=value2
key3=value3
...
In java code these localized strings are used usually as
private ResourceBundle bundle = ResourceBundle.getBundle("Messages", Locale.ENGLISH);
public String getLocalizedKey1() {
return bundle.getString(key1);
}
All will be good when you developing an application. But after several redesign nobody can already say, that localiyed strings are used and that are obsolete and can be removed.
Solution
@CheckResourceBundle(baseName="Messages", locales={"en","de"})
private ResourceBundle bundle = ResourceBundle.getBundle("Messages", Locale.ENGLISH);
@CheckKey(baseName="Messages", value="key1", locale="en")
public String getLocalizedKey1() {
return bundle.getString("key1");
}
With annotations @CheckKey
, @CheckKeys
and @CheckResourceBundle
you can be always sure, that key exists in default locale resource bundle and all keys in specified locales of specified resource bundle are synchronized.
More samples
Full example you can find here: examples/resource-bundle.
Check classes for Serializable
Problem description
In some cases some of your classes must be serializable (must implement java.io.Serializable). If class has no such interface, in most of cases java-compiler says nothing and an error in runtime can be acquired.
Solution
in java-package with serializable classes add file package-info.java
with such contents:
@CheckSerializable
package your.package.name;
When added, this annotation forced to run unit-test for check all classes of your.package.name
and fails if any class not implements java.io.Serializable.
More samples
Here you can see full examples of usage JUnit5-Extensions annotation @CheckSerializable
.