Lesson 37 • Advanced

    Unit Testing with JUnit & Mockito

    Tests aren't just "nice to have" — they're your safety net. Every time you refactor, add features, or fix bugs, tests tell you instantly if something broke. JUnit 5 + Mockito is the gold standard for Java testing, and mastering this combo makes you a reliable developer that teams trust.

    Before You Start

    You should know OOP (classes, interfaces for mocking) and Annotations (JUnit is annotation-driven). Familiarity with Exception Handling helps for assertThrows testing.

    What You'll Learn

    • ✅ JUnit 5 lifecycle: @Test, @BeforeEach, @AfterEach
    • ✅ Assertions: assertEquals, assertThrows, assertAll
    • ✅ Parameterized tests with @ParameterizedTest
    • ✅ Mocking dependencies with Mockito
    • ✅ Test-Driven Development (TDD) workflow
    • ✅ Code coverage and testing best practices

    1️⃣ The Testing Pyramid

    Analogy: Unit tests are like spell-checking individual words — fast, cheap, catch most errors. Integration tests check that sentences make sense together. E2E tests verify the whole essay reads correctly. You want a LOT of unit tests, some integration tests, and few E2E tests.

    LevelSpeedScopeTarget
    Unit⚡ msSingle method/class70% of tests
    Integration🔄 secondsMultiple components + DB20% of tests
    E2E🐢 minutesFull application10% of tests

    Try It: JUnit Test Suite

    Try it Yourself »
    JavaScript
    // 💡 Try modifying this code and see what happens!
    // Build a complete test suite with assertions
    
    console.log("=== JUnit Test Suite ===\n");
    
    // Mini test framework
    let passed = 0, failed = 0;
    function assertEquals(expected, actual, msg) {
      if (expected === actual) { passed++; console.log("  ✅ " + msg); }
      else { failed++; console.log("  ❌ " + msg + " (expected " + expected + ", got " + actual + ")"); }
    }
    function assertTrue(condition, msg) {
      if (condition) { passed++; console.log("  ✅ " +
    ...

    2️⃣ Arrange → Act → Assert

    Every well-structured test follows this three-step pattern. It keeps your tests readable and focused:

    1. Arrange: Set up test data and dependencies

    2. Act: Call the method you're testing

    3. Assert: Verify the result is what you expected

    Try It: Mockito — Isolating Dependencies

    Try it Yourself »
    JavaScript
    // 💡 Try modifying this code and see what happens!
    // Simulating Mockito — mocking external dependencies
    
    console.log("=== Mockito — Mocking Dependencies ===\n");
    
    // The problem: UserService depends on UserRepository (database)
    // We can't test UserService without a real database... or CAN we?
    
    // Mock UserRepository (simulating @Mock)
    class MockUserRepository {
      constructor() { this.calls = []; this.returnValues = {}; }
      
      // when(mock.method(args)).thenReturn(value)
      whenFindById(id, ret
    ...

    Common Mistakes

    • ⚠️ Testing implementation, not behavior: Don't assert that a specific private method was called. Assert the outcome
    • ⚠️ Testing too much per test: One logical concept per test. If it fails, you should immediately know why
    • ⚠️ Not testing edge cases: Test null inputs, empty lists, boundary values, error conditions
    • ⚠️ Mocking everything: Only mock external dependencies (DB, API). Test real logic with real objects
    • ⚠️ Ignoring flaky tests: A test that sometimes passes is worse than no test — it erodes trust

    Pro Tips

    • 💡 @DisplayName — give tests human-readable names: @DisplayName("should throw when email is blank")
    • 💡 @ParameterizedTest — run the same test with 20 different inputs in one declaration
    • 💡 assertAll() — check multiple assertions at once. All failures are reported, not just the first
    • 💡 TDD workflow: 🔴 Write failing test → 🟢 Write minimal code to pass → 🔵 Refactor while tests stay green

    Try It: Parameterized Tests & TDD

    Try it Yourself »
    JavaScript
    // 💡 Try modifying this code and see what happens!
    // Parameterized tests and TDD workflow
    
    console.log("=== Parameterized Tests & TDD ===\n");
    
    // TDD Example: Build a password validator step by step
    
    // 🔴 RED: Write failing tests first
    console.log("1. TDD STEP 1 — 🔴 RED (write tests first):");
    console.log("  Tests define the requirements BEFORE writing code\n");
    
    // 🟢 GREEN: Implement to make tests pass
    class PasswordValidator {
      validate(password) {
        let errors = [];
        if (!password
    ...

    📋 Quick Reference

    AnnotationPurposeExample
    @TestMark test methodRequired on each test
    @BeforeEachSetup before each testInitialize fresh objects
    @ParameterizedTestRun with multiple inputs@CsvSource, @ValueSource
    @MockCreate mock objectMockito annotation
    verify()Check mock was calledverify(mock).method()

    🎉 Lesson Complete!

    You can now write professional unit tests that catch bugs before users do! Next: Maven & Gradle — build tools and dependency management.

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service