JUnit 5 non-static Parameterized Method
JUnit 5 provides a significant upgrade over JUnit 4 in terms
of running parameterized tests. In JUnit 5, you
had to pass the parameters to your constructor, making it unnatural
to mix parameterized and non-parameterized tests in the same test
class. In JUnit 4, the parameterization functionality attaches
to a singular test method. Usually, @ValueSource
or @CsvSource
gives you all the flexibility that you need. When you need slightly
more complex parameters, you can use @MethodSource
.
If you follow most online guides on @MethodSource
you would
believe that you need the method to be static
. This is however
not true. If you annotate your test class with
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
, then your method
source can be non-static, provided it resides in the same class as
your test (i.e. not in an external class).
Having your test method non-static might seem like an insignificant
change. But it does allow you to create significantly more complex
parameter methods. For example, if you are using @SpringBootTest
, then
you have access to any beans that you might inject into the fields
of your test. The Lifecycle.PER_CLASS
annotation does however mean
that your test class is being reused in between test methods, meaning
that you might get side effects from one class leaking into
another test.
You might say that having complex parameters methods is an anti-pattern and indeed, I would agree with you on most occasions. I don’t recommend having a complex setup in the parameters method. But it’s a useful tool that I sometimes have found myself needing.
Here is an example that showcases the functionality:
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class NonStaticParametersTest {
@Autowired
private MyService myService;
private int counter = 0;
@BeforeAll
static void beforeAll() {
System.out.println("Before all");
}
@BeforeEach
void beforeEach() {
counter++;
System.out.printf("Before each (counter=%d)%n", counter);
}
@MethodSource("parameters")
@ParameterizedTest
void test(int a, int b, int expectedResult) {
System.out.printf("Test %d+%d=%d%n", a, b, expectedResult);
assertEquals(expectedResult, a + b);
}
private List<Arguments> parameters() {
System.out.printf("Parameters (myService=%s)%n", myService);
return List.of(
Arguments.of(1, 2, 3),
Arguments.of(2, 2, 4)
);
}
@Test
void anotherTest() {
System.out.println("Another test");
}
}
The output of running this test is the following:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
... rest of Spring output omitted for brevity
Before all
Before each (counter=1)
Another test
Parameters (myService=se.plilja.junit_playground.MyService@747f6c5a)
Before each (counter=2)
Test 1+2=3
Before each (counter=3)
Test 2+2=4
As you can see from the output, you have access to the bean MyService
from the
parameters method. Also worth noting is that the counter variable isn’t reset
between tests as you would normally expect in a JUnit test.