DEV Community

Cover image for Why You Should Start Using JUnit 5
Jan Wedel
Jan Wedel

Posted on • Updated on

Why You Should Start Using JUnit 5

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
        }
    }
Enter fullscreen mode Exit fullscreen mode

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
        }
    }
Enter fullscreen mode Exit fullscreen mode

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
        }
    }
Enter fullscreen mode Exit fullscreen mode

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
        }
Enter fullscreen mode Exit fullscreen mode

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
        }
    }
Enter fullscreen mode Exit fullscreen mode

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

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
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode

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());
        }
    }
Enter fullscreen mode Exit fullscreen mode

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 imports, 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)

Collapse
 
lukefx profile image
Luca Simone

Why they added a @DisplayName and didn't add a param to @test like @test (name = "it should not fails")?

Collapse
 
stealthmusic profile image
Jan Wedel

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:

  • There are other "things" that support @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.
  • If JUnit would have a concept like @AliasFor meta annotations in spring, Β΄this would still be possible (but it doesn't)
  • You could write your own custom composed annotation
Collapse
 
firuzzz profile image
Jose Luis Romero

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 ;)

Collapse
 
stealthmusic profile image
Jan Wedel

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.

Collapse
 
vga profile image
Victor Gallet

SpringExtension is available since Spring 5 not Spring boot 2. This class can be found in spring-test dependency.

Collapse
 
stealthmusic profile image
Jan Wedel

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.

Collapse
 
pdrmdrs profile image
Pedro Paulo Paiva de Medeiros

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.

Collapse
 
yhippa profile image
Richard Yhip

What does not making methods and classes public buy us? Better readability?

Collapse
 
stealthmusic profile image
Jan Wedel

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.