# JUnit and Mockito

> The Java testing duo: JUnit 5 for structuring and running tests with assertions and lifecycle, and Mockito for mocking the collaborators you want to isolate.


---

# JUnit and Mockito

You opened a Java repo, saw a `src/test/java` folder full of `@Test` methods and `mock(...)` calls, and felt the familiar dread: which annotation does what, why is half the test setting up fake objects, and how do you write one of these yourself without copy-pasting the rest of the suite. This guide gives you the two tools that 90% of Java test code is built from, and the mental model to use them without drowning in mocks.

## How to read this

Read the phases in order. Phase 1 builds the JUnit 5 mental model so the annotations stop being noise. Phase 2 puts Mockito next to it and shows the everyday isolate-the-unit workflow. Phase 3 is the part nobody warns you about: the ways these tools quietly lie to you, and how to keep your tests trustworthy. If you've never written a unit test at all, /guides/your-first-unit-test is the gentler on-ramp; come back here for the Java specifics.

## The phases

1. [What JUnit 5 actually is](01-what-junit-5-actually-is.md) - the Jupiter model, `@Test`, lifecycle, and assertions.
2. [Mocking with Mockito](02-mocking-with-mockito.md) - isolate the unit, stub collaborators, verify interactions.
3. [When tests lie](03-when-tests-lie.md) - the mock-too-much trap, brittle verification, and production reality.


---

# What JUnit 5 actually is

Here's the reality you're starting from: a test in Java is not a special language feature. It's an ordinary method in an ordinary class. The only thing that makes it a *test* is an annotation that tells a test runner "call this method, and if it throws, that's a failure." That's the whole trick. JUnit is the machinery that finds those annotated methods, runs them in a predictable order, and reports which ones blew up.

JUnit 5 is the current generation, and it's actually three pieces wearing one name. **Jupiter** is the API you write against (`@Test`, `@BeforeEach`, assertions). The **platform** is the engine that discovers and launches tests - it's what your build tool and IDE talk to. And there's a **vintage** engine that runs old JUnit 4 tests so legacy suites don't have to be rewritten overnight. When someone says "JUnit 5," they almost always mean *writing Jupiter tests*. That's what you'll do here.

## The smallest test that exists

A test class is a normal class. A test method is a method annotated `@Test` that contains an assertion - a statement that throws if reality doesn't match expectation.

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

class CalculatorTest {

    @Test
    void addsTwoNumbers() {
        Calculator calc = new Calculator();
        int result = calc.add(2, 3);
        assertEquals(5, result);   // expected first, actual second
    }
}
```

*What just happened:* the runner found `addsTwoNumbers` because of `@Test`, called it, and `assertEquals` compared `5` (expected) against `result` (actual). They matched, so nothing threw, so the test passed. Note the argument order - `assertEquals(expected, actual)`. Reverse it and your failure messages read backwards, which costs you minutes every time something breaks.

Test methods don't need to be `public` in JUnit 5 (they did in JUnit 4). Package-private is the convention. They also return `void` and take no arguments - unless you ask for them, which Phase 2 gets into with mocks.

## Assertions: the part that does the judging

An assertion is the line that decides pass or fail. JUnit gives you a focused set, all static methods on `Assertions`:

```java
assertEquals(expected, actual);          // values match
assertTrue(condition);                   // condition is true
assertNull(value);                       // value is null
assertThrows(IllegalArgumentException.class,
             () -> service.parse("oops")); // the call throws that type
```

*What just happened:* each line is a small contract. The first three check a value; `assertThrows` is the one people forget exists - it asserts that the lambda *does* throw the given exception type, and it returns the caught exception so you can assert on its message too. Testing the unhappy path this way is far cleaner than wrapping things in `try/catch` and a manual `fail()`.

One habit worth building early: **one logical thing per test**. Not literally one assertion, but one behavior. A test named `addsTwoNumbers` that also checks subtraction is a test that, when it fails, can't tell you which half broke.

## Lifecycle: setup without copy-paste

Most tests need a fresh object to work on. Writing `new Calculator()` at the top of every method works until you have twenty methods. The lifecycle annotations fix that.

```java
class OrderServiceTest {

