Learning Session Summary

JUnit & Mockito

Two libraries, two jobs, working together inside the same test — JUnit runs the show; Mockito provides the stand-in actors

The Big Picture

Two libraries, two jobs

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.

LibraryRole
JUnitThe framework. Discovers test classes, runs them, provides @Test, @BeforeEach, lifecycle, assertions.
MockitoThe mocking library. Creates fake versions of collaborators so you can test one class in isolation.
Mental model

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.

How It Shows Up in Code

AuthServiceTest.java — the wiring
@ExtendWith(MockitoExtension.class) // JUnit hook that activates Mockito's annotations
class AuthServiceTest {

  @Mock private UserRepository userRepository; // fake — no real DB
  @Mock private PasswordEncoder passwordEncoder; // fake — no real bcrypt
  @Mock private TokenService tokenService; // fake — no real JWT signing

  @InjectMocks private AuthService authService; // the REAL thing under test
}
@ExtendWith
JUnit 5 hook. Tells JUnit "while running this class, also let Mockito process its annotations."
@Mock
Replace this field with a fake. The fake has all the real methods, but they all return null/0/empty by default until you tell them otherwise.
@InjectMocks
Create the real class under test, with the @Mock fields wired in as its dependencies. No Spring context, no factory — just constructor / setter injection by type.
@BeforeEach
Pure JUnit. Runs before every @Test — used here to build fresh request DTOs so tests don't share mutable state.

A Typical Test, Beat by Beat

Arrange → Act → Assert
@Test
void register_throws_when_email_exists() {
  // Arrange — script the fake
  when(userRepository.existsByEmail("vera@example.com")).thenReturn(true);

  // Act + Assert (combined here via AssertJ)
  assertThatThrownBy(() -> authService.register(registerRequest))
    .hasMessage("Email already exists");

  // Verify — assert the fake was NOT called
  verify(userRepository, never()).save(any());
}
when().thenReturn()
Script: "when this method is called with these args, return that value." Lets you simulate any DB state instantly — empty, full, error.
verify()
Assert that a method was called (or wasn't). With never(), times(n), or atLeastOnce(). Lets you prove your code talks to its collaborators correctly.
Why the verify line matters

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."

What Mockito Gives You That JUnit Alone Can't

① Isolation

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.

② Control

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.

③ Verification

verify(repo).save(captor.capture()) proves your service called the repository correctly, and lets you inspect what it tried to save with an ArgumentCaptor.

④ Edge cases on demand

Simulate "DB throws an exception" with one line: when(repo.save(any())).thenThrow(new RuntimeException(...)). No real database breaking required.

When You Don't Need Mockito

Pure unit tests on self-contained code

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:

@Test
void generates_and_validates_token() {
  String token = jwtUtil.generateToken("vera@example.com", 1L);
  assertThat(jwtUtil.validateToken(token)).isTrue();
}
Rule of thumb

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.

Putting It Together

JUnit
finds tests,
runs @Test methods
MockitoExtension
processes @Mock /
@InjectMocks before each test
Test body
when().thenReturn()
+ assertions + verify()
JUnit reports
pass / fail
+ assertion message
Where each tool's responsibility ends
  • JUnit never knows the dependencies are fake — it just runs the test method and checks the assertions.
  • Mockito never knows about @Test — it cares about field-level annotations and the method calls inside the test body.
  • Your code doesn't know either — AuthService calls userRepository.existsByEmail(...) the same way it would in production. The fake just answers differently.

TL;DR

ConceptWhat it is
JUnitFramework that runs tests, provides @Test / @BeforeEach / assertions.
MockitoLibrary 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 whenThe class under test has no slow / expensive / non-deterministic collaborators (e.g., JwtUtil).

Key Takeaway

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.