Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Embrace JUnit5

DZone's Guide to

Embrace JUnit5

Have you been waiting for JUnit5? See what it offers over its older version and how you can use it to start testing your Java projects.

· Java Zone
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

JUnit5 is the upgraded version of the JUnit Testing Framework and it is also referred as JUnit5 — The Next Gen Tool. This tool was developed from scratch and written completely in Java 8. 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. JUnit5 is the result of JUnit Lambda and its crowdfunding campaign on Indiegogo.

WHY JUnit5

  • JUnit4 was created almost a decade ago and in this span, technology has evolved immensely — and testing activities have also matured. Software engineers and testers demanded an extensible testing framework.
  • JUnit4 was very monolithic. Apart from including the hamcrest JAR (a library to assist software tests using the Java language), JUnit4 is itself one bulky JAR (junitxx.jar), which was used by both developers, testers, IDEs, and build tools (Maven/Gradle). Hence, it was difficult to maintain or extend it.
  • JUnit4 is not modular (there is a single junit.jar dependency for everything). Test discovery and execution were tightly coupled in JUnit4.
  • Using multiple runners/rules within a single test class like Runners were not composable. This is required when we are working with Spring Framework using a third-party Test engine like TestNG or Mockito. And also, JUnit4 does not allow single Rule (@Rule, @ClassRule) at the method level and class level.
  • All tests were done at compile time. There was no way to test dynamic values.

Enter JUnit5

  • JUnit5 is written completely in Java 8 from scratch and uses newer features — and is compatible with Java 9.
  • JUnit5 comes with a BDD approach and uses lambda programming. In fact, it was called JUnit Lambda previously.
  • JUnit5 comes with various new features and a next-generation architecture, where it is modular, extensible, and has seamless integration with other frameworks like Spring or Mockito.
  • Backward compatibility with the JUnit4 and JUnit3 frameworks. Previously written test classes in JUnit4 and JUnit3 can work seamlessly with the JUnit5 test engine.
  • JUnit5 incorporated a functional type of programming.

JUnit5 Architecture

JUnit5 is modular:

junit5_arch1

junit5_arch2

JUnit5 has been re-written to separate two areas of concern:

  1. The TestEngine API for writing tests. {e.g. junit-jupiter-engine, junit-vintage-engine, third-party test engine}
  2. The Launcher API for discovering and running those tests. {e.g. junit-platform-engine,junit-platform-launcher}. The Launcher API provides several components to execute tests from different tools like
    1. Build plugins for Maven and Gradle.
    2. A JUnit4-based runner to execute legacy tests.
    3. A ConsoleLauncher runner to run the platform from the command line.

Junit5 Features

Annotations: JUnit5 has renamed and extended some of the annotations used in JUnit4.There are few new annotations like @Nested that can be put for any inner class in a test class. They can be used to test some boundary cases. And another important annotation is @DisplayName, which we use to give a business name to the test method in a test class.

Assertions: JUnit5 comes with many assertion methods that JUnit4 has and adds a few that lend themselves well to be used with Java 8 lambdas. Other important assertions are assertThrows, assertTimeout, assertTimeoutPreemptively.assertThat, etc.

Assumptions: Assumptions are similar to assertions, except that assumptions must hold true or the test will be aborted. They are useful when tests should only be executed under certain conditions like assumeTrue, assumeFalse, assumingThat(<predicate>), etc.

Extensions:An extension point corresponds to a predefined point in the JUnit test lifecycle. Thus, the extension point is the callback interface, and the extension is the implementation of that interface. One of the important ones is the ParameterResolver Interface.

Parameterized tests:Parameterized tests allow running the same test multiple times, but with different arguments. In order to enable parameterized tests, you need to add the junit-jupiter-params dependency to the classpath.

There are several types of parameter sources:

  • @ValueSource: Defines an array of literals of primitive types and can only provide a single parameter per test invocation.
  • @EnumSource: Uses an Enum as a parameter source.
  • @MethodSource: Uses one or more methods of the test class; the methods must return an array or a Stream, Iterable, or Iterator object, and must be static and have no arguments
  • @CsvSource and @CsvFileSource: Use parameters defined in CSV format, either in String objects or read from a file
  • @ArgumentsSource: Uses a custom ArgumentsProvider.

Dynamic tests: JUnit5 introduces a new type of test called a dynamic test, which is generated at runtime by a special method called a test factory.

  • The @TestFactory method is used to generate dynamic tests. This method must return a Stream, Collection, Iterable, or Iterator of DynamicTest instances.
  • Unlike a @Test method, there are no lifecycle callbacks for DynamicTest instance. So @BeforeEach, @AfterEach, and the other lifecycle callbacks do not apply to a DynamicTest.

RepeatedTest:Using the RepeatedTest annotation, a test method can be repeated for a configurable number of times.

Tags: Tags can be used to filter test discovery and execution. If a specific test class or test method is annotated with specific tags, using Maven or Gradle filters, we can skip or execute those specific test classes or methods in a test class. For example, using includeTags/excludeTags in Maven.

Though we have discussed some of the important new features of JUnit5 and its usages, there are few other aspects of note, like running tests in a TestSuite, which can be studied further once we get more practice with these features.

For more hands-on practice, you can refer to the GitHub account of my organization (ERS-HCL), where I have tried to implement and explain the new features with JUnit5 code.

Important Points

Q1. What is the process of migrating from JUnit4 to JUnit5?

The process of migration is one of the features of JUnit5. The JUnit5 Platform has junit-vintage-engine, where test code written in JUnit4 and JUnit3 can execute seamlessly in parallel with new code written in JUnit5. However, the most important thing is to make sure the project can support Java 8. JUnit5 can only work in the Java 8 runtime and above.

Also, there is no rework needed to already existing test code. The needed changes include:

  • Update the Maven/Gradle dependencies with JUnit5.

  • Update the @RunWith annotation with JUnitPlatForm.class.(Only needed if working in Eclipse/Intellij IDEA IDE)

Q2. What are the Maven/Gradle plugin dependencies for a JUnit5 environment?

JUnit5 contains separate plugins for Maven and Gradle.

  • junit-platform-surefire-provider (for Maven)

  • junit-platform-gradle-plugin (for Gradle)

To run old Junit4 test classes with JUnit5, we need to add the dependencies for junit-platform-surefire-provider and the junit-vintage-engine plugin with the maven–surefire-plugin.

vintage_pomTo execute both new JUnit5 and old JUnit4 test classes together, we need to add the dependency for junit-jupiter-engine.

To execute only new JUnit5 test classes, we need to add the dependency for junit-jupiter-engine and use maven-compiler-plugin to run the test classes.

Add junit-platform-runner to execute tests and test suites on the JUnit Platform in a JUnit 4 environment.

Image title

Q3. How will JUnit5 integrate with existing Spring-based applications?

The @RunWith annotation was used to integrate the test context with other frameworks or to change the overall execution flow in test cases in JUnit 4. With JUnit 5, we can now use the @ExtendWith annotation to provide similar functionality. We can create a new SpringExtension class, which we can use with the @ExtendWith annotation. We can also use @RunWith(JunitPlatform.class)

JUnit5 should be used by projects, as it has provided us with so many advanced features and has an extensible and adaptable architecture by using @ExtendWith annotations. Many more advanced tools today have easily adopted using JUnit5 like Docker, Selenium, and Cucumber, to name a few.

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

Topics:
java ,junit 5 ,unit testing ,java 8 ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}