    private OrderService service;

    @BeforeEach
    void setUp() {
        service = new OrderService();   // runs before EVERY @Test
    }

    @Test
    void startsEmpty() {
        assertEquals(0, service.itemCount());
    }

    @Test
    void countsAddedItems() {
        service.add("widget");
        assertEquals(1, service.itemCount());
    }
}
```

*What just happened:* `@BeforeEach` ran `setUp` once before *each* test method, handing both tests a brand-new `service`. That freshness is the point - `countsAddedItems` adding an item can't leak into `startsEmpty`, because the second test never sees the first one's object. Test isolation is non-negotiable; the day two tests share mutable state is the day "run them in a different order and they fail" enters your life.

The family: `@BeforeEach` / `@AfterEach` run around every test; `@BeforeAll` / `@AfterAll` run once for the whole class (and must be `static`, because there's no instance yet). Reach for `@BeforeAll` only for genuinely expensive shared setup - JUnit creates a fresh test-class instance per method by default precisely to keep tests independent.

## Parameterized tests: same logic, many inputs

When you'd otherwise copy a test five times with different numbers, that's the signal for a parameterized test. One method body, many runs.

```java
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

@ParameterizedTest
@ValueSource(strings = {"racecar", "level", "noon"})
void detectsPalindromes(String word) {
    assertTrue(Palindrome.isPalindrome(word));
}
```

*What just happened:* `@ParameterizedTest` replaces `@Test`, and `@ValueSource` fed the method three strings - so this ran three separate times, once per word, each reported individually. If `"level"` fails, you see *that* input named in the failure, not a vague "the palindrome test broke." For pairs of inputs and expected outputs, `@CsvSource({"2, 3, 5", "0, 0, 0"})` gives you `(a, b, expected)` columns.

> **For builders:** parameterized tests are where bugs hide and die. Edge cases - empty string, zero, negative, the boundary value - are cheap to add as one more row, and each row is a named, independent failure. The marginal cost of testing one more input is one line.

## How it actually runs

You rarely invoke JUnit by hand. Your build tool drives the platform, which discovers your Jupiter tests and runs them.

```bash
$ mvn test
[INFO] Running com.example.OrderServiceTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] Running com.example.CalculatorTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
```

*What just happened:* `mvn test` (Gradle's equivalent is `gradle test`) told Maven to compile and run everything under `src/test/java`. Maven handed the platform the test classes, the platform asked the Jupiter engine to run them, and you got a tally. "Failures" are failed assertions; "Errors" are unexpected exceptions - a distinction that tells you whether your code did the wrong thing or fell over entirely.

```quiz
[
  {
    "q": "What makes an ordinary Java method into a test JUnit will run?",
    "choices": ["It must be public and named test*", "The @Test annotation", "It must return a boolean", "It must live in a class ending in Test"],
    "answer": 1,
    "explain": "JUnit's runner discovers methods by the @Test annotation. Naming and the Test suffix are conventions, not requirements, and JUnit 5 methods need not be public."
  },
  {
    "q": "Why does @BeforeEach matter for test isolation?",
    "choices": ["It runs the slowest tests first", "It gives every test method a freshly built object so state can't leak between tests", "It runs only once for the whole class", "It marks a method as a parameterized test"],
    "answer": 1,
    "explain": "@BeforeEach runs before each test, rebuilding shared fixtures so one test's mutations can't bleed into another. @BeforeAll is the once-per-class one."
  },
  {
    "q": "What is the correct argument order for assertEquals?",
    "choices": ["actual, then expected", "expected, then actual", "order doesn't matter", "message, expected, actual only"],
    "answer": 1,
    "explain": "assertEquals(expected, actual). Reversing it produces backwards failure messages that waste debugging time."
  }
]
```


---

# Mocking with Mockito

JUnit can run a test, but it can't help you with the real problem of unit testing: the class you want to test rarely stands alone. Your `OrderService` calls a `PaymentGateway`, which calls a bank. Your `UserService` reads a `UserRepository`, which hits a database. You do not want your test to charge a real card or need a live database - you want to test *your* logic in isolation, with the collaborators replaced by stand-ins you control.

That's what Mockito is for. A **mock** is a fake object that implements the same interface as a real collaborator, but does nothing on its own. You tell it what to return when called (**stubbing**), and afterward you can ask it what it was called with (**verifying**). The class under test can't tell the difference - and that's the entire point. The conceptual background lives in /guides/mocking-and-test-doubles; here we make it concrete in Java.

## The shape of a Mockito test

Say `OrderService` needs a `PaymentGateway` to place an order. You don't want the real gateway; you want a fake whose answers you dictate.

```java
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;

