Getting started with JUnit 5

JUnit is the most popular Java Testing framework. It is explicitly recommended for Unit Testing of Java applications.

JUnit framework is also well integrated with common IDE such as Eclipse and IntelliJ Idea which allows quick and easy generation of test cases and test data. In this tutorial we will learn how to code a simple Test case for a Java class using JUnit 5.

JUnit HelloWorld

We will start with a basic JUnit example. Here is a Java class with an add method to be tested:

public class Calculator {

    public int add(int a, int b) {
        return a + b;
    }

}

In order to test the add method, we will code the following JUnit 5 Test:

package com.example.project;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class CalculatorTests {

    @Test
    @DisplayName("1 + 1 = 2")
    void addsTwoNumbers() {
        Calculator calculator = new Calculator();
        assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2");
    }

    @ParameterizedTest(name = "{0} + {1} = {2}")
    @CsvSource({
            "0,    1,   1",
            "1,    2,   3",
            "49,  51, 100",
            "1,  100, 101"
    })
    void add(int first, int second, int expectedResult) {
        Calculator calculator = new Calculator();
        assertEquals(expectedResult, calculator.add(first, second),
                () -> first + " + " + second + " should equal " + expectedResult);
    }
}

To run a JUnit Test, we need to decorate our methods with the @org.junit.jupiter.api.Test annotation.

  • The first one, uses a custom @Displayname for the text
  • The second one is a @ParameterizedTest which uses as source of data a Csv provided as argument to the Test.

To build a JUnit 5 project it’s recommended to include the following dependencies in your pom.xml:

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

<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

For Gradle, you can use the following build.gradle:

plugins {
    id 'java'
    id 'eclipse' // optional (to generate Eclipse project files)
    id 'idea' // optional (to generate IntelliJ IDEA project files)
}

repositories {
    mavenCentral()
}

dependencies {
    testImplementation(platform('org.junit:junit-bom:5.7.2'))
    testImplementation('org.junit.jupiter:junit-jupiter')
}

test {
    useJUnitPlatform()
    testLogging {
        events "passed", "skipped", "failed"
    }
}

Next, run the above test from the Command Line or from your IDE:

As you can see, all assertion were verified so test passed.

The source code for this tutorial is available here: https://github.com/fmarchioni/mastertheboss/tree/master/test/junit5-jupiter

Summary:

  • JUnit is a framework which supports several annotations to identify a method which contains a test.
  • JUnit provides an annotation called @Test, which tells the JUnit that the public void method in which it is used can run as a test case.
  • JUnit can be easily be plugged in your IDE: check this tutorial as an example: Getting started with JUnit and IntelliJ Idea
  • JUnit can also be used to execute multiple tests in a specified order, using test suites. Check this tutorial to learn more: Getting started with JUnit 5 TestSuite
  • JUnit also provides life-cycle methods for a fine grained control of your Tests: JUnit 5 Lifecycle methods

Finally, check as reference our JUnit CheatSheet: JUnit 5 cheatsheet

Getting started with JUnit Assertions

In this tutorial we will learn how to use JUnit assertions. An assertion is one of a number of static methods on the org.junit.jupiter.api.Assertions class. Assertions are used to test a condition that must evaluate to true in order for the test to continue executing.

If an assertion fails, the test is halted at the line of code where the assertion is located, and the assertion failure is reported. If the assertion succeeds, the test continues to the next line of code. JUnit Jupiter, comes with many of assertion methods that JUnit 4 has and adds some overloads which can be used with Java 8 Lambda Expression. Let’s see them in detail:

assertAll

AssertAll can be used to verify a group of conditions. The interesting thing about assertAll is that it always checks all of the assertions that are passed to it, no matter how many fail. If all pass, all is fine – if at least one fails you get a detailed result of all that went wrong.

It is best used for asserting a set of properties that belong together conceptionally. Example: assume you have a simple class like an address with fields city, street, number and would like to assert that those are what you expect them to be:

Address address = addressFactory.buildAddress();
assertEquals("New York", address.getCity());
assertEquals("Park avenue", address.getStreet());
assertEquals("1250", address.getNumber());

Now, as soon as the first assertion fails, you will never see the results of the second, which can be an issue. Using assertAll can be the right approach. Indeed, if the method returns the wrong address, you will still see the following error stack trace:

    (3 failures)
    expected: <New York> but was: <Philadelphia>
    expected: <Park avenue> but was: <street 51>
    expected: <1250> but was: <126>

Please notice that you coule use also the lamda syntax as well:

Address address = addressFactory.buildAddress();
assertAll("Checking full address",
    () -> assertEquals("New York", address.getCity()),
    () -> assertEquals("Park avenue", address.getStreet()),
    () -> assertEquals("1250", address.getNumber())

assertNull

Asserts that an object is null. If it isn’t an AssertionError is thrown:

 @Test
  public void testWithNull() {
     
    Integer year = null;     
    assertNull(year, "The year is not null");
   
}

assertNotNull

Asserts that an object is not null. If it isn’t an AssertionError is thrown:

 @Test
  public void testWithNull() {
     
    Integer year = 2019;     
    assertNotNull(year, "The year is not null");
   
}

assertThrows

An expected exception is considered to be just another condition that can be asserted, and thus Assertions contains methods to handle this. Example:

@Test()
@DisplayName("Empty argument")
public void testAdd_ZeroOperands_EmptyArgument() {
  long[] numbersToSum = {};
  assertThrows(IllegalArgumentException.class, () ‑> classUnderTest.add(numbersToSum));
}

 @Test
  public void testIsNullOrBlankOK() {
    // Test the case that the input is NULL
    String input = null;

    assertTrue(StringUtils.isNullOrBlank(input));
  
    // Test case with the input is empty
    input = " ";
    assertTrue(StringUtils.isNullOrBlank(input));

    // Test case with the input is not empty
    input = "abc";

    assertFalse(StringUtils.isNullOrBlank(input));

  }

assertSame and assertNotSame

assertSame checks the object reference using the == operator. Here is an example:

BigDecimal b1 = new BigDecimal("500");
BigDecimal b2 = new BigDecimal("500");
BigDecimal b3 = b1;
 
int i1 = 10;
int i2 = 10;
 
  @Test
  public void BigDecimaltest() throws Exception {
    // if(b1 == b2)
    assertSame(b1, b2);    // FAILS!
 
    // b1.equals(b2)
    assertEquals(b1, b2);  // PASSES!
 
    // (b1 == b3)
    assertSame(b1, b3);    // PASSES!
 
    //(b1.equals(b3))
    assertEquals(b1, b3);  // PASSES!
  }


    assertNotSame(defaultSt, actual, () -> "Expected ouput is same with actual");

assertEquals

Check two objects/primitive values using the following rule:

  • If primitive values are passed and then the values are compared.
  • If objects are passed, then the equals() method is invoked.
  @Test
  public void testConcatWithRegularInput() {
    String st1 = "Hello";
    String st2 = "World";
    String st3 = "!";
    String expect = st1 + st2 + st3;
    String actual = StringUtils.concat(st1, st2, st3);
    assertEquals(expect, actual);
}

What is the difference between assertions and assumptions? The difference can be subtle, so use this rule of thumb: Use assertions to check the results of a test method. Use assumptions to determine whether to run the test method at all. An aborted test is not reported as a failure, meaning that failure won’t break the build.

 

 

Getting started with JUnit and IntelliJ Idea

In this tutorial we will learn how to create a JUnit 5 project using IntelliJ. We will be at first creating a simple Maven Project, then we will add a basic Class and a JUnit Test for it.

Creating an IntelliJ Project:

Requirements: Download IntelliJ Community edition from: https://www.jetbrains.com/idea/download/

Install IntelliJ Idea, then launch it:

  • Create a new Project using your favorite framework (e.g. Maven):

  • Fill up the Project coordinates (Group, Artifact, Version)
  • Next add a Java Class

Here is our basic class:

public class UnitTestDemo {

    public int sum(int x, int y) {
        return x+y;
    }
}

Next, right-click on your Class and choose “Generate”  to generate a Test Class:

Select the Class name to be generated, the methods to be tested and the testing framework:

As you can see from the above image, the JUnit library has not been found in your project. Clicking on Fix you can choose where to install the JUnit library.

Here is the generated class:

import static org.junit.jupiter.api.Assertions.*;

class UnitTestDemoTest {

    @org.junit.jupiter.api.Test
    void sum() {
    }
}

Add an example test to verify the sum method:

import static org.junit.jupiter.api.Assertions.*;

class UnitTestDemoTest {

    @org.junit.jupiter.api.Test
    void sum() {
        UnitTestDemo test = new UnitTestDemo();
        assert(test.sum(3,3) == 6);
    }
}

Here is how your JUnit project should look like:

src
├── main
│   ├── java
│   │   └── UnitTestDemo.java
│   └── resources
└── test
    └── java
        └── UnitTestDemoTest.java

And this is the pom.xml used by our project:

<?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>com.example</groupId>
    <artifactId>junitdemo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

Now right-click on the Test class and check that the Test passed:

Congratulations! You just managed to create your first JUnit 5Project with IntelliJ Idea!

How to control JUnit Tests order

JUnit tests are executed using a deterministic (but not obvious) criteria, however it’s possible to define the exact order of Test execution. This tutorial shows how to do it.

Even if true unit tests should not rely on a precise order in which they are executed, sometimes it is needed to enforce a specific order for your tests.

To control the order in which test methods are executed, you can annotate your test class with @TestMethodOrder and specify which criteria you want to use for ordering tests, among the following options:

  • Alphanumeric: the order of test is chosen alphanumerically based on the method names
  • OrderAnnotation: the order of tests is specified with a number
  • Random: tests are executed using a random order

The following example demonstrates how to guarantee that test methods are executed in the order specified via the @Order annotation.

import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

@TestMethodOrder(OrderAnnotation.class)
class OrderedTestsDemo {

    @Test
    @Order(1)
    void test1() {
        // This test first
    }

    @Test
    @Order(2)
    void test2() {
        // Then this test
    }

    @Test
    @Order(3)
    void test3() {
        Finally this test
    }

}

You can specify a custom display name for your tests using the @DisplayName annotation which accepts spaces, special characters, and even emojis. Here is an example:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@DisplayName("This will display in the Test Report")
class ExampleDisplayName {

    @Test
    @DisplayName("Custom test name")
    void testWithDisplayName() {
    }

    @Test
    @DisplayName("😱")
    void testWithDisplayNameAndEmoji() {
    }

}

How to run repeated tests in JUnit

The @RepeatedTest annotation enable to run multiple times single tests in JUnit 5. The number of times the test will be executed can be configured as parameter to @RepeatedTest annotation.

Example:

@Test
@RepeatedTest(5)
void addNumber() {
    Calculator calculator = new Calculator();
Random rand = new Random();
int n = rand.nextInt(50);

    Assertions.assertEquals(n*2, calculator.add(n, n), "Checking calculator sum");
}

In above code, addNumber() test will executed 5 times. Also note that each invocation of a repeated test works just like the execution of a regular @Test method. I.e. you have full support for the same lifecycle callbacks and extensions.

How to assert Exceptions in JUnit

In JUnit 5 you can use Assertions.assertThrows to assert that a particular Exception is thrown in your test. Example:

A very simple example can be:

@Test
void testExpectedException() {
 
  Assertions.assertThrows(NumberFormatException.class, () -> {
    Integer.parseInt("abc");
  });
 
}

If you are using Junit 4.7 you can use the ExpectedException Rule:

public class SampleTest {
  @Rule
  public final ExpectedException exception = ExpectedException.none();

  @Test
  public void testThrowsNumberFormatException() {
     
    exception.expect(NumberFormatException.class);
    Integer.parseInt("One");
  }
}

Older version of Junit 4 can use:

@Test(expected = NumberFormatException.class)
public void testThrowsNumberFormatException()() {
    Integer.parseInt("One");
}

How to verify the Console output in JUnit Tests

This tutorial shows how to assert the Console output of a JUnit Test:

First of all we need a class which sets the out and err streams to the PrintStream in the @Before and @After callback methods:

private final ByteArrayOutputStream out = new ByteArrayOutputStream();
private final ByteArrayOutputStream err = new ByteArrayOutputStream();
private final PrintStream originalOut = System.out;
private final PrintStream originalErr = System.err;

@Before
public void setStreams() {
    System.setOut(new PrintStream(out));
    System.setErr(new PrintStream(err));
}

@After
public void restoreInitialStreams() {
    System.setOut(originalOut);
    System.setErr(originalErr);
}

And this is a sample Test class:

@Test
public void out() {
    System.out.print("test output");
    assertEquals("test output", out.toString());
}

@Test
public void err() {
    System.err.print("test output changed");
    assertEquals("test output", err.toString());
}

Getting started with JUnit assumptions

Assumptions are similar to assertions, except that assumptions must hold true or the test will be aborted. In contrast, when an assertion fails, the test is considered to have failed. JUnit Assumptions helps us in skipping a test for some specific scenario. Sometimes our test cases are dependent on the inputs, operating systems or any third party data. If our test is not written for some specific inputs, then we can use assumptions to skip our test method.

An assumption is a static method of the org.junit.jupiter.api.Assumptions class. To appreciate the value of assumptions, all you need is a simple example. Suppose you want to run a particular unit test only on Monday:

@Test
@DisplayName("This test is only run on Mondays")
public void testAdd_OnlyOnMonday() {
  LocalDateTime ldt = LocalDateTime.now();
  assumeTrue(ldt.getDayOfWeek().getValue() == 1);
  // Rest of the test executed only if assumption is valid
}

We can use assumeTrue() to skip the test if the above condition evaluates to false.

We can use assumeFalse() to validate that the condition is returning false. Let’s say we have a test method that we don’t want to run with Windows operating system:

@Test
public static void skipWindowsOs() {
    Assume.assumeFalse(OperationSystemHelper.isWindows());
}

assumingThat()

This method executes the supplied Executable if the assumption is valid. If the assumption is invalid, this method does nothing. We can use this for logging or notifications when our assumptions are valid. Example:

@Test
	void testInAllEnvironments() {
		assumingThat("Redis".equals(System.getenv("ENV")),
			() -> {
				// perform these assertions only on the Redis server
				assertEquals(4, checkNumberOfCPU());
			});

		// perform these assertions in all environments
		assertEquals(2048, checkFreeMemory());
}

How to Tag Tests in JUnit 5

In this tutorial, we are going to learn how to Tag Tests using JUnit Jupiter tests.

Test classes and methods can be tagged in the JUnit 5 programming model by means of the annotation @Tag. Those tags can later be used to filter test discovery and execution. In this example, we see the use of @org.junit.jupiter.api.Tag at class level and also at method level.

package com.example;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Tag;

import static org.junit.jupiter.api.Assertions.assertEquals;

 
@Tag("all")
public class AppTest 
{

    @Test
    @Tag("development")

    void testOne() {
        assertEquals("ok","ok");
    }

    @Test
    @Tag("production")
    void testTwo() {
        assertEquals("ok","ok");
    }

    @Test
    @Tag("performance")
    void testThree() {
        assertEquals("ok","ok");
    }

}

Now how to choose which of the tagged Tests can be executed? There are two simple ways: one is specifying in the maven-surfire plugin which Test Tags we want to execute:

        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
          <configuration>
            <groups>production</groups>
          </configuration>
        </plugin>

Another option is to use a simple TestSuite class which uses the @IncludeTags to specify the list of @Tag Tests:

package com.example;

import org.junit.platform.runner.JUnitPlatform;
import org.junit.platform.suite.api.IncludeTags;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.runner.RunWith;

@RunWith(JUnitPlatform.class)
@SelectPackages("com.example")
@IncludeTags("all")
public class TestSuiteDemo {
}

For example, if we were to run this TestSuite, we would fire all tests, as the “all” Tag has been placed at Class level:

Getting started with JUnit 5 TestSuite

A JUnit 5 TestSuite can be used to to run multiple tests together. They allow to aggregate multiple test classes. JUnit 5 provides the following annotations:

  • @SelectPackages – used to specify the names of packages for the test suite
  • @SelectClasses – used to specify the classes for the test suite. They can be located in different packages.

Here is an example which uses both annotation to select two Test Classes and a package:

package com.example;

import org.junit.platform.runner.JUnitPlatform;
import org.junit.platform.suite.api.IncludeTags;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.runner.RunWith;


@RunWith(JUnitPlatform.class)
@SelectClasses( { AppTest.class, AppTest2.class } )
@SelectPackages("com.example")
public class TestSuiteDemo {
}

Include the following dependency to run the Testsuite class:

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <junit-platform.version>1.2.0</junit-platform.version>
    <junit-jupiter.version>5.2.0</junit-jupiter.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.junit.platform</groupId>
      <artifactId>junit-platform-runner</artifactId>
      <version>${junit-platform.version}</version>
      <scope>test</scope>
    </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-params</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>