Parasoft Logo
Icon for embedded world in white

We're an Embedded Award 2026 Tools nominee and would love your support! Vote for C/C++test CT >>

Parasoft Blog

JUnit Tutorial: Setting Up, Writing, and Running Java Unit Tests

By Nathan Jakubiak May 2, 2025 11 min read
May 2, 2025 | 11 min read
By Nathan Jakubiak
Text on left: JUnit Tutorial: Setting Up, Writing, and Running Java Unit Tests. On the right is a female developer sitting in front of two monitors displaying Java script.

Understand the basics and scale your unit testing practice like a pro with this tutorial.

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 >>

Key Takeaways

  • JUnit is Java’s de facto testing standard. JUnit is the most widely used framework for writing and running automated unit tests, catching bugs early in development.
  • The JUnit framework is mature and continually evolving. JUnit continues to evolve to meet the needs of Java developers, containing many features that support all facets of a robust unit testing practice.
  • Setup is simple. JUnit is preconfigured in popular IDEs like Eclipse and IntelliJ, while build systems like Maven and Gradle can be configured to use JUnit using a few lines of configuration, regardless of version used.
  • JUnit tests follow a structured pattern. The JUnit framework provides a simple API for test definition, while the Given-When-Then format provides useful organization for test code.
  • JUnit tests can be executed anywhere. Tests can be run directly in the IDE for rapid feedback during development or through Maven or Gradle commands, CI/CD pipelines, and the command line for automated execution.
  • Mocking frameworks can be easily integrated. When the code under test depends on external systems or complex objects, mocking frameworks like Mockito can be used to isolate the code by simulating dependencies, enabling focused, reliable unit tests without side effects.
  • AI accelerates JUnit testing at scale. Parasoft Jtest automatically generates test cases, mocks, and assertions directly in the IDE or pull request workflows, helping teams achieve significant code coverage while reducing test creation and maintenance effort by more than 50%.

What Is JUnit Testing?

JUnit is the most popular Java unit testing framework. An open-source framework, it’s used to write and run repeatable automated tests.

JUnit established itself as the de facto standard for Java unit testing through a combination of powerful, well-designed features that work together seamlessly. It was the first unit test framework developed, and it has remained the preferred choice for Java developers worldwide through its distinct characteristics and continued evolution.

What Are the Key Features of JUnit & the Major Differences Between Versions?

As with anything else, the JUnit testing framework has evolved over time.

JUnit 4.0 was released in 2006, 5.0 was released in 2017, and 6.0 was released in September 2025.

As of February 2026, the latest version is 6.0.2.

JUnit 5.x migrated to a modular architecture and addressed many of the earlier limitations of JUnit.

JUnit 6.x is a gentler iteration that keeps the JUnit 5 approach, but it has further modernized and simplified the framework.

JUnit 4 and 5 both still enjoy high usage rates, but the future likely belongs to JUnit 6.

Here are some of the key aspects and features of JUnit along with differences between versions:

Feature/AspectJUnit 4JUnit 5JUnit 6
Original Release Year200620172025
ArchitectureMonolithic architecture with all functionality in a single jarProvides 3 modules: JUnit Platform (foundation for launching tests), JUnit Jupiter (API for writing tests), and JUnit Vintage (runs older tests)All 3 modules now share the same version number
Minimum Java Version5817
Test NamingMethod name serves as the test nameThe @DisplayName annotation allows descriptive, human-readable test namesSame as JUnit 5
Setup and Teardown of TestsProvides annotations to configure state before each test or all tests in a class: @Before, @After, @BeforeClass, @AfterClassProvides different annotations to configure state before each test or all tests in a class: @BeforeEach, @AfterEach, @BeforeAll, @AfterallSame as JUnit 5
AssertionsProvides methods in org.junit.Assert class to verify expected results. If one assertion fails, the rest of them are not run.Provides methods in org.junit.jupiter.api.Assertions class with improved failure messages, including assertAll() to verify multiple assertions at once regardless of whether one of them fails.Same as JUnit 5
Verifying Expected ExceptionsProvides @Test(expected = Exception.class) annotation to verify that assertion was thrown, but can't verify exception message or detailsProvides assertThrows() method that allows detailed assertions on exception message and detailsSame as JUnit 5
Performance Verification and EnforcementProvides @Test(timeout = 1000) annotation to assert that test doesn’t run past the expected time and to prevent infinitely running testsProvides assertTimeout() and assertTimeoutPreemptively() methods to assert performance of specific code blocks and @Timeout annotation for entire testSame as JUnit 5
Parameterized TestsRequires separate runner specified by @RunWith(Parameterized.class) annotation within separate test classFirst-class support via @ParameterizedTest annotation that allows parameterized tests to be mixed with non-parameterized testsUses modern CSV parser for better behavior and performance for @CsvSource and @CsvFileSource annotations
Disabling TestsProvides @Ignore annotation that silently skips testProvides @Disabled annotation for both individual test methods and entire classes that supports reporting a reasonSame as JUnit 5
Nested TestsNot supportedProvides @Nested annotation that enables inner test classes for better organization of related testsUpdated ordering for @Nested classes that are declared in the same enclosing class or interface
Test GroupingProvides @Category annotation to group tests but requires separate runner and category classesProvides @Tag annotation to group tests using a simple label; does not require a separate runnerSame as JUnit 5
Test PreconditionsProvides methods in Assume class to control whether test is run based on specified environment factorsProvides enhanced assumption method Assumptions.assumeThat() that allows fine-grained control for running a specific code block based on environmentSame as JUnit 5
Customization of BehaviorProvides runners and rules to support customization of the test framework behavior, but they have some limitationsUses a single flexible Extension modelUses same extension model as JUnit 5 but it has been cleaned up
Backwards CompatibilityCan run JUnit 3 testsCan run JUnit 3 and 4 tests via the Vintage engineThe Vintage engine still exists but is deprecated

Working Together for Comprehensive Testing

These features combine synergistically to create a complete, extensible framework that supports professional software development practices.

  • Test isolation and consistency. Setup and teardown methods ensure that each test starts in a desired predefined state.
  • Organizational flexibility. Test suites, nested tests, and tagging enable organizing and running the right tests at the right time.
  • Clear test expression. The core API supplies tools needed to write understandable, maintainable tests.
  • Scalability. Works for individual developers writing a handful of tests and enterprise teams maintaining thousands of tests across multiple modules.

How to Set Up JUnit Testing

So let’s dive 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 configuration of JUnit is handled via the pom.xml or build.gradle file, respectively.

It’s important to note that JUnit 5 was split into three modules, one of which is a vintage module that supports execution of JUnit 4 and three tests from JUnit 5.

JUnit 6 keeps this architecture.

JUnit 3 usage is very low at this point and is usually seen only in much older projects.

How to Set Up JUnit 6

Because of the modular fashion of JUnit 6, a Bill of Materials POM 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 6 to Maven, add the following to pom.xml:

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>6.0.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>6.0.2</version>
<scope>test</scope>
</dependency>
<dependencies>

For Gradle, add the following to the build.gradle file:

dependencies {
testImplementation platform("org.junit:junit-bom:6.0.2")
testImplementation "org.junit.jupiter:junit-jupiter-api"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
useJUnitPlatform()
}

How to Set Up JUnit 5

Because of the modular fashion of JUnit 5, a Bill of Materials POM is used to import all JUnit modules and dependencies. If only specific modules are needed, individual groups or artifacts can be specified instead.

JUnit 5 is configured in the same way as JUnit 6:

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.14.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.14.2</version>
<scope>test</scope>
</dependency>
<dependencies>

For Gradle, add the following to the build.gradle file:

dependencies {
testImplementation platform("org.junit:junit-bom:5.14.2")
testImplementation "org.junit.jupiter:junit-jupiter-api"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
useJUnitPlatform()
}

How to Set Up JUnit 4

To add JUnit 4 to Maven, add the following to pom.xml.

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>

For Gradle, add the following to the build.gradle:

dependencies {
testImplementation ‘junit:junit:4.13.2’
}
test {
useJUnit()
}

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, 5, and 6 all have the jar available to download directly. For JUnit 5 and 6, you’ll need to download a fat jar (aka uber jar) as described here.

