JUnit is a unit testing framework for Java and is one of the most popular frameworks used by organizations. After we’ve been working with the 4.12 JUnit version for a while, a new and exciting version is out – JUnit 5. In this post, we will learn what JUnit framework is and which cool novelties the new version brings along.
So, what is JUnit? And why should we all want to work with it?
With JUnit we have the ability to run the tests (the code) automatically (It’s basically the automation of automation!). With JUnit we have these capabilities:
- Annotations
- Assertions
- Suite Executions
- And more!
JUnit 5 Configuration
In order to configure JUnit, you need to use a Build Management Tool such as Maven. Here are the dependencies you need to add to your project to work with JUnit 5:
- junit-jupiter-api – Exposes the JUnit API.
- junit-jupiter-engine – A dependency that defines the JUnit execution engine.
- junit-vintage-engine – In case we’d like to continue working with previous JUnit versions (JUnit 3 or 4), we’ll need to work with this dependency.
- junit-platform-launcher – Exposes the API to the configuration that the IDE usually uses.
- junit-platform-runner – Enables to run tests and packages written in JUnit 4.
Annotations
Annotations start with the @ character and then the annotation’s name. This way we can write our classes in a testable way and define executable functions that can run by JUnit execution engine (that is built on top of Java). In JUnit 5 we have some new annotations. Here’s a summary of all the JUnit 5 supported annotations:
Those of you who are familiar with JUnit and are used to working with JUnit 4, will probably notice that there are some new annotations (such as: @nested) and that the old annotations now have different names (such as: @BeforeEach). Below is a code example that demonstrates working with these JUnit 5 annotations:
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; public class JUnit5Sample { @BeforeAll static void beforeAll() { System.out.println("Executed once before all test methods in this class"); } @BeforeEach void beforeEach() { System.out.println("Executed before each test method in this class"); } @Test void testMethod1() { System.out.println("Test method1 executed"); } @Test void testMethod2() { System.out.println("Test method2 executed"); } @AfterEach void afterEach() { System.out.println("Executed after each test method in this class"); } @AfterAll static void afterAll() { System.out.println("Executed once after all test methods in this class"); } }
Now we can play with other annotations, such as DisplayName:
@Test @DisplayName("Hello World") void test01() { System.out.println("Test Hello World is Invoked"); }
The Disabled annotation will not allow executing the test it’s associated to (needed in case we temporarily do not want to execute the test):
@Test @Disabled("Do not run this test") void test01() { System.out.println("Hello World"); }
We can also implement the Disabled annotation on a class and then all the tests under that class will be disabled:
@Disabled public class AppTest { @Test void test01() { System.out.println("Hello World"); } }
The Tag annotation filters certain tests and associates tests to groups, as can be seen in the example below:
@Test @Tag("Sanity") void test01() { System.out.println("Hello World"); } @Test @Tag("Regression") void test02() { System.out.println("Hello World 2"); } @Test @Tag("Sanity") @Tag("Regression") void test03() { System.out.println("Hello World 3"); }
In this example above, test01 is associated with a group of tests called Sanity, and test02 is associated with a group called Regression, whereas test03 is associated with both groups: Sanity and Regression.
The RepeatedTest annotation enables executing the same test case multiple times. In addition, you can send this test case parameters in order for it to pass different values within the same test. For example:
@Test @RepeatedTest(5) void test01() { System.out.println("Hello World"); }
Let’s send the test case parameters:
@Test @DisplayName("My Test Name") @RepeatedTest(value = 5, name = "{displayName} - repetition {currentRepetition} of {totalRepetitions}") void addNumber(TestInfo testInfo) { System.out.println("Hello World"); }
Nested Classes
This is one of the new interesting features JUnit 5 has to offer – The ability to nest one class inside the other in order to create modular tests that will allow creating groups of sub-tests within tests. For instance, when you want to create tests for a sub-menu:
@DisplayName("FatherClass") class JUnit5Sample { @BeforeAll static void beforeAll() { System.out.println("Before all test methods"); } @BeforeEach void beforeEach() { System.out.println("Before each test method"); } @AfterEach void afterEach() { System.out.println("After each test method"); } @AfterAll static void afterAll() { System.out.println("After all test methods"); } @Nested @DisplayName("Child Nested Class") class JUnit5NestedSample { @BeforeEach void beforeEach() { System.out.println("Before each test method of the JUnit5NestedSample class"); } @AfterEach void afterEach() { System.out.println("After each test method of the JUnit5NestedSample class"); } @Test @DisplayName("Example test for method JUnit5NestedSample") void sampleTestForMethod() { System.out.println("Example test for method JUnit5NestedSample"); } } }
Passing Parameters to Tests
You can also pass values into tests, so that this test will be repeated according to the number of parameters you’ve passed:
class MyClass { @ParameterizedTest @ValueSource(strings = {"Hello", "World"}) void test01(String message) { System.out.println(message); } }
In this case, the test will be executed twice. The first time it will print the word ‘Hello’ and the second time it will print the word ‘World’.
Of course, we can also pass the test a structure of parameters like a CSV structure, as seen in the example below:
@CsvSource({"1, 1, 2", "2, 3, 5"}) void sum(int a, int b, int sum) { assertEquals(sum, a + b); }
Or even pass it an external CSV file:
@CsvSource(resources = "C:/Data/values.csv") void sum(int a, int b, int sum) { assertEquals(sum, a + b); }
Assertions
Assertions are functions that allow us to check data during our tests. JUnit 5 supports all types of assertions that exist in version 4. In fact, in JUnit5 we can work with 3 types of assertions:
1. The Assertions of JUnit5 API, for example:
assertEquals(EXPECTED, ACTUAL); assertNotEquals(EXPECTED, ACTUAL); assertNull(null); assertNotNull(new Object()); assertTrue(true); assertFalse(false);
2. The assertions of Hamcrest, for example:
assertThat(true, is(true)); assertThat(false, is(false)); assertThat(ACTUAL, is(EXPECTED)); assertThat(ACTUAL, not(EXPECTED));
3. The assertions of AssertJ, for example:
assertThat(true).isTrue(); assertThat(false).isFalse(); assertThat(ACTUAL).isEqualByComparingTo(EXPECTED); assertThat(ACTUAL).isNotEqualByComparingTo(EXPECTED);
Executing a Test Suite
With JUnit 5 we can also execute test suites by defining them in a separate class, for instance by choosing different packages:
@RunWith(JUnitPlatform.class) @SelectPackages({"packageAAA","packageBBB"}) public class JUnit5SuiteExample { }
or even choosing specific classes for execution:
@RunWith(JUnitPlatform.class) @SelectClasses( { MyTestsA.class, MyTestsB.class, MyTestsC.class } ) public class JUnit5SuiteExample { }
You can filter the execution according to packages:
@RunWith(JUnitPlatform.class) @SelectPackages("MyPackages") @IncludePackages("MyPackages.packageA") // Include Package public class JUnit5SuiteExample { }
@RunWith(JUnitPlatform.class) @SelectPackages("MyPackages") @ExcludePackages("MyPackages.packageA") // Exclude Package public class JUnit5SuiteExample { }
You can also filter the execution according to classes:
@RunWith(JUnitPlatform.class) @SelectPackages("MyClasses") @IncludeClassName("MyClasses.classA") // Include Class public class JUnit5SuiteExample { }
@RunWith(JUnitPlatform.class) @SelectPackages("MyClasses") @ExcludeClassName("MyClasses.ClassA") // Exclude Class public class JUnit5SuiteExample { }
And also filter according to tags (by using the Tag annotation we talked about above):
@RunWith(JUnitPlatform.class) @SelectPackages("MyPackages") @IncludeTags("Sanity") // Include Tags public class JUnit5SuiteExample { }
@RunWith(JUnitPlatform.class) @SelectPackages("MyPackages") @ExcludeTags("Sanity") // Exclude Tags public class JUnit5SuiteExample { }
Are you already working with JUnit 5? Share your experience in the comments below 😎
i am trying to create a test suite for Unit test of android application, but it always fails with different kinds of configuration error, one among that is as follows,
Error: Test events were not received android
FAILURE: Build failed with an exception.
What went wrong:
Execution failed for task ‘:appModules:factList:testDebugUnitTest’.
No tests found for given includes: reprator.wipro.factlist.FactListTestSuite
RepoDetails: GitHub – TheReprator/Wipro at junit5
Branch: junit5
TestSuite Class: Wipro/FactListTestSuite.kt at junit5 · TheReprator/Wipro · GitHub
My whole code works perfectly with junit4 in master branch.
Stackoverflow: https://stackoverflow.com/questions/68750803/junit5-testsuite-with-selectclasses-not-working
Please assist.