Getting started with JUnit

JUnit is one of a family of unit testing frameworks collectively known as xUnit, and is the most commonly used test framework in the Java ecosystem.

Similar unit testing frameworks are available for many other languages, and these unit test frameworks provide a convenient way to build unit, component, integration and end to end tests without having to write supporting code and have now become the norm for code based (as opposed to tool based) testing.

Test Lifecycle

As with manual tests, automated tests have expected outputs, and almost always have prerequisites and preconditions in addition to the test steps themselves.

Annotations

At the heart of JUnit class files are the annotations that describe what each method is for, the most commonly being an individual test and then additional annotations for actions that run before or after each test, or every test.

There is a full list of JUnit 5 annotations here:

https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations

Assertions

Assertions are the verification steps that produce the pass or fail results of each test – for example, does an actual output match the expected value.

A full list of JUnit 5 assertion methods are listed here:

https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html

The anatomy of a test

Here’s part of a simple example test showing common annotations and two simple mathematical assertions:

    @BeforeAll
    public static void setUpAll() {
        System.out.println("once at the start");
    }

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

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

    @AfterAll
    public static void tearDownAll() {
        System.out.println("once at the end");
    }

    @Test
    public void test1division() {
        System.out.println("@Test - divison");
        double d = 1.0 / 8.0;
        assertEquals(0.125, d, 0);
    }

    @Test()
    public void test2divisionWithException() {
        System.out.println("@Test - divisonbyzero");
        Exception exception = assertThrows(ArithmeticException.class, () -> {
            int i = 1 / 0;
        });
    }

It’s important to note that the order is not governed by location within the file, but by the annotations and for the test annotation, the order is deterministic but not related to the name. It is possible to specify an order for tests by adding additional directives as described here:

https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.html

In addition to the tests classes themselves, there is additional configuration required to actually run the tests. Rather than repeat this here (to copy and paste), you can download a fully working example of a simple JUnit 5 (and JUnit 4) test project from:

https://github.com/ObjectiveTester/AllThingsTesting.com-examples

Running a test

The easiest way to run these tests is probably from an IDE – consult the documentation of your preferred development environment, or if you’re feeling adventurous install a Java Development Kit and Maven.

Regardless of how you actually run the test, the output of the tests will be the same and demonstrates the lifecycle of the test.

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running example.com.SampleTest
once at the start
before every test
@Test - divisonbyzero
after every test
before every test
@Test - divison
after every test
once at the end
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.03 s - in example.com.SampleTest

As you can see from the output, this demonstrates where the various annotations result inwhich outputs, and the counterintuitive order of test execution.

Next Steps

This is a very simplistic look at the basics of JUnit – as a learning exercise, I would encourage you to extend the sample with additional assertion types, purposefully implement broken tests, and experiment with annotations including @ParameterizedTest.

In future blog posts I will explore other testing frameworks to perform both API and GUI testing.