Improve Unit Testing for Java With Automation: Best Practices for Java Developers

Get the Free Ebook

What Are the Components of a JUnit Test?

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.

Screenshot showing a JUnit test example of a simple method (left) that converts Fahrenheit to Celsius and a JUnit test (right) written to test the method.

 

Imports & Dependencies in JUnit

Parts 1 and 2 in the above screenshot are imports for the JUnit classes and methods used by the unit test. The imports can be specified as individual classes or methods, but are commonly specified as entire packages using asterisks. Either way works—the level of granularity of the imports is a matter of preference.

How to Define a JUnit Test Class

Part 3 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.

JUnit Annotations & How They’re Used

In Part 4, 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 for JUnit 5 and 6 are the following.

  • @BeforeEach 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.
  • @AfterEach 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.
  • @Disabled specifies that a test method should not be run.
  • @BeforeAll identifies a method that should be run once before any test methods are run.
  • @AfterAll identifies a method that should be run once after all test methods are run.

Test Method Naming in JUnit

For Part 5, again, note the naming convention testMethodName, where methodName is the name of the method being tested in the class under test. This naming convention is common but is not required. Sometimes additional description is added to the name to describe specific behavior being verified by the test

The "Given" Section & How to Set Up a Test

In Part 6, 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.

The "When" Section & How to Invoke the Method Under Test

In 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.

Capture the Output of the Method Under Test

In 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.

The "Then" Section and Using Assertions to Verify Test Outcomes

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, shown in Part 9, you have no verification. 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 6, 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.

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)
  • Using build systems (Maven and Gradle)

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.

Screenshot of how to run a JUnit in Jtest in the Eclipse IDE

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.

Screenshot running a JUnit test via Jtest in the IntelliJ IDE

How to Run a JUnit Test From the Maven Build Systems

Maven

Maven has made running unit tests simple. Ensure you are in the proper location in your console, 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

How to Run a JUnit Test From the Gradle Build System

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.

How to Run a JUnit Test From the Command Line

To run a JUnit directly from the command line, you need a few things:

  • JDK on your path
  • The appropriate JUnit jar file(s)
  • The source and test classes

The easiest way to do this in JUnit 5 and 6 is to use the JUnit Console Launcher as follows.

java -jar /path/to/junit-platform-console-standalone-<version>.jar execute -cp /path/to/source/classes -cp /path/to/test/classes <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.

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.

If unit testing is so important, why doesn’t everyone do it consistently? Despite its importance, unit testing isn’t always easy to implement or maintain. It requires deep development knowledge and ongoing effort to keep test suites current. As a result, unit testing often gets deprioritized—until regressions happen.

But it doesn’t have to be that way.

Accelerate JUnit Testing With AI

This is where Parasoft Jtest comes in. Its AI-powered Unit Test Assistant was designed to remove the friction from writing and maintaining unit tests. With just a few clicks, teams starting with 0% code coverage can automatically generate robust test suites that cover 60% or more of their Java code. As one financial services firm noted, "Since we implemented Parasoft Jtest, we have successfully reduced the amount of time it takes to create and maintain unit tests by more than 50%."

Here’s how your team can scale your testing practice.

  • Automatically generate JUnit test cases for previously uncovered code using intelligent bulk test creation.
  • Target test creation for modified code directly in the IDE or within pull request workflows.
  • Keep your data secure with Parasoft’s proprietary AI, which can run on-premises—no code or data leaves your environment.
  • Generate mocks and stubs to isolate dependencies and focus your tests, saving time and eliminating the need to write mocking logic manually.
  • Customize and extend tests with one-click actions to add assertions, parameterize test cases, and improve maintainability.
  • Speed up test execution using built-in test impact analysis and live unit testing, which identifies and runs only the tests needed to validate your recent code changes.

If writing and maintaining JUnit tests has felt like a chore, it’s time to modernize your approach. AI-enhanced testing solutions like this can turn test creation from a bottleneck into a strategic advantage—giving you more time to focus on building great software.

Make unit testing easier and faster with AI-enhanced Parasoft Jtest.

Try it yourself with a free, full-access trial for 14 days.

Get Free Trial