class OrderServiceTest {

    @Test
    void placesOrderWhenPaymentSucceeds() {
        PaymentGateway gateway = mock(PaymentGateway.class);   // a fake
        when(gateway.charge(100)).thenReturn(true);            // stub it

        OrderService service = new OrderService(gateway);
        boolean placed = service.placeOrder(100);

        assertTrue(placed);
    }
}
```

*What just happened:* `mock(PaymentGateway.class)` built a fake gateway whose every method returns a harmless default (`false`, `0`, `null`) until told otherwise. `when(gateway.charge(100)).thenReturn(true)` is the stub: "if anyone calls `charge(100)`, hand back `true`." Then `OrderService` ran its real logic against that fake and we asserted the outcome. No bank, no network - pure logic.

Read `when(...).thenReturn(...)` out loud as a sentence: *when this method is called like this, then return that.* The call inside `when(...)` doesn't really execute the gateway - Mockito intercepts it to record the rule.

## Wiring mocks in with annotations

Building mocks by hand is fine for one collaborator. With several, the annotation style is cleaner and is what you'll see in most codebases.

```java
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.Mock;
import org.mockito.InjectMocks;

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock PaymentGateway gateway;        // a fresh mock per test
    @InjectMocks OrderService service;   // mocks pushed into its constructor

    @Test
    void placesOrderWhenPaymentSucceeds() {
        when(gateway.charge(100)).thenReturn(true);
        assertTrue(service.placeOrder(100));
    }
}
```

*What just happened:* `@ExtendWith(MockitoExtension.class)` hooks Mockito into JUnit's lifecycle. Before each test, `@Mock` creates a fresh `gateway`, and `@InjectMocks` constructs the real `OrderService` and passes the mocks into it. You skipped all the `mock(...)` and `new OrderService(...)` boilerplate, and - importantly - each test gets *fresh* mocks, so stubs from one test don't leak into the next. That extension also fails the build on stubs you set up but never use, which catches stale tests.

## Verifying interactions

Sometimes the thing you care about isn't a return value - it's whether a side effect happened. Did the order actually get saved? Was the email actually sent? `verify` answers that.

```java
@Test
void savesOrderAfterSuccessfulPayment() {
    when(gateway.charge(100)).thenReturn(true);

    service.placeOrder(100);

    verify(repository).save(any(Order.class));   // it WAS called
    verify(gateway, never()).refund(anyInt());   // refund was NOT called
}
```

*What just happened:* `verify(repository).save(...)` asserts that `service.placeOrder` called `save` exactly once during this test. `verify(gateway, never()).refund(...)` asserts the opposite - that a successful order never triggers a refund. Verification turns "I think it does the right thing" into "it provably called the right collaborator." `times(2)`, `atLeastOnce()`, and `never()` cover the counting cases.

## Argument matchers: when the exact value doesn't matter

In `verify(repository).save(any(Order.class))`, that `any(...)` is an **argument matcher**. You used it because the test doesn't care about the exact `Order` instance - only that *something* was saved. Matchers let you stub and verify by pattern instead of by exact value.

```java
when(repository.findById(anyLong())).thenReturn(Optional.of(user));
when(gateway.charge(eq(100))).thenReturn(true);
verify(emailer).send(eq("user@example.com"), contains("receipt"));
```

*What just happened:* `anyLong()` matches any long id; `eq(100)` matches exactly 100; `contains("receipt")` matches any string containing that word. There's one rule that bites everyone: **if you use a matcher for one argument, you must use matchers for all arguments in that call.** `gateway.charge(eq(100), userId)` throws at runtime - mixing a matcher with a raw value is the single most common Mockito error. Wrap the raw one as `eq(userId)` and it's fine.

```mermaid
flowchart LR
    T[Test] -->|stub: when/thenReturn| M[Mock collaborator]
    T -->|call| U[Unit under test]
    U -->|real logic| U
    U -->|delegates to| M
    M -->|canned answer| U
    T -->|verify interactions| M
