Skip to Content
Course content

124: Assertions and Test Lifecycle Annotations

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've spent a lot of time in the trenches of legacy codebases, and one thing I've noticed is that developers often confuse assert statements with validation logic. They aren't the same thing. Let's look at why, and then we'll tackle how to actually organize our tests so we aren't writing the same setup code twenty times a file.

The vanishing act of the assert keyword

Suppose I'm building a simple BankAccount class. I have a rule: the balance should never drop below zero. I'll use a Java assertion to enforce this invariant during development.

public class BankAccount {
    private double balance;

    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    public void withdraw(double amount) {
        balance -= amount;
        // I want to make sure the balance didn't go negative
        assert balance >= 0 : "Balance cannot be negative! Current balance: " + balance;
    }

    public double getBalance() {
        return balance;
    }
}

Now, if I run a main method and call withdraw(200) on an account with only 100, I expect the program to scream at me with an AssertionError. I run it... and nothing happens. The program just continues, and the balance becomes -100.0. I'm thinking, "Wait, did I misspell the keyword?"

Nope. Here is the quirk: assertions are disabled by default in the JVM. Java assumes that in a production environment, you don't want the performance hit of checking these invariants. To actually see that error, I have to pass the -ea (enable assertions) flag to the JVM at runtime.

Once I add -ea to my VM arguments, the program crashes exactly where I expected. This is a key distinction: you use assert for things that "should be impossible" if your code is correct. You use if (amount > balance) throw new IllegalArgumentException(...) for things that can actually happen due to bad user input. Assertions are for the developer; exceptions are for the runtime.

Cleaning up the repetitive setup

Now let's move into the testing phase. I'm writing a JUnit 5 test suite for this account. I start writing my tests, and it looks like this:

class BankAccountTest {
    @Test
    void testWithdraw() {
        BankAccount account = new BankAccount(100);
        account.withdraw(50);
        assertEquals(50, account.getBalance());
    }

    @Test
    void testDeposit() {
        BankAccount account = new BankAccount(100);
        account.deposit(50);
        assertEquals(150, account.getBalance());
    }
}

I'm already annoyed. I'm instantiating new BankAccount(100) in every single method. If I decide later that accounts should start with 0, I have to change it in ten different places. I need a way to "reset the stage" before every test.

This is where @BeforeEach comes in. I'll move the account creation to a separate method. Now, JUnit will execute this method automatically before every single @Test method in the class.

class BankAccountTest {
    private BankAccount account;

    @BeforeEach
    void setUp() {
        account = new BankAccount(100);
        System.out.println("Setting up a fresh account...");
    }

    @Test
    void testWithdraw() {
        account.withdraw(50);
        assertEquals(50, account.getBalance());
    }

    @Test
    void testDeposit() {
        account.deposit(50);
        assertEquals(150, account.getBalance());
    }
}

The code is cleaner, and the tests are isolated. If testWithdraw messes up the account state, testDeposit doesn't care because it gets a brand new instance.

Handling the heavy lifting

But what if I have something expensive? Imagine I'm connecting to a real database to verify my account logs. Creating a database connection takes two seconds. If I have 100 tests, @BeforeEach would add over three minutes to my build time. That's unacceptable.

I need something that runs once for the entire class, not once per method. That's @BeforeAll. There's a catch, though: because it runs before the class is even instantiated for the individual tests, the method must be static.

class BankAccountTest {
    private static DatabaseConnection db;
    private BankAccount account;

    @BeforeAll
    static void initSharedResources() {
        db = new DatabaseConnection("jdbc:mysql://localhost:3306/bank");
        db.connect();
        System.out.println("Database connected once for all tests.");
    }

    @BeforeEach
    void setUp() {
        account = new BankAccount(100);
    }

    @AfterAll
    static void tearDownSharedResources() {
        db.disconnect();
        System.out.println("Database closed.");
    }
}

I've now got a hierarchy of lifecycles. @BeforeAll handles the heavy, shared infrastructure; @BeforeEach handles the lightweight, fresh state for every test. If I had files to delete or sockets to close after a specific test, I'd use @AfterEach. It's all about balancing performance with test isolation.




📋 Practical Task

Implementing the InventoryManager Lifecycle

You are tasked with testing an InventoryManager class. This class tracks product stock and connects to an external InventoryLogFile (simulated) to record changes. Your goal is to set up a test suite that is efficient and clean.

Requirements:

  • The Class: Create an InventoryManager class with a stock (int) and a recordChange(String msg) method.
  • The Assertion: Inside InventoryManager.removeStock(int amount), add a Java assert statement to ensure the stock never drops below zero.
  • The Lifecycle: Create a JUnit 5 test class InventoryManagerTest:
    • Use @BeforeAll to simulate opening a log file (just print "Log File Opened"). This method must be static.
    • Use @BeforeEach to initialize the InventoryManager with 10 items before every test.
    • Use @AfterAll to simulate closing the log file (just print "Log File Closed").
  • The Tests: Write one test that verifies removing stock works, and another that would trigger your assert statement (remember to mention how you would run it with -ea in a comment).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.