ARTICLE

Newly-Introduced JUnit 5 Annotations and Classes

From JUnit in Action, Third Edition by Catalin Tudose

The @DisplayName annotation

The @DisplayName annotation can be used over classes and test methods. It helps the engineers at Test It Inc. to declare their own display name for the annotated test class or test method. Typically, it’s used for test reporting in IDEs and build tools. The string argument of the @DisplayName annotation may contain spaces, special characters, and even emojis.

@DisplayName("Test class showing the @DisplayName annotation.")            (1)
class DisplayNameTest {
private SUT systemUnderTest = new SUT();

@Test
@DisplayName("Our system under test says hello.") (2)
void testHello() {
assertEquals("Hello", systemUnderTest.hello());
}

@Test
@DisplayName("
") (3)
void testTalking() {
assertEquals("How are you?", systemUnderTest.talk());
}

@Test
void testBye() {
assertEquals("Bye", systemUnderTest.bye());
}
}
Figure 1 Running the DisplayNameTest in IntelliJ.
  1. It shows the display name applied to the entire class (1).
  2. Then, we see that we may apply a usual text display name (2).
  3. We may also include an emoji (3). The test without an associated display name shows the method name.

The @Disabled annotation

The @Disabled annotation can be used over classes and test methods. It’s used to signal that the annotated test class or test method is currently disabled and shouldn’t be executed. The programmers at Test It Inc. give reasons for disabling a test, llowing the rest of your team to know exactly why this has been done. If it’s applied on a class, it disables all the methods of the test. The disabled tests are also shown differently when each programmer is running them from the IDE, and the disabling reason is displayed into their console.

@Disabled("Feature is still under construction.")                          (1)
class DisabledClassTest {
private SUT systemUnderTest= new SUT("Our system under test");

@Test
void testUsualWork() {
boolean canReceiveUsualWork = systemUnderTest.canReceiveUsualWork();

assertTrue(canReceiveUsualWork);
}

@Test
void testAdditionalWork() {
boolean canReceiveAdditionalWork =
systemUnderTest.canReceiveAdditionalWork();

assertFalse(canReceiveAdditionalWork);
}

}
class DisabledMethodsTest {
private SUT systemUnderTest= new SUT("Our system under test");

@Test
@Disabled (1)
void testUsualWork() {
boolean canReceiveUsualWork = systemUnderTest.canReceiveUsualWork ();

assertTrue(canReceiveUsualWork);
}

@Test
@Disabled("Feature still under construction.") (2)
void testAdditionalWork() {
boolean canReceiveAdditionalWork =
systemUnderTest.canReceiveAdditionalWork ();

assertFalse(canReceiveAdditionalWork);
}
}
  1. The code provides two tests, both of them disabled.
  2. One of the tests is disabled without a given reason (1).
  3. The other test is disabled with a reason that may be quickly understood (2) — the recommended way to work.

Nested tests

An inner class means a class that is a member of another class. It can access any private instance variable of the outer class, as it’s effective part of that outer class. The typical use case is when two classes are tightly coupled, and it’s logical to provide direct access from the inner one to all instance variables of the outer one.

public class NestedTestsTest {                                             (1)
private static final String FIRST_NAME = "John"; (3)
private static final String LAST_NAME = "Smith"; (3)

@Nested (2)
class BuilderTest { (2)
private String MIDDLE_NAME = "Michael";

@Test (4)
void customerBuilder() throws ParseException { (4)

SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("MM-dd-yyyy");
Date customerDate = simpleDateFormat.parse("04-21-2019");

Customer customer = new Customer.Builder( (5)
Gender.MALE, FIRST_NAME, LAST_NAME) (5)
.withMiddleName(MIDDLE_NAME) (5)
.withBecomeCustomer(customerDate) (5)
.build(); (5)

assertAll(() -> { (6)
assertEquals(Gender.MALE, customer.getGender()); (6)
assertEquals(FIRST_NAME, customer.getFirstName()); (6)
assertEquals(LAST_NAME, customer.getLastName()); (6)
assertEquals(MIDDLE_NAME, customer.getMiddleName()); (6)
assertEquals(customerDate, customer.getBecomeCustomer()); (6)
}); (6)
}
}

}

Tagged tests

Tagged tests represent a replacement for the JUnit 4 Categories. The @Tag annotation can be used over classes and test methods. Tags can later be used to filter test discovery and execution. Listing 5 presents the CustomerTest tagged class, which tests the correct creation of the customers from Test It Inc.. A use case may be to group your tests into a few categories, based on the business logic and on those things you are effectively testing. Each tests category has its own particular tag. Then, you decide which tests to run, or alternate between running different categories, depending on the current needs.

@Tag("individual")                                                         (1)
public class CustomerTest {
private String CUSTOMER_NAME = "John Smith";

@Test
void testCustomer() {
Customer customer = new Customer(CUSTOMER_NAME);

assertEquals("John Smith", customer.getName());
}
}
@Tag("repository")                                                         (1)
public class CustomersRepositoryTest {
private String CUSTOMER_NAME = "John Smith";
private CustomersRepository repository = new CustomersRepository();

@Test
void testNonExistence() {
boolean exists = repository.contains("John Smith");

assertFalse(exists);

}

@Test

void testCustomerPersistence() {
repository.persist(new Customer(CUSTOMER_NAME));

assertTrue(repository.contains("John Smith"));

}
}
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20</version>
<!-- (1)
<configuration> (1)
<properties> (1)
<includeTags>individual</includeTags> (1)

<excludeTags>repository</excludeTags> (1)
</properties> (1)
</configuration> (1)
--> (1)
<dependencies>

<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>
</plugin>
Figure 2 Configuring the tagged tests from the IntelliJ IDEA IDE.

Assertions

To perform test validation, you use the assert methods provided by the JUnit Assertions class. As you can see from the previous examples, you have to statically import these methods in your test class. Alternatively, you can import the JUnit Assertions class, depending on your taste for static imports. The following table lists some of the most popular assert methods.

Table 1 JUnit 5 assert methods sample.
class AssertAllTest {
@Test
@DisplayName("SUT should not be under current verification")
void testSystemNotVerified() {
SUT systemUnderTest = new SUT("Our system under test");

assertAll("SUT not under current verification", (1)
() -> assertEquals("Our system under test", (2)
systemUnderTest.getSystemName()), (2)

() -> assertFalse(systemUnderTest.isVerified()) (3)

);
}

@Test

@DisplayName("SUT should be under current verification")
void testSystemUnderVerification() {
SUT systemUnderTest = new SUT("Our system under test");

systemUnderTest.verify();

assertAll("SUT under current verification", (4)
() -> assertEquals("Our system under test", (5)
systemUnderTest.getSystemName()), (5)
() -> assertTrue(systemUnderTest.isVerified()) (6)
);
}
}

@Test
@DisplayName("SUT should be under current verification")
void testSystemUnderVerification() {
systemUnderTest.verify();
assertTrue(systemUnderTest.isVerified(), (1)
() -> "System should be under verification"); (2)
}

@Test
@DisplayName("SUT should not be under current verification")
void testSystemNotUnderVerification() {
assertFalse(systemUnderTest.isVerified(), (3)
() -> "System should not be under verification."); (4)
}

@Test
@DisplayName("SUT should have no current job")
void testNoJob() {
assertNull(systemUnderTest.getCurrentJob(), (5)
() -> "There should be no current job"); (6)
}
  1. A condition is verified with the help of the assertTrue method (1), and in case of failure, a message is lazily created (2).
  2. Then, a condition is verified with the help of the assertFalse method (3), and in case of failure, a message is lazily created (4).
  3. Then, the existence of an object is verified with the help of the assertNull method (5), and in case of failure, a message is lazily created (6).
class AssertTimeoutTest {
private SUT systemUnderTest = new SUT("Our system under test");

@Test
@DisplayName("A job is executed within a timeout")
void testTimeout() throws InterruptedException {
systemUnderTest.addJob(new Job("Job 1"));
assertTimeout(ofMillis(500), () -> systemUnderTest.run(200)); (1)
}

@Test

@DisplayName("A job is executed preemptively within a timeout")
void testTimeoutPreemptively() throws InterruptedException {
systemUnderTest.addJob(new Job("Job 1"));
assertTimeoutPreemptively(ofMillis(500), (2)
() -> systemUnderTest.run(200)); (2)
}

}
class AssertThrowsTest {
private SUT systemUnderTest = new SUT("Our system under test");

@Test
@DisplayName("An exception is expected")
void testExpectedException() {
assertThrows(NoJobException.class, systemUnderTest::run); (1)
}


@Test

@DisplayName("An exception is caught")
void testCatchException() {
Throwable throwable = assertThrows(NoJobException.class,
() -> systemUnderTest.run(1000)); (2)
assertEquals("No jobs on the execution list!",
throwable.getMessage()); (3)
}
}
  1. We verify the condition that the call of the run method on the systemUnderTest object throws a NoJobException (1).
  2. Then, we verify that a call to systemUnderTest.run(1000) throws a NoJobException and we keep a reference to the thrown exception into the throwable variable (2).
  3. Finally, we check the message kept into the throwable exception variable (3).

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Manning Publications

Follow Manning Publications on Medium for free content and exclusive discounts.