```

*What just happened:* the diagram shows the loop - the test programs the mock, runs the real unit, the unit leans on the mock for its dependencies, and the test inspects the mock afterward. The unit under test is the only thing running real code.

> **In the wild:** the strongest tests stub the *inputs* (what collaborators return) and verify only the *outputs that matter* (the one or two side effects that define correct behavior). A test that stubs five methods and verifies all five is usually testing Mockito, not your code.

```quiz
[
  {
    "q": "What does when(gateway.charge(100)).thenReturn(true) do?",
    "choices": ["Calls the real charge method and caches its result", "Tells the mock to return true when charge is called with 100", "Verifies charge was already called with 100", "Asserts that charge returns true"],
    "answer": 1,
    "explain": "It's stubbing: it records a rule so the mock returns true for charge(100). The call inside when(...) is intercepted, not really executed."
  },
  {
    "q": "When must you use argument matchers for ALL arguments in a Mockito call?",
    "choices": ["Always, even with one argument", "Never; you can freely mix matchers and raw values", "Whenever you use a matcher for any one of the arguments", "Only inside verify, never inside when"],
    "answer": 2,
    "explain": "If any argument uses a matcher like any() or eq(), every argument in that call must use a matcher. Mixing a matcher with a raw value throws at runtime; wrap raw values in eq()."
  },
  {
    "q": "What is verify(repository).save(...) checking?",
    "choices": ["That save returns a non-null value", "That the unit under test actually called save during the test", "That save was stubbed beforehand", "That the repository is a real object, not a mock"],
    "answer": 1,
    "explain": "verify asserts an interaction happened - that the code under test called save. It checks behavior (a side effect), not a return value."
  }
]
```


---

# When tests lie

Here's the uncomfortable truth about a green test suite: passing tests prove your *tests* pass, not that your *code is correct*. Mockito makes this especially easy to get wrong, because a mock does exactly what you told it to - including agreeing with bugs. This phase is the set of failure modes that turn a test suite from a safety net into a wall of false confidence, and how to spot them before they cost you a production incident.

## The mock-too-much trap

This is the big one. When you mock every collaborator, your test stops describing reality and starts describing your *assumptions* about reality. If your assumption is wrong, the mock is wrong, and the test passes anyway.

```java
@Test
void calculatesDiscount() {
    // mocking the thing we're supposedly testing the math of
    when(pricingRules.discountFor(customer)).thenReturn(0.20);

    BigDecimal total = checkout.total(customer, items);

    verify(pricingRules).discountFor(customer);
    // ...but did the discount actually get APPLIED to the total? Untested.
}
```

*What just happened:* this test stubs the discount, then verifies the stub was *called* - but it never checks that the discount changed the total. If `checkout.total` fetches the discount and then ignores it, this test stays green while the customer gets charged full price. The mock answered the phone; nobody checked what was done with the answer. The fix is to assert on the real output (`assertEquals(new BigDecimal("80.00"), total)`), not on the fact that a collaborator was consulted.

The deeper rule: **don't mock the thing you're testing, and don't mock value objects you could build yourself.** If `pricingRules` is simple enough to instantiate with real data, use the real one. Mock the things that are slow, non-deterministic, or have side effects - the database, the clock, the payment gateway - not the things that are pure logic.

## Brittle verification: testing how, not what

Over-verification couples your test to the *implementation* instead of the *behavior*. Then a harmless refactor - same result, different internal calls - turns your suite red for no real reason.

```java
// brittle: asserts the exact sequence of internal calls
InOrder order = inOrder(cache, repository);
order.verify(cache).get("user:1");
order.verify(repository).findById(1L);
order.verify(cache).put("user:1", user);
```

*What just happened:* this locks in three calls in a specific order. The moment someone reorders the cache write, adds a metrics call, or swaps the caching strategy, this test fails - even though `getUser(1)` still returns the right user. The test is now an obstacle to good changes. Unless the *ordering itself is the contract* (rare, but real for things like "lock before write"), verify the outcome and skip the choreography.

A test that breaks every time you refactor working code isn't protecting you - it's taxing you. The signal of a healthy mock-based test: you can rewrite the method's internals freely, and as long as the behavior is unchanged, the test stays green.

## NullPointerException from an unstubbed mock

A mock returns "empty" defaults for anything you didn't stub - `null` for objects, `false` for booleans, `0` for numbers. That `null` is a landmine.

```java
@Test
void greetsUser() {
    // forgot to stub findById, so it returns Optional... no, plain null
    User user = userService.load(1L);   // NPE inside load()
    assertEquals("Alice", user.name());
}
```

*What just happened:* `findById` was never stubbed, so the mock returned `null`, and `load` exploded trying to use it. The error points *inside your production code*, so it looks like a bug there - but the real cause is a missing stub in the test. When a mock-based test throws an unexpected NPE, your first suspect should be an un-stubbed collaborator returning `null`, not a flaw in the code under test.

## Static, time, and the things mocks can't reach cleanly

Plenty of code reaches for `LocalDateTime.now()`, `Math.random()`, or a static factory deep in a method. Mockito can mock statics now (`mockStatic`), but reaching for it is often a smell - it usually means the dependency wasn't injected and should have been.

```java
// hard to test: time is grabbed internally
public boolean isExpired() {
    return expiry.isBefore(LocalDateTime.now());   // now() is uncontrollable
}

