About

8.5 JUnit, Fixtures, Assertions & Test Doubles

Theory: JUnit, Fixtures, Assertions & Test Doubles

TDD needs a tool that can run hundreds of small tests in seconds, every time the code changes. For Java that tool is JUnit. This lesson covers what JUnit is, how fixtures give every test a clean start, the order in which JUnit runs annotated methods, the main assertions, how tests are grouped into suites, and the family of fake objects called test doubles.

What JUnit Is

JUnit is a free, open-source unit-testing framework for Java, written by Erich Gamma and Kent Beck. It is a code-level regression testing tool: once written, the tests can be re-run automatically after every change, so any behaviour that breaks is noticed at once. Many languages have similar tools in the same family (PHPUnit for PHP, and so on). The latest version is JUnit 5; the slides use JUnit 3 and JUnit 4.

Fixtures: Set the Table Before Every Guest

A good restaurant does not let the next guest sit down at a table covered in the previous guest’s plates. Before each guest arrives, the table is set the same way: clean cloth, knife, fork, glass.

A fixture does the same for tests. It creates the objects and initial values that several tests need, so that every test starts from the same known state.

Fixture: the set-up (objects and initial values) that puts every test into the same known starting state. In JUnit 4 it is usually built in a @Before method, which runs before each test.

This is exactly the “every test starts fresh” rule from 8.1: a fixture removes any chance of one test depending on a side effect of another.

The Order of the Annotations

JUnit finds the methods to run by their annotations and runs them in a fixed order:

Diagram loads as you scroll.

In one line: @BeforeClass → ( @Before → @Test → @After ) repeated → @AfterClass.

The part in brackets repeats once for every @Test method. These are the JUnit 4 names; JUnit 5 renamed them:

RunsJUnit 4JUnit 5
Once, before all tests in the class@BeforeClass@BeforeAll
Before each test@Before@BeforeEach
The test itself@Test@Test
After each test@After@AfterEach
Once, after all tests in the class@AfterClass@AfterAll

Example: AccountTest (JUnit 4)

import org.junit.*;
import static org.junit.Assert.*;

public class AccountTest {
    private Account acc;

    @Before
    public void setUp() {
        acc = new Account(100);            // fixture: a fresh account before EACH test
    }

    @Test
    public void depositAddsMoney() {
        acc.deposit(50);
        assertEquals(150, acc.getBalance());   // (expected, actual)
    }

    @Test(expected = IllegalArgumentException.class)
    public void negativeDepositIsRejected() {
        acc.deposit(-50);                  // passes only if this throws
    }
}

Because setUp() runs before each test, negativeDepositIsRejected gets a brand-new account with balance 100, whatever depositAddsMoney did, and the two tests can run in any order.

JUnit 3 vs. JUnit 4

JUnit 3JUnit 4
Test classmust extend TestCaseany class (no inheritance needed)
Test methodname must start with test…@Test annotation
Before / after each testoverride setUp() / tearDown()@Before / @After
Expected exceptiontry { …; fail(); } catch (…) { }@Test(expected = X.class)
Time limitcheck by hand@Test(timeout = ms)

The Main Assertions

AssertionPasses when…
assertEquals(expected, actual)the values are equal (uses equals() for objects)
assertEquals(expected, actual, delta)two decimal numbers are equal within delta
assertSame / assertNotSamethey are / are not the same object in memory
assertTrue / assertFalsethe condition is true / false
assertNull / assertNotNullthe object is / is not null
fail(message)never: it always makes the test fail

Note the argument order: expected first, actual second. Swapping them still compares the same values, but the failure message (“expected 140 but was 150”) then reads backwards. The delta version exists because decimals are stored inexactly: 0.1 + 0.2 is not exactly 0.3 in a double.

From the slides: two traps.

  1. If you write your own equals for a class, its parameter must be Object: public boolean equals(Object obj). If you write equals(Person p), you have overloaded rather than overridden equals, and assertEquals will not use it.
  2. assertEquals checks equal values; assertSame checks the same object. Two different Person objects with the same name can be equal but are not the same.

Test Suites

A test suite runs many test classes together:

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({ AccountTest.class, TransactionTest.class })
public class AllTests { }   // empty: it only holds the annotations

Test Doubles

A test double is any object that stands in for a real one during a test, like a stunt double stands in for an actor. The general idea is the stub of 8.2; the vocabulary distinguishes five kinds:

Test doubleWhat it does
DummyOnly fills a parameter list. It is never really used.
StubGives fixed (canned) answers to the calls made during the test.
SpyA stub that also records how it was called, so the test can check it afterwards.
MockKnows which calls it expects; fails the test if they do not happen.
FakeA simple working implementation, for example an in-memory database.

The professor mentioned mocks and dummies only briefly; know the idea that each is a stand-in with a different amount of behaviour.


Worked Examples

Exercise 1

What is JUnit, who wrote it, and why is it called a regression testing tool?

Show solution ↓
Solution

JUnit is a free, open-source unit-testing framework for Java, written by Erich Gamma and Kent Beck.

It is a regression testing tool because the tests, once written, can be re-run automatically after every change to the code. If a change breaks behaviour that used to work (a regression), the test that covers that behaviour fails at once. This is what makes the TDD cycle and safe refactoring possible.

Exercise 2

A JUnit 4 test class has @BeforeClass, @Before, @After, @AfterClass methods and two @Test methods, t1 and t2. Give the order in which the methods run (assume t1 runs first).

Show solution ↓
Solution

@BeforeClass → @Before → t1 → @After → @Before → t2 → @After → @AfterClass.

@BeforeClass and @AfterClass run once for the whole class; @Before and @After run around each test, so each test starts from the same fixture. In JUnit 5 the same order uses @BeforeAll, @BeforeEach, @AfterEach, @AfterAll.

Exercise 3

What is a fixture, and which unit-testing rule does it enforce?

Show solution ↓
Solution

A fixture is the set-up code that creates the objects and initial values several tests need, so that every test starts from the same known state (in JUnit 4, a @Before method).

It enforces the rule that each unit test must be independent and must not depend on a side effect of a previous test (8.1). Because the fixture rebuilds the state before every test, the tests can run in any order.

Exercise 4

Rewrite this JUnit 3 test in JUnit 4 style:

public class StackTest extends TestCase {
    private Stack s;
    protected void setUp() { s = new Stack(); }
    public void testPopEmptyThrows() {
        try { s.pop(); fail(); } catch (EmptyStackException e) { }
    }
}
Show solution ↓
Solution

In JUnit 4 the class no longer extends TestCase, the set-up method is marked @Before, the test method is marked @Test (its name no longer needs to start with test), and the expected exception is declared in the annotation instead of a try/fail/catch:

import org.junit.*;
import java.util.EmptyStackException;

public class StackTest {
    private Stack s;

    @Before
    public void setUp() { s = new Stack(); }

    @Test(expected = EmptyStackException.class)
    public void popOnEmptyStackThrows() {
        s.pop();
    }
}
Exercise 5

A class Person defines public boolean equals(Person p), comparing names. A test does assertEquals(new Person("Ann"), new Person("Ann")) and fails. Why? What would assertSame do with the same two objects?

Show solution ↓
Solution

assertEquals fails because Person never overrode equals(Object). assertEquals calls equals(Object obj). The method equals(Person p) has a different parameter type, so it is an overload that assertEquals does not call; the inherited Object.equals is used instead, and it compares identity, so two different objects are “not equal”. The fix is public boolean equals(Object obj) (with hashCode to match).

assertSame would fail too, whatever equals does, because it checks whether both arguments are the same object in memory, and new created two different objects.

Exercise 6

Name the test double you would use in each case:

  1. A method needs a Logger argument, but the test never triggers any logging.
  2. The code under test reads exchange rates; the test needs the rate to be exactly 35.0 every time.
  3. The test must check that the code sent exactly one e-mail through the mail service.
  4. The tests need a database that really stores and retrieves records, but must run in memory without a server.
Show solution ↓
Solution
  1. Dummy: it only fills the parameter and is never used.
  2. Stub: it returns a fixed, canned answer (35.0).
  3. Mock (or a spy): a mock expects exactly one send call and fails the test if it does not happen; a spy would record the calls so the test can check them afterwards.
  4. Fake: a simple working implementation, such as an in-memory database.