Your First Test

Start with an empty test class:

package list;

import org.junit.jupiter.api.Test;

public class ArrayListTest {

  @Test
  public void newListIsEmpty() {
  }
}

As explained earlier, this test class lives in src/test/list/ and its name, ArrayListTest, marks it as the tests for ArrayList. The test itself is an ordinary method marked with the @Test annotation. That annotation is how JUnit finds the methods to run. Notice the annotation must be imported from org.junit.jupiter.api.Test.

If you compile and run this now, JUnit reports one test passes. In JUnit, a test passes if it runs without throwing an exception. Since the method body is empty, the test passes whether or not ArrayList is correct. We need to add an assertion to make a claim about the code.

Making a claim

A test makes a claim by asserting something and letting JUnit check it. The claim behind newListIsEmpty is that a brand-new list has size 0. We write that with assertEquals:

package list;

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

public class ArrayListTest {

  @Test
  public void newListIsEmpty() {
    List<Integer> list = new ArrayList<>();
    int expected = 0;
    int actual = list.size();
    assertEquals(expected, actual);
    // or just write
    // assertEquals(0, list.size());
  }
}

assertEquals(expected, actual) checks that two values are equal. The expected value comes first, which is what we believe the answer should be. The actual value comes second, which is what the code produces. If they match, the assertion does nothing and the test passes. If they do not, the assertion fails, and the whole test fails.

Notice the second import. assertEquals is a static method on JUnit’s Assertions class. The import static lets us call it as plain assertEquals(...) instead of Assertions.assertEquals(...). We import each assertion we use this way, one per line.

You need to know the import statements for the annotations and assertions you use. The IDE can add them automatically, or you can copy them from the examples in this chapter. You will not have to write them from memory on an exam. But you should know the names of the annotations and assertions, what they do, and how to use them.

We build the list with new ArrayList<>(), but we hold it in a variable of type List. That is programming to the interface, which we learned when we studied the List ADT. So the test can call only the methods that the List interface declares.

What a failure looks like

Suppose ArrayList had a bug and a new list reported size 1. Running the test would now produce a failure that names the test and reports the expected value and the actual value:

ArrayListTest > newListIsEmpty() FAILED
  expected: <0> but was: <1>

The three phases of a test

Here is a slightly longer test. If we add an element to an empty list, we should be able to get it back at index 0:

@Test
public void addThenGetReturnsElement() {
  List<Integer> list = new ArrayList<>();
  list.add(42);
  assertEquals(42, list.get(0));
}

Almost every test has the same three phases, in order:

  • Arrange: set up the objects and inputs the test needs. Here, create the list.
  • Act: perform the operation under test. Here, add(42).
  • Assert: check that the result is what the contract promises. Here, the element is at index 0.

This is called Arrange-Act-Assert.