CodeGym /Courses /Module 5. Spring /Lecture 132: Unit Tests using JUnit 5

Lecture 132: Unit Tests using JUnit 5

Module 5. Spring
Level 9 , Lesson 1
Available

Today we'll cover the basics of Unit testing using JUnit 5.

Let's figure out how to use this power to build high-quality, safe code.

What is JUnit 5 and why do we need it?

JUnit 5 is the modern version of the popular testing framework for Java. Its goal is to help developers write solid Unit tests. The advantage of JUnit 5 is its modularity and flexibility. Here are the main components of JUnit 5:

  • JUnit Platform: responsible for running tests.
  • JUnit Jupiter: provides new features and annotations for testing.
  • JUnit Vintage: allows running older tests written for JUnit 3 and 4 (archaeologists, this one's for you).

Why JUnit 5? Because:

  • It supports Java 8 and above, including lambdas and default methods.
  • More options to configure the test lifecycle.
  • The lovely @DisplayName annotation to make your tests look nice and clear.

Main JUnit 5 annotations

Annotations are like pointers for JUnit that tell it what and how to test.

@Test

The main annotation. If you want code to run as a test, just mark the method with this annotation.


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

class MathTest {

    @Test
    void additionTest() {
        int result = 2 + 3;
        assertEquals(5, result, "2 + 3 should equal 5");
    }
}

@BeforeEach and @AfterEach

Used to run actions before and after each test. For example, setting up the test environment or cleaning resources.


import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;

class LifecycleTest {

    @BeforeEach
    void setUp() {
        System.out.println("Setup before test");
    }

    @AfterEach
    void tearDown() {
        System.out.println("Cleanup after test");
    }

    @Test
    void simpleTest() {
        System.out.println("Running the test");
    }
}

The output will be:


Setup before test
Running the test
Cleanup after test

@BeforeAll and @AfterAll

Used to perform actions before or after all tests in the class. These methods must be static.


import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;

class GlobalSetupTest {

    @BeforeAll
    static void init() {
        System.out.println("Setup before ALL tests");
    }

    @AfterAll
    static void cleanup() {
        System.out.println("Cleanup after ALL tests");
    }

    @Test
    void testOne() {
        System.out.println("First test");
    }

    @Test
    void testTwo() {
        System.out.println("Second test");
    }
}

Output:


Setup before ALL tests
First test
Second test
Cleanup after ALL tests

@DisplayName

Add some greenery to the digital desert of your tests! Make them prettier with @DisplayName.

@Test
@DisplayName("Addition check: 2 + 2 = 4")
void additionTest() {
    assertEquals(4, 2 + 2);
}

Basic Assertions

Assertions are like checkpoints. They verify that your code behaves as expected.

Assertion Description Example
assertEquals(expected, actual) Checks whether the expected and actual values are equal assertEquals(4, 2 + 2)
assertNotEquals(unexpected, actual) Checks that values are NOT equal assertNotEquals(5, 2 + 2)
assertTrue(condition) Asserts that the condition is true assertTrue(2 + 2 == 4)
assertFalse(condition) Asserts that the condition is false assertFalse(2 + 2 == 5)
assertNull(object) Checks that the object is null assertNull(nullVariable)
assertNotNull(object) Checks that the object is NOT null assertNotNull(nonNullVariable)
assertThrows(exception, lambda) Checks that the code throws the specified exception assertThrows(NumberFormatException.class, () -> Integer.parseInt("NaN"))

Example:

@Test
void testAssertions() {
    assertEquals(4, 2 + 2, "Adding two numbers should be correct");
    assertTrue(3 > 2, "3 > 2 is true");
    assertThrows(ArithmeticException.class, () -> {
        int result = 10 / 0;
    }, "Expected ArithmeticException when dividing by 0");
}

Test lifecycle in JUnit 5

Tests in JUnit go through a specific lifecycle:

  1. An instance of the test class is created.
  2. The method annotated with @BeforeEach is called.
  3. The test runs.
  4. The method annotated with @AfterEach is called.
  5. This repeats for every test.

Methods annotated with @BeforeAll and @AfterAll run once per class.


Practice: Writing Unit tests for simple methods

Suppose we have a class Calculator:


class Calculator {

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

    int subtract(int a, int b) {
        return a - b;
    }

    int multiply(int a, int b) {
        return a * b;
    }

    int divide(int a, int b) {
        if (b == 0) {
            throw new IllegalArgumentException("Division by zero is not allowed");
        }
        return a / b;
    }
}

And tests for it:


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

class CalculatorTest {

    Calculator calculator = new Calculator();

    @Test
    void testAddition() {
        assertEquals(5, calculator.add(2, 3), "2 + 3 should equal 5");
    }

    @Test
    void testSubtraction() {
        assertEquals(1, calculator.subtract(3, 2), "3 - 2 should equal 1");
    }

    @Test
    void testMultiplication() {
        assertEquals(6, calculator.multiply(2, 3), "2 * 3 should equal 6");
    }

    @Test
    void testDivision() {
        assertEquals(2, calculator.divide(6, 3), "6 / 3 should equal 2");
    }

    @Test
    void testDivisionByZero() {
        Exception exception = assertThrows(IllegalArgumentException.class, () -> calculator.divide(5, 0));
        assertEquals("Division by zero is not allowed", exception.getMessage());
    }
}

These tests check basic arithmetic operations and handle the exception when dividing by zero.


What usually confuses people the most?

  1. Why aren't my tests running? Maybe your method in the test class isn't annotated with @Test.
  2. Why does a test "fail"? Read the error message carefully. Most likely the expectations in assertEquals don't match the actual result.
  3. How to test private methods? Often private methods are tested indirectly through public ones.

JUnit 5 is a powerful tool, and the more you practice with it, the easier it gets to write solid tests. Go forth — smash your bugs in true superhero fashion!

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION