Hey, Tea Lovers! This post is the start of a new series of posts on TDD development in Java. This will be a quick introduction to JUnit 5. It won’t be about JUnit 4 vs. JUnit 5 instead, we will be focusing entirely on JUnit 5 and how as a beginner, you can start working on it right off the bat. Let’s jump right into it.
I will keep on adding more posts on the topic such as Best practices, tips, and tricks, advanced testing, and many more. So please subscribe to our newsletter for new updates.
I would be happy to connect with you guys on social media. It’s @coderstea on Twitter, Linkedin, Facebook, Instagram, and YouTube.
Please Subscribe to the newsletter to know about the latest posts from CodersTea.
Why Testing or TDD
To keep up with the demand for continuous delivery and integration, we need to make sure that this continuity isn’t hampering the quality of the product. We should thoroughly test the changes. Not only the new ones but make sure that the old ones are not broken. Using TDD or Test Driven Development we can reduce the chances of forgetting to test or breaking any parts of the code, or even more bring down the whole product.
Java JUnit 5 has brought a lot of new changes to help us do TDD Development. Let’s look at them one by one.
Requirement for TDD using JUnit 5
First of all. I will be using a Maven project. For Java, I will use Java 8 but it works with any newer java versions. I will create another one for Java 16 test using record. In the project, I will add JUnit 5 dependency.
But to understand what’s happening, you must be familiar with Maven (basic), Java, and Java annotations. If you have worked with any testing framework that would be a bonus.
What is JUnit 5 and How is Structured
JUnit 5 is a significant update of JUnit 5. Unlike previous versions, JUnit 5 is a multi-module framework. It consists of the platform, engine, and vintage Junit modules.
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform sets up the JVM to run the test engines. With this, it gets flexible to create your testing framework or add 3rd party plugins. JUnit Jupiter provides the annotations for testing. We will look at the common one in a bit. The last one, JUnit Vintage, is to run older versions of JUnit, 3 and 4, on the JUnit 5 platform.
Install JUnit 5 in Java Project
To add JUnit 5 to the project, add the following JUnit 5 dependency in the build file. I have put both maven and Gradle dependencies for version 5.8.0-M1.
Maven project
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.0-M1</version>
<scope>test</scope>
</dependency>
Code language: HTML, XML (xml)
You have to name your test classes with the following naming conventions, otherwise, it won’t get picked up by maven.
Test*
*Test
*Tests
(has been added in Maven Surefire Plugin 2.20)*TestCase
You can also add the Maven Surefire plugin to customize which test classes to pick. It will go under the build
> pluginManagement
> plugins
tag.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
Code language: HTML, XML (xml)
Your first Test Using JUnit 5
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class JUnit5ExampleTest {
@Test
void yourFirstTest(){
int a = 5;
int b = 10;
assertEquals(15, a + b);
}
}
Code language: Java (java)
There are multiple things in this small block. I will explain it to you in brief. First, the class JUnit5ExampleTest
is in the test folder of the project i.e. src/java/test
. Simple class, nothing fancy here except no need for public
keywords for class. The second thing is @Test
an annotation on the yourFirstTest
method. This tells JUnit that this function is to be tested. The third one is the assertEquals. This is a static function from Assertion
the class of JUnit 5. It simply says that 15 should be the output when a + b
it is executed, Expected and Actual value respectively.
@Test
As shown in the above example, @Test annotation is used to mark the method as testable. These methods will be picked for testing by JUnit.Nothing fancy, similar to older versions.
@Test
void thisTestWillRun(){
assertEquals(15, 12 + 3);
}
void thisWillNotRun(){
assertEquals(15, 12 + 3);
}
Code language: Java (java)
Assertions
The most important part of any testing framework is the assertion. This simply stops the execution of the program in case the given output is not what we expected. This helps us to identify the problem with the code. JUnit 5 assertion methods are static and are part of org.junit.jupiter.api.Assertions
the class. It has overloaded methods assertEquals
with all the primitive and Objects as parameters for expected and actual parameters.
The new thing in JUnit 5 is the added support for Lambdas. This helps lazily initialize the values, increasing the overall time.
@Test
void assertUsingLambda(){
assertTrue( 5 > 1, () -> "This message will be lazily loaded");
}
Code language: Java (java)
@BeforeEach and @BeforeAll
Most of the time you need to set up a few things before running the test, such as creating the testing class’s object. @BeforeAll and @BeforeEach help you to do exactly that. The function annotated with this is executed first. There is a difference between @BeforeAll and @BeforeEach. @BeforeAll will be a static function that will run when the test class is loaded, and it will run exactly once before all the test cases. On the other hand, @BeforeEach will be executed before each test case. @BeforeAll can be set up to create objects which will be required throughout the tests. @BeforeEach can be used for updating something or resetting an object or creating a new one at each iteration.
@BeforeAll
static void itWillRunBeforeAllTests(){
System.out.println("This will run exactly once before all test cases");
}
@BeforeEach
void itWillRunBeforeEachTests(){
System.out.println("This will run before each test cases");
}
Code language: Java (java)
@AfterAll and @AfterEach
These annotations are the opposite of @Before* annotations. As the name suggests, @AfterAll runs after all the test cases are complete. @AfterEach is executed after each test case is complete.
@AfterAll
static void itWillRunAfterAllTests() {
System.out.println("This will run exactly once after all test cases");
}
@AfterEach
void itWillRunAfterEachTests() {
System.out.println("This will run after each test cases");
}
Code language: Java (java)
Again, please note that @AfterAll and @BeforeAll annotated methods must be static.
@DisplayName: JUnit 5 Annotation
When testing a method, you have to name the testing method in such a way that it tells you what you are testing. It’s considered one of the best practices when writing tests. Generally, it looks something like divideTest_WhenDividerIs0_ShouldReturn1
. This name gives a lot of information about the test we are about to see or write. It’s a test divide()
and when we pass 0 as a divider it must return 0, which is the condition being tested.
But Most of the time name keeps getting bigger. So the solution is to use the @DisplayName annotation. With this, you can write about the test, which can contain spaces, special characters, and even emojis. This text will be shown in the console whenever tests are executed.
@Test
@DisplayName("Test Divide ✨🎠. It should return 1 if we pass 0 as divider.")
void divideTest_WhenDividerIs0_ShouldReturn1() {
assertEquals(1, divide(11, 0));
assertEquals(2, divide(16, 8));
}
int divide(int number, int divider) {
if (divider == 0) return 1;
return number / divider;
}
Code language: Java (java)
As you can see I only added spaces but also emojis. To get the most out of test cases’ names, you first have to name the actual methods in a better way. You can use the best practices described in the post. Java Naming Conventions: What’s In a Name?
Assumtions in JUnit 5
Sometimes, before running tests you need to ensure certain conditions are fulfilled. It can be anything that your test required. It can be an object not being empty, or some value is set among others. In these scenarios, you don’t want to execute tests as you know those tests will fail since the requirement is not fulfilled.
With Assumptions in JUnit 5, you can tell JUnit to first check for these conditions and then only execute further. It could be done with, methods of Assumptions
class, assumeTrue()
, assumeFalse()
, and assumingThat()
for true, false, and lambda respectively. If this condition fails an error will be thrown and tests are aborted.
For example, suppose you don’t want to run the test unless you have a system environment variable available named CODERSTEA
whose value is the coderstea.in
. You can do so with the following.
@Test
void testEnvVariable(){
System.out.println("Checking if CODERSTEA variable is set in system environment");
assumeTrue(System.getenv().containsKey("CODERSTEA"),
"You must set CODERSTEA in your system env variable.");
System.out.println("This won't run until you set CODERSTEA in you system");
}<gwmw style="display:none;"></gwmw>
Code language: Java (java)
Conclusion for JUnit 5
I hope this will help you get started with JUnit 5. As I said this is just the beginning of the TDD development series that I will be writing on. Please be updated about them via Social Media or subscribe to our newsletter. The code for this tutorial is available on GitHub.
See you in the next post. HAKUNA MATATA!!!
I would be happy to connect with you guys on social media. It’s @coderstea on Twitter, Linkedin, Facebook, Instagram, and YouTube.
Please Subscribe to the newsletter to know about the latest posts from CodersTea.