Two libraries, two jobs, working together inside the same test — JUnit runs the show; Mockito provides the stand-in actors
JUnit is the test framework — it discovers test classes, runs them, and provides assertions and lifecycle hooks. Mockito is a mocking library — it creates fake versions of collaborators so you can test one class in isolation, without booting a database or network.
| Library | Role |
|---|---|
| JUnit | The framework. Discovers test classes, runs them, provides @Test, @BeforeEach, lifecycle, assertions. |
| Mockito | The mocking library. Creates fake versions of collaborators so you can test one class in isolation. |
JUnit is the stage — it sets up the test, runs it, judges pass/fail. Mockito provides the stand-in actors — fake collaborators that follow your script so you can rehearse one performer's lines without the rest of the cast.
AuthServiceTest.java — the wiring@Mock fields wired in as its dependencies. No Spring context, no factory — just constructor / setter injection by type.@Test — used here to build fresh request DTOs so tests don't share mutable state.never(), times(n), or atLeastOnce(). Lets you prove your code talks to its collaborators correctly.
verify(userRepository, never()).save(any()) is what proves the service didn't accidentally save a duplicate to the DB before throwing. Without that line, the test only checks the message — not the safety guarantee. Mockito's verify is the difference between "the right thing happened" and "the wrong thing didn't happen."
Test AuthService without booting Postgres or Spring's full context. Tests run in milliseconds — a full service-test suite finishes faster than one integration test would start.
when(repo.findByEmail(x)).thenReturn(...) lets you simulate any DB state instantly: empty, full, returning a specific row, even throwing an exception. No fixture files, no @Sql scripts.
verify(repo).save(captor.capture()) proves your service called the repository correctly, and lets you inspect what it tried to save with an ArgumentCaptor.
Simulate "DB throws an exception" with one line: when(repo.save(any())).thenThrow(new RuntimeException(...)). No real database breaking required.
JwtUtilTest in this repo skips Mockito entirely. JwtUtil takes a string secret and returns a string token — no collaborators, no I/O. Plain JUnit + assertions are enough:
Mock when the class under test depends on something slow, expensive, or non-deterministic (DB, HTTP, time, randomness). Skip it when the class is a self-contained pure function of its inputs.
@Test methods
@Test — it cares about field-level annotations and the method calls inside the test body.AuthService calls userRepository.existsByEmail(...) the same way it would in production. The fake just answers differently.| Concept | What it is |
|---|---|
| JUnit | Framework that runs tests, provides @Test / @BeforeEach / assertions. |
| Mockito | Library that creates fake collaborators inside JUnit tests, used via @Mock / @InjectMocks. |
| @ExtendWith(MockitoExtension.class) | JUnit hook that lets Mockito process annotations. |
| when().thenReturn() | Script a fake's response to a method call. |
| verify() | Assert a fake was (or wasn't) called. |
| Skip Mockito when | The class under test has no slow / expensive / non-deterministic collaborators (e.g., JwtUtil). |
JUnit and Mockito don't compete — they complement. JUnit is the stage; Mockito is the stand-in actor. Use JUnit alone for pure functions, JUnit + Mockito when the class under test reaches outward to dependencies you don't want booting up for every test run.