// testable: time comes in as a Clock you can fix in a test
public boolean isExpired(Clock clock) {
    return expiry.isBefore(LocalDateTime.now(clock));
}
```

*What just happened:* the first version bakes "now" into the method, so a test can't pin time down and can't reliably check the boundary. The second takes a `Clock`, and a test passes `Clock.fixed(...)` to make "now" deterministic. The lesson generalizes: when something is painful to test, the design - not the test tool - is usually telling you to inject the dependency instead of grabbing it.

> **In the wild:** the most valuable thing a mature team does is keep a few **un-mocked** tests that wire real objects together and only fake the true edges (DB, network, clock). Heavily-mocked unit tests verify each part in isolation; a handful of integration tests catch the wiring bugs that mocks define away. You want both, weighted toward the cheap unit tests but never zero of the integration ones.

## A quick gut-check before you commit a test

- Does it assert on a real **output**, or only that a collaborator was *called*?
- Would it survive a refactor that keeps behavior identical?
- Are you mocking slow/external things, or also mocking plain logic you could run for real?
- If it passes, would a genuine bug in this method make it fail? (If not, it's decoration.)

```quiz
[
  {
    "q": "What is the core danger of the 'mock too much' trap?",
    "choices": ["Mocks make tests run slower", "Tests verify that collaborators were called but never that the real output is correct, so bugs pass", "Mockito can't create more than three mocks per test", "Mocks always throw NullPointerException"],
    "answer": 1,
    "explain": "When you stub a value and only verify the stub was consulted, you never check the actual result. The code can ignore the value and the test still passes."
  },
  {
    "q": "A test throws an unexpected NullPointerException inside the production method. What should you suspect first in a mock-heavy test?",
    "choices": ["A bug in JUnit's runner", "An un-stubbed mock returning null by default", "The @Test annotation is missing", "assertEquals arguments are reversed"],
    "answer": 1,
    "explain": "Unstubbed mock methods return defaults - null for objects. The NPE surfaces in production code but is caused by the missing stub."
  },
  {
    "q": "Why is asserting an exact ordered sequence of internal calls usually a bad idea?",
    "choices": ["InOrder is deprecated in Mockito", "It couples the test to implementation, so a behavior-preserving refactor breaks it", "Ordered verification is slower than unordered", "You can only verify one call at a time"],
    "answer": 1,
    "explain": "Locking in the call sequence tests HOW the method works, not WHAT it does. A refactor with identical results turns the suite red for no real reason - unless the order itself is the contract."
  }
]
```
