This is a cross-posting of an article published on my blog which you can find here.
What is JUnit 5?
According to its website,
JUnit 5 is the next generation of JUnit. The goal is to create an up-to-date foundation for developer-side testing on the JVM. This includes focusing on Java 8 and above, as well as enabling many different styles of testing.
This article is for everyone that did or did not hear about JUnit 5 and wonders why they would want to use it.
I don't want to go into details on all the features or how it works internally, I just want to show some of the new features that are most valuable to me so far.
So let's get right into it...
Display Name
Previously in JUnit, tests got their names from their test methods.
E.g.:
public class AccountTest {
@Test
public void testMethod() {
// do some tests here
}
}
When you run it, it will print out testMethod
as the name for the test which is problably not very useful and most importantly not readable. Test shoud be human readable because tests are the only true code documentation that does not get deprecated. So people started trying to come up with some more descriptive test names.
public class AccountTest {
@Test
public void withdrawSomeAmountGreaterThanCurrentBalanceFailsWithException() {
// do some tests here
}
}
Better? Not so much. At least it gives you some idea on what is going to happen here. Something about withdrawing some amount from an account that is greater than the current balance. That should fail. But how to make it more readable? Some developers (including myself) started breaking with the strict Java convention of camelCase and started using underlines to separate aspects of the test, for instance method__scenario__expectedResult
.
public class AccountTest {
@Test
public void withdraw__amountGreaterThanCurrentBalance__failsWithException() {
// do some tests here
}
}
This is a bit more readable although it hurts the eyes of every Java developer that is used to camelCase. What you actually want is using any arbitrary string as the test name.
Kotlin allow you to use string literals like e.g.
internal class AccountTest {
@Test
fun `should thrown an exception when an amount is withdrawn greater that the current balance`() {
// do some tests here
}
JUnit 5 to the rescue. Although not as simple as in Kotlin, JUnit 5 comes with an @DisplayName
annotation that can be used for both test classes and methods. The downside is, you still need to make a method name:
@DisplayName("An account")
class AccountTest {
@Test
@DisplayName("should throw an exception when an amount is withdrawn greater that the current balance")
void withdrawBalanceExceeded() {
// do some tests here
}
}
So although its the typical Java annotation cluttering going on, the result (as in what the IDE shows) is much more readable.
Note that you do not need to use public
anymore with methods and classes.
Nested Tests
While pair-programming with a colleague, I was copy and pasting tests and descriptions and just change parts of it to tests different edge cases. She told me I should use nested tests. We tried it and I immediately fell in love with the idea.
Instead of having test like this:
- An account
- should throw an exception when an amount is withdrawn greater that the current balance
- should reduce the balance by the amount that is withdrawn
You could do something like that
- An account
- withdrawal
- should throw an exception when the amount greater that the current balance
- should reduce the balance by the amount
- withdrawal
With JUnit 5, it looks like this:
@DisplayName("An account")
public class AccountTest {
@Nested
@DisplayName("withdrawal")
class Withdrawal {
@Test
@DisplayName("should throw an exception when the amount greater that the current balance")
public void failureBalanceExceeded() {
// do some tests here
}
@Test
@DisplayName("should reduce the balance by the amount")
public void success() {
// do some tests here
}
}
}
Again, the gross number of lines increase by using @Nested
as it is necessary to wrap tests within a nested class but it keeps the tests its self cleaner.
Extensions
JUnit 5 is built to be very extensible. Custom or third-party extensions can be added by using the @ExtendWith
annotation at class level.
Spring Boot
Spring boot integration tests for example are now run by using @ExtendWith(SpringExtension.class)
. Note that this only works from spring-boot 2.x and higher.
Mockito Extension
There will be an updated MockitoExtension
that could do something like that:
@ExtendWith(MockitoExtension.class)
class MyMockitoTest {
@BeforeEach
void init(@Mock Person person) {
when(person.getName()).thenReturn("Dilbert");
}
@Test
void simpleTestWithInjectedMock(@Mock Person person) {
assertEquals("Dilbert", person.getName());
}
}
So you do neither need to write Person person = mock(Person.class)
or use @Mock private Person person
.
Migration from 4 to 5
Those new features in JUnit 5 come at a price: They are not compatible with JUnit 4 tests. This is called JUnit Jupiter.
"But wait, what about my existing 500 test cases suite?" you might ask. It not as bad as you might think. The JUnit team did a smart thing, they moved all JUnit Jupiter code and annotations in a separate package so can have both JUnit 4 and 5 in the same code base. You would still need to add the new JUnit platform, the JUnit 4 tests are called JUnit Vintage. There is a nice article about the migration you can find here.
We actually have a project with hundreds of tests and we step-by-step migrate them. All new tests are written with JUnit 5 and once we need to touch existing tests, we migrate them. It basically delete all JUnit import
s, and replace @Before
by @BeforeEach
as well as @BeforeClass
by @BeforeAll
.
Conclusion
Although Java itself could be improved a lot by e.g. introducing meta programming (which has its disadvantages, too) or at least allow string literals as method names to make tests even more readable, but JUnit 5 is way better than JUnit 4 and I would assume that there will be a lot of extensions to come in the future.
Moreover, there are a lot of improvements to JUnits assertion library but since I use AssertJ, I didn't cover that part. See the JUnit 5 documentation for reference.
Top comments (12)
Why they added a @DisplayName and didn't add a param to @test like @test (name = "it should not fails")?
Ha! That's a very good question that I just recently asked Sam Brannon, one of the core committers of JUnit 5. :)
His answer goes as follows:
@DisplayName
like test class (as shown in my example),@TestFactory
,@RepeatedTest
,@ParameterizedTest
and@TestTemplate
. Having@DisplayName
orthogonal to those features is just about separating concern.@AliasFor
meta annotations in spring, ´this would still be possible (but it doesn't)An intuitive method/function name is vital in every language, if it's not enough we got documentation and if we got access to source, inner comments too. DisplayName is the excuse to avoid all previous
Nested your example just add more pollution, it is not a good one
PD: I like tests ;)
Honestly, I don't really understand your point.
Sure, functional readable method names are important. But why would anyone stop doing that in favor of using
@DisplayName
? Is this just your hypothesis or did you actually see that?Testing is not about how the code works but what it should do. This you won't get from looking at the code. Tests are the functional specification written in your language of choice. Having
@DisplayName
just makes this specification more readable.As I pointed out already,
@Nested
is more "pollution", however when you execute the tests and you see them properly grouped and named in the IDE, it is very helpful.SpringExtension
is available since Spring 5 not Spring boot 2. This class can be found inspring-test
dependency.Yep, you are right.
I was looking at this from a spring-boot perspective (maybe a bit narrow-minded) where you don't select the spring framework or testing version anymore. Spring 5 will be used by boot 2 automatically.
Amazing. I really like the idea of giving names to the tests. The tests that I did always ended up as you said, with bigger names.
What does not making methods and classes public buy us? Better readability?
First, it's not necessary but your IDE will probably warn you (e.g. IntelliJ does it). Second, yes it's just less boilerplate. But I intentionally did not try to communicate that a must-have feature.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.