Featured Webinar: Simplify Compliance Workflows With New C/C++test 2024.2 & AI-Driven Automation Watch Now
Jump to Section
JUnit Tutorial: Setting Up, Writing, and Running Java Unit Tests
Understand the basics and scale your unit testing practice like a pro with this tutorial.
Jump to Section
Jump to Section
Do you want to skip the basics and see how to automate unit test generation enhanced with AI to go from 0 to 60%+ code coverage in <5 minutes? Check out Jtest >>
What Is Unit Testing?
Let’s first talk a little bit about what unit testing is and why it matters in general.
Unit testing is a form of white box testing in which test cases are based on internal structure. The tester chooses inputs to exercise particular paths through the code and configures assertions that validate the output. The purpose of unit testing is to examine the individual components or pieces of methods/classes to verify functionality, ensuring the behavior is as expected.
The exact scope of a “unit” is often left to interpretation, but a nice rule of thumb is to define a unit as the least amount of code needed to perform a standalone task, for example, a single method or class.
There’s a good reason why we limit scope when unit testing: if we construct a test that validates a broad portion of a project, then we shift focus from the functionality of a single method to interactions between different portions of the code. If the test fails, we don’t know why it failed. We’re left wondering whether the point of failure was within the method we were interested in or within the dependencies associated with that method.
JUnit Basics: Getting Started With JUnit and Using Automation to Create Unit Tests
What Is JUnit?
JUnit is the most popular Java unit testing framework. An open-source framework, it’s used to write and run repeatable automated tests.
As with anything else, the JUnit testing framework has evolved over time. JUnit 4.0 was released in 2006, and 5.0 was released in 2017. At the time of this writing, the latest version is 5.9.1. JUnit 5.x has addressed many of the earlier limitations of JUnit, and it has become the most robust Java unit testing framework.
This blog post will cover the usage of both JUnit 4 and 5.
How to Set Up JUnit Testing
So let’s dive right in. Here are the steps to set up JUnit.
The more common IDEs, such as Eclipse and IntelliJ, will already have JUnit testing integration installed by default. If you’re not using an IDE and perhaps relying solely on a build system such as Maven or Gradle, the installation of JUnit 4/5 is handled via the pom.xml or build.gradle file, respectively.
It’s important to note that JUnit 5 was split into 3 modules, one of those being a vintage module that supports annotation/syntax of JUnit 4 and 3. JUnit 3 usage is very low at this point and is usually seen only in much older projects.
JUnit 5
Because of the modular fashion of JUnit 5, a BOM is used to import all JUnit modules and dependencies. If only specific modules are needed, individual groups or artifacts can be specified instead.
To add JUnit 5 to Maven, add the following to pom.xml:
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
For Gradle, add the following to the build.gradle file:
apply plugin: 'java'
dependencies {
implementation 'org.junit:junit-bom:5.9.1'
}
JUnit 4
To add JUnit 4 to Maven, add the following to pom.xml.
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
For Gradle, add the following to the build.gradle:
apply
plugin
: 'java'
dependencies {
testCompile 'junit:junit:4.13'
}
If you need to manually add JUnit to the classpath for JUnit testing, you need to reference the raw jar file(s) directly, although this isn’t usually required. JUnit 4 has the jar available to download directly. Junit 5 (as I’m writing this) doesn’t currently have the jar file prebuilt, but you can easily compile the code and generate the jars. JUnit 4 has the jar available to download directly.
Improve Unit Testing for Java With Automation: Best Practices for Java Developers
Writing Unit Tests: The Anatomy of a JUnit
Now that we’ve talked a little about JUnit setup, let’s move on to the actual construction and execution of these tests. To best illustrate the creation of JUnits, we’ll start with something basic. In the JUnit test example below, we have a simple method (left) that converts Fahrenheit to Celsius and a JUnit test (right) written to test our method. I’ve numbered the key parts of the JUnit test and will discuss each part in detail below.
Parts 1 and 2
These are imports for the JUnit classes and packages used by the unit test. The imports can be specified as individual classes but are commonly specified as entire packages using asterisks. Either way works—the level of granularity of the imports is a matter of preference.
Part 3
This defines the start of our test class. The important thing to take note of here is the naming convention used for the class, which is ClassNameTest. This naming convention is not required, but it’s the most common way to name JUnit classes because the name succinctly describes the purpose of the unit test class and which class it is testing.
Part 4
Here, we see our first JUnit-specific syntax: the @Test annotation. Annotations are extremely important when creating JUnits. This is how the JUnit framework identifies the important parts of the unit test. In our example, the @Test annotation tells JUnit that the public void method to which it is attached can be run as a test case.
There are many other annotations, but some of the most common are the following.
- @Before identifies a method that should be run before each test method in the class. It’s typically used to update or reset the state needed for the test methods to run properly.
- @After identifies a method that should be run after each test method in the class. It can be used to reset variables, delete temporary files, and so on.
- @Ignore specifies that a test method should not be run.
- @BeforeClass identifies a method that should be run once before any test methods are run.
- @AfterClass identifies a method that should be run once after all test methods are run.
Part 5
Again, note the naming convention testMethodName, where methodName is the name of the method being tested in the class under test.
Part 6
In the Given section of the test, we construct a new instance of the class under test and initialize it as appropriate. This is necessary since the test method needs to call the method under test to test it. In our example, no other initialization is needed beyond instantiating the class, but in many cases, additional setup may need to happen, such as initializing objects to pass into the constructor or calling methods that configure the state of the class under test.
Part 7
The When section of the test includes initializing variables that need to be passed when calling the method being tested and then calling the test method (part 8). The variables should be given meaningful values that cause the test to exercise the parts of the test method that we care about. Note that if a variable is an object, it can be instantiated or mocked.
Part 8
If the method under test returns a value, it should be captured in a variable so that its value can be asserted on.
Part 9
Unit tests are only valuable if they include assertions that validate that the method being tested returns the right value and/or adjusts the state of other objects as expected. Without assertions, you have no verification, and your test is at best a smoke test that gives feedback only when an exception is thrown.
The JUnit assertion methods, which are included in the org.junit.jupiter.api.Assertions class in JUnit 5 and the org.junit.Assert class in JUnit 4, are commonly used to determine the pass/fail status of test cases. Only failed assertions are reported by the JUnit framework. Like with annotations, there are many assertion options.
In our example JUnit above, we use the assertEquals (expected, actual, delta) method. The first argument is:
- The expected outcome, which the test author defines.
- The actual output, which is the return value of the method being called
- The delta, which allows for an acceptable deviation between the expected and actual values. This delta is specific to the fact that we’re validating the value of a double type.
See Parasoft Jtest in action!
How to Run a JUnit
Choose your own adventure! Here, we will look at three ways to run JUnits: straight from the command line, from the IDE (Eclipse and IntelliJ), and using build systems (Maven and Gradle).
How to Run a JUnit From the Command Line
To run a JUnit directly from the command line, you need a few things:
- JDK on your path
- The JUnit jar file(s)
- The test cases
The command is as follows. This example is for JUnit 4.
java -cp /path/to/junit.jar org.junit.runner.JUnitCore <test class name>
Note: Running a test from the command line is most commonly done from a CI/CD process running in a build system like Jenkins or Azure DevOps.
How to Run a JUnit From the IDE
Eclipse
Within the Package Explorer, locate your JUnit test. Right-click and select Run As > JUnit Test. This will execute your test and report results within the JUnit Eclipse view.
IntelliJ
Running a test in IntelliJ is similar to Eclipse. From the Project window, locate your test, right-click and select Run ‘testName’. Like Eclipse, a JUnit window will open with the results of the test.
How to Run a JUnit Using Build Systems
Maven
Maven has made running unit tests simple. Ensure you are in the proper location from your command line, and the project pom.xml is properly configured. Then you can run the following to execute your JUnits.
To run the entire test suite:
mvn test
To run a single/specific test(s):
mvn -Dtest=TestName test
Gradle
Gradle, like maven, has made running tests simple.
To run the entire test suite:
gradlew test
To run a single/specific test(s):
gradlew -Dtest.single=testName test
Note: Maven and Gradle are their own beasts. What is shown here is minimal to cover the basics. Check out their documentation if you want to learn more.
Continuing With Unit Testing
Our example ran through a simple unit test, and of course, this is just the start of unit testing. More complex methods that need to be tested may call methods in dependent classes, or connect to external systems like a database. In these kinds of cases, it may be desirable to isolate the code through mocking.
Mocking helps to isolate units of code so that our unit tests can focus only on the specific class/method being tested. The most common framework used for mocking in JUnit tests is Mockito.
To learn more about mocking, read my colleague’s post: How to automate a Java unit test, including mocking and assertions.
So, if unit testing is so important, why doesn’t everyone do it consistently? Well, unit testing isn’t easy to implement. It also takes a lot of development skills. Developers tend not to enjoy unit testing very much. It takes commitment and time to maintain test suites.
But the benefits of unit testing are clear.
It’s necessary to ensure that previously written code continues to function as the code evolves and new functionality is added to the application. Unit tests generally run fast and are effective at catching regressions introduced into the code. Unit testing is a general requirement to ensure that applications continue to function as intended. It’s a quick way to get feedback on code changes before integrating them into the application.
So, it’s helpful to deploy powerful unit testing tools like Parasoft Jtest that can remedy many of the challenges associated with JUnit testing and save developers valuable time.
Parasoft Jtest’s AI-powered Unit Test Assistant allows users starting at 0% code coverage to quickly generate test suites that cover 60% or more of their application code with a single action. When test creation hits the limits of what can be fully automated, the Unit Test Assistant offers workflows and recommendations that help users manually create tests more efficiently.
Additionally, the test impact analysis module speeds up unit test execution time within CI/CD processes or the IDE by identifying and running only the subset of the unit test suite that’s needed to validate recent code changes.