Reputation: 24482
Can you give a simple explanation of @TestInstance
annotation and how it is useful in JUnit 5?
I think we can achieve the same effect probably by making our fields static
.
Upvotes: 73
Views: 61842
Reputation: 410
This annotation was introduced to reduce the number of objects created when running your unit tests.
Adding @TestInstance(TestInstance.Lifecycle.PER_CLASS)
to your test class will avoid that a new instance of your class is created for every test in the class.
This is particulary usefull when you have a lot of tests in the same test class and the instantiation of this class is expensive.
This annotation should be used with caution. All unit tests should be isolated and independent of eachother. If one of the tests changes the state of the test class then you should not use this feature.
Making your fields static to achieve the same effect is not a good idea. It will indeed reduce the number of objects created but they cannot be cleaned up when all tests in the test class are executed. This can cause problems when you have a giant test suite.
Upvotes: 39
Reputation: 24482
This is also useful when writing tests in Kotlin (because it doesn't have static methods).
So, instead of using a companion object with @JvmStatic
funs in it for @BeforeAll
or @AfterAll
, make the lifecycle PER_CLASS
and annotate regular methods with @BeforeAll
or @AfterAll
:
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyTest {
@BeforeAll
fun setup() {
println("I am invoked only once")
}
}
In addition, it also makes it possible to use simple class methods as factory functions for @MethodSource
in parameterized tests (instead of wrapping the factory function in companion object {}
and marking the factory function with @JvmStatic
):
@ParameterizedTest
@MethodSource("generateStrings")
fun `Test strings`(argument: String) {
check(argument.isNotEmpty())
}
private fun generateStrings() = listOf(
"java",
"kotlin"
)
When using this approach, be careful to reset your instance variables in @BeforeEach
or @AfterEach
funs if necessary.
Thanks to this article for its help.
Upvotes: 3
Reputation: 687
since no one provide a proper coding example, I would like to give a simple code sample as below to understand the concept,
Per Method Sample - Default Option in Junit5 Note two methods are static, otherwise it will fire an exception because class instantiate in each method.
@TestInstance(Lifecycle.PER_METHOD)
public class MathUtilTestPerMethod {
MathUtil util;
@BeforeAll
static void beforeAllInit() {
System.out.println("running before all");
}
@AfterAll
static void afterAllCleanUp() {
System.out.println("running after all");
}
@BeforeEach
void init() {
util = new MathUtil();
System.out.println("running before each...");
}
@AfterEach
void cleanUp() {
System.out.println("running after each...");
}
@Test
void testSum() {
assertEquals(2, util.addtwoNumbers(1, 1));
}
}
Per Class Sample Note that static is removed from the two methods and MathUtil object is created globally not in a method, because class instantiate only once.
@TestInstance(Lifecycle.PER_CLASS)
public class MathUtilTestPerClass {
MathUtil util = new MathUtil();
@BeforeAll
void beforeAllInit() {
System.out.println("running before all");
}
@AfterAll
void afterAllCleanUp() {
System.out.println("running after all");
}
@BeforeEach
void init() {
System.out.println("running before each...");
}
@AfterEach
void cleanUp() {
System.out.println("running after each...");
}
@Test
void testSum() {
assertEquals(2, util.addtwoNumbers(1, 1));
}
}
Upvotes: 8
Reputation: 130857
@TestInstance
is used to configure the lifecycle of test instances for the annotated test class or test interface:
PER_CLASS
: A new test instance will be created once per test class.PER_METHOD
: A new test instance will be created for each test method, test factory method, or test template method. This mode is analogous to the behavior found in JUnit versions 1 through 4.If @TestInstance
is not explicitly declared on a test class or on a test interface implemented by a test class, the lifecycle mode will implicitly default to PER_METHOD
.
Setting the test instance lifecycle mode to PER_CLASS
enables the following features:
@BeforeAll
and @AfterAll
methods in the test class.@BeforeAll
and @AfterAll
methods in @Nested
test classes.@BeforeAll
and @AfterAll
on interface default methods.@BeforeAll
and @AfterAll
methods in test classes implemented with the Kotlin programming language.See the test instance lifecycle documentation for further details.
Upvotes: 15
Reputation: 47865
I think the docs provide a useful summary:
If you would prefer that JUnit Jupiter execute all test methods on the same test instance, simply annotate your test class with @TestInstance(Lifecycle.PER_CLASS). When using this mode, a new test instance will be created once per test class. Thus, if your test methods rely on state stored in instance variables, you may need to reset that state in @BeforeEach or @AfterEach methods.
The "per-class" mode has some additional benefits over the default "per-method" mode. Specifically, with the "per-class" mode it becomes possible to declare @BeforeAll and @AfterAll on non-static methods as well as on interface default methods. The "per-class" mode therefore also makes it possible to use @BeforeAll and @AfterAll methods in @Nested test classes.
But you've probably read that already and you are correct in thinking that making a field static will have the same effect as declaring the field as an instance variable and using @TestInstance(Lifecycle.PER_CLASS)
.
So, perhaps the answer to the question "how it could be useful in JUnit 5" is that using a @TestInstance
...
@TestInstance
is less likely to be accidental or a result of thoughless copy-n-paste.Upvotes: 59