junit

Java Unit Testing with JUnit Example

This is an article for Java Unit Testing with JUnit Example.

You can also check this tutorial in the following video:

JUnit Testing in Java – video

1. Introduction

Java unit testing is a software testing where methods and classes are tested. JUnit is a unit testing framework for the Java programming language which provides a way to test the application as many as you want. Unit testing usually includes the following steps:

  1. define a test
  2. create an instance of the testing class
  3. prepare the test data
  4. execute a test
  5. verify the testing results
  6. report the testing results

JUnit supports step 1 via @Test annotation, step 4 via @RunWith annotation, and step 5 via assertion API. In this example, I will create a multi-module maven project to demonstrate how to utilize the JUnit framework to create a test class.

2. Technologies Used

The example code in this article was built and run using:

  • Java 11
  • Maven 3.3.9
  • Eclipse Oxygen
  • JUnit (4 and 5)

3. Maven Multi-Modules Project

JUnit 5 was released in 2017. It is not backwards compatible with JUnit 4 which released in 2006. In this step, I will demonstrate both JUnit 4 and JUnit 5 in a three-module Maven project:

  • common – includes a main class – SomeClass.
  • JUnit4-demo – tests SomeClass with JUnit 4.
  • JUnit5-demo – tests SomeClass with JUnit 5.

3.1 Parent POM

Parent pom.xml includes three modules and two common build plug-ins:

  • maven-compiler-plugin – defines the Java 11 for the compiler
  • maven-surefire-plugin – defines the JUnit report plug-in

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>jcg.zheng.demo</groupId>
	<artifactId>junit-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>pom</packaging>

	<name>junit-demo</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>


	<description>parent project for junit demo</description>
	<modules>
		<module>common</module>
		<module>junit4-demo</module>
		<module>junit5-demo</module>
	</modules>

	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.0</version>
				<configuration>
					<release>11</release>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>3.0.0-M3</version>
			</plugin>
		</plugins>
	</build>

</project>

Execute mvn clean install command and capture the output here:

[INFO] Reactor Summary for junit-demo 0.0.1-SNAPSHOT:
[INFO]
[INFO] junit-demo ......................................... SUCCESS [  2.287 s]
[INFO] comon .............................................. SUCCESS [ 10.295 s]
[INFO] junit4-demo ........................................ SUCCESS [  6.631 s]
[INFO] junit5-demo ........................................ SUCCESS [  6.191 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  26.052 s
[INFO] Finished at: 2020-03-30T20:46:54-05:00
[INFO] ------------------------------------------------------------------------

4. Common Module

In this step, I will create a common module which contains a main class. The main class will be tested at both JUnit 4 and JUnit 5 at its respective module.

4.1 POM

The common module’s pom.xml is defined as the following:

pom.xml

<?xml version="1.0"?>
<project
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>jcg.zheng.demo</groupId>
		<artifactId>junit-demo</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>

	<artifactId>common</artifactId>

	<name>comon</name>
	<url>http://maven.apache.org</url>

</project>

4.2 SomeClass

In this step, I will create SomeClass which has the following methods:

  • doubleANumber – return an integer number by multiplying two.
  • returnABoolean – return a boolean value based on the input string value.
  • voidFoo – does not return anything and throws an exception when receiving a bad argument.

SomeClass.java

package jcg.zheng.demo;

public class SomeClass {
	
 
	public int doubleANumber(int num) {
		return num * 2;
	}
	
	public boolean returnABoolean(String inputData) {
		if ("Save".equalsIgnoreCase(inputData)) {
			return true;
		} else {
			return false;
		}
	}

	public void voidFoo(String inputData) {
		if ("Ok".equalsIgnoreCase(inputData)) {
			System.out.println("doing something.");;
		} else {
			throw new IllegalArgumentException("Bad argument:" + inputData);
		}
	}
}

5. JUnit 4 Module

JUnit 4 was first released in 2006. It only has one jar and requires JDK 5 or higher version.

5.1 POM

The JUnit4-demo module’s pom.xml and depends on JUnit 4 and the common module.

In this step, I will create a JUnit 4 test class to test SomeClass.

pom.xml

<?xml version="1.0"?>
<project
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>jcg.zheng.demo</groupId>
		<artifactId>junit-demo</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>

	<artifactId>junit4-demo</artifactId>

	<name>junit4-demo</name>
	<url>http://maven.apache.org</url>
	<properties>
		<junit.version>4.12</junit.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>jcg.zheng.demo</groupId>
			<artifactId>common</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
	
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

</project>

5.2 SomeClassTest

In this step, I will create a SomeClassTest class in JUnit 4.

  • Define a test with @org.junit.Test
  • Print out a test name with a @org.junit.Rule on a org.junit.rules.TestName class
  • Setup the test before each tests with @org.junit.Before
  • Ignore a test with @org.junit.Ignore
  • Set a test with a timeout limitation
  • Set a test with an expected exception
  • Verify the testing result with the expected value with a static class org.junit.Assert

SomeClassTest.java

package jcg.zheng.demo.junit4;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;

import jcg.zheng.demo.SomeClass;

public class SomeClassTest {

	private SomeClass classUnderTest = new SomeClass();
	
	@Rule
	public TestName testName = new TestName();

	@Before
	public void setup() {
		classUnderTest = new SomeClass();
		System.out.println("Start " + testName.getMethodName());
	}

	@Test
	public void test_doubleANumber() {
		assertEquals(6, classUnderTest.doubleANumber(3));
	}

	@Ignore
	public void test_not_executed() {
		fail("It should not executed");
	}

	@Test
	public void test_returnBooleanFoo_false() {
		boolean shouldReturnFalse = classUnderTest.returnABoolean("NA");
		assertFalse(shouldReturnFalse);
	}

	@Test
	public void test_returnBooleanFoo_true() {
		boolean shouldReturnTrue = classUnderTest.returnABoolean("Save");
		assertTrue(shouldReturnTrue);
	}

	@Test
	public void test_voidFoo() throws IllegalAccessException {
		try {
			classUnderTest.voidFoo("OK");
		} catch (Exception e) {
			fail("Should not throw exception");
		}
	}

	@Test(expected = IllegalArgumentException.class)
	public void test_voidFoo_exception() throws IllegalAccessException {
		classUnderTest.voidFoo("NA");
	}
	
	@Test(timeout = 1)
	public void test_timeout() {
		classUnderTest.doubleANumber(9999);
	}
}
  • Line 20, 26 – the TestName instance marked by @Rule can access the test name.
  • Line 23 – the method marked with @Before will be invoked before executing each test.
  • Line 29 – @Test marks a method as a test. It will be executed by the JUnit default runner.
  • Line 34 – JUnit runner will ignore test tests which marks with @Ignore.
  • Line 31, 42, 48 – invokes assertFalse, assertTrue, assertEquals to verify the test results to the expected value.
  • Line 60 – catch the expected exception.
  • Line 65 – set up the timeout limit.

Output

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running jcg.zheng.demo.junit4.SomeClassTest
Start test_voidFoo
doing something.
Start test_returnBooleanFoo_false
Start test_voidFoo_exception
Start test_doubleANumber
Start test_timeout
Start test_returnBooleanFoo_true
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.214 s - in jcg.zheng.demo.junit4.SomeClassTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0

6. JUnit 5 Module

JUnit 5 was first released in 2017. It requires JDK 8 or higher. It includes a collection of three sub-projects: JUnit Jupiter, JUnit Platform, and JUnit Vintage.

6.1 POM

The JUnit5-demo module’s pom.xml depends on JUnit 5 and common modules. Please note that it includes two of JUnit 5 modules: junit-jupiter-engine and junit-jupiter-api.

In this step, I will create a JUnit 5 test class to test SomeClass.

pom.xml

<?xml version="1.0"?>
<project
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
	xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>jcg.zheng.demo</groupId>
		<artifactId>junit-demo</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>

	<artifactId>junit5-demo</artifactId>

	<name>junit5-demo</name>
	<url>http://maven.apache.org</url>
	<properties>
		<junit-jupiter.version>5.5.2</junit-jupiter.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>jcg.zheng.demo</groupId>
			<artifactId>common</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
		 
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-engine</artifactId>
			<version>${junit-jupiter.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-api</artifactId>
			<version>${junit-jupiter.version}</version>
			<scope>test</scope>
		</dependency>

	</dependencies>


</project>

6.2 SomeClassTest

In this step, I will create a SomeClassTest class in JUnit 5.

  • Define a test with @org.junit.jupiter.api.Test
  • Define a display name with @org.junit.jupiter.api.DisplayName
  • Print out a test name from @org.junit.jupiter.api.TestInfo
  • Setup the test before each tests with @org.junit.jupiter.api.BeforeEach
  • Ignore a test with @org.junit.jupiter.api.Disabled
  • Set a test with the org.junit.jupiter.api.assertTimeout method
  • Catch an exception with the org.junit.jupiter.api.assertThrow method
  • Verify the testing result with the expected value with a static class: org.junit.jupiter.api.Assertions

SomeClassTest.java

package jcg.zheng.demo.junit5;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.time.Duration;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestReporter;

import jcg.zheng.demo.SomeClass;

public class SomeClassTest {

	private SomeClass classUnderTest;
	private TestInfo testInfo;
	private TestReporter testReporter;

	@BeforeEach
	public void setup(TestInfo testInfo, TestReporter terstReporter ) {
		this.testInfo = testInfo;
		this.testReporter = terstReporter;
		classUnderTest = new SomeClass();
	}

	@RepeatedTest(5)
	public void test_doubleANumber() {
		assertEquals(6, classUnderTest.doubleANumber(3), "it should return 6");
	}

	@Disabled
	public void test_not_executed() {
		fail("It should not executed");
	}

	@Test
	@DisplayName("It should return false when input data isn't Save")
	public void test_returnBooleanFoo_false() {
		boolean shouldReturnFalse = classUnderTest.returnABoolean("NA");
		assertFalse(shouldReturnFalse);
	}

	@Test
	@DisplayName("It should return true when input data is Save")
	public void test_returnBooleanFoo_true() {
		boolean shouldReturnTrue = classUnderTest.returnABoolean("Save");
		assertTrue(shouldReturnTrue);
		testReporter.publishEntry(testInfo.getDisplayName());
	}

	@Test
	public void test_voidFoo() throws IllegalAccessException {
	 
		try {
			classUnderTest.voidFoo("OK");
		} catch (Exception e) {
			fail("Should not throw exception");
		}
	}

	@Test
	public void test_voidFoo_exception() throws IllegalAccessException {
		assertThrows(IllegalArgumentException.class, () -> {
			classUnderTest.voidFoo("NA");
		});

	}
	
	@Test
	public void test_timeout() {
		assertTimeout(Duration.ofMillis(1), ()-> classUnderTest.doubleANumber(1000));
	}

}
  • Line 28 – @BeforeEach marks the method to be executed for each test.
  • Line 29 – can inject TestInfo and TestReporter from Junit framework.
  • Line 35 – @RepeatedTest annotation is a new annotation in Junit 5 which executes the test repeatedly.
  • Line 40 – @Disabled annotation replaces the @Ignore annotation in Junit 4.
  • Line 45 – @Test in org.junit.jupiter.api package marks a test.
  • Line 46 – @DisplayName is a new annotation which names the test with a more meaningful name.
  • Line 72 – assertThrows in JUnit 5 replaces the @Test‘s expected attribute in Junit 4 .
  • Line 80 – assertTimeout in JUnit 5 replaces the @Test‘s timeout attribute in Junit 4 .

Output

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running jcg.zheng.demo.junit5.SomeClassTest
doing something.
[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.254 s - in jcg.zheng.demo.junit5.SomeClassTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0

In Eclipse IDE, you can see the test with the display name.

JUnit Example - JUnit 5 Result
Figure 1 JUnit 5 Result

7. Summary

In this example, I demonstrated how to write a unit test in JUnit. Here are the major differences between JUnit 4 and JUnit 5:

JUnit 4 JUnit 5
Required JDK 5 (+) 8 (+)
Package org.junit org.junit.jupiter
Annotation @Before @BeforeEach
@After @AfterEach
@BeforeClass @BeforeAll
@AfterClass @AfterAll
@Ignore @Disabled
@DisplayName
@RepeatedTest
@Category @Tag
@RunWith @ExtendWith

8. Download the Source Code

Download
You can download the full source code of this example here: Java Unit Testing with JUnit Example

Mary Zheng

Mary has graduated from Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She works as a senior Software Engineer in the telecommunications sector where she acts as a leader and works with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Adam Ziegler
Adam Ziegler
4 years ago

I hate how-to’s that combine multiple technologies. They’re like lab experiments with multiple unknowns. Get rid of all the other tools, write the java code, and demonstrate JUnit to test it. Everything else is just noise.

Back to top button