0
0
JUnittesting~10 mins

Why different doubles serve different purposes in JUnit - Test Execution Impact

Choose your learning style9 modes available
Test Overview

This test demonstrates how different test doubles (dummy, stub, mock, spy) serve different purposes in unit testing. It verifies that each double behaves as expected in a simple calculator service scenario.

Test Code - JUnit 5 with Mockito
JUnit
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

interface CalculatorService {
    int add(int a, int b);
    int subtract(int a, int b);
}

class Calculator {
    private final CalculatorService service;
    public Calculator(CalculatorService service) {
        this.service = service;
    }
    public int performAddition(int a, int b) {
        return service.add(a, b);
    }
    public int performSubtraction(int a, int b) {
        return service.subtract(a, b);
    }
}

public class CalculatorTest {

    @Test
    void testWithDummy() {
        // Dummy: passed but not used
        CalculatorService dummyService = new CalculatorService() {
            public int add(int a, int b) { return 0; }
            public int subtract(int a, int b) { return 0; }
        };
        Calculator calc = new Calculator(dummyService);
        int result = calc.performAddition(5, 3);
        assertEquals(0, result, "Dummy returns default 0");
    }

    @Test
    void testWithStub() {
        // Stub: returns fixed value
        CalculatorService stubService = new CalculatorService() {
            public int add(int a, int b) { return 8; }
            public int subtract(int a, int b) { return 0; }
        };
        Calculator calc = new Calculator(stubService);
        int result = calc.performAddition(5, 3);
        assertEquals(8, result, "Stub returns fixed sum");
    }

    @Test
    void testWithMock() {
        // Mock: verifies interaction
        CalculatorService mockService = Mockito.mock(CalculatorService.class);
        Mockito.when(mockService.add(5, 3)).thenReturn(8);
        Calculator calc = new Calculator(mockService);
        int result = calc.performAddition(5, 3);
        assertEquals(8, result, "Mock returns stubbed sum");
        Mockito.verify(mockService).add(5, 3);
    }

    @Test
    void testWithSpy() {
        // Spy: partial real behavior + verification
        CalculatorService realService = new CalculatorService() {
            public int add(int a, int b) { return a + b; }
            public int subtract(int a, int b) { return a - b; }
        };
        CalculatorService spyService = Mockito.spy(realService);
        Calculator calc = new Calculator(spyService);
        int result = calc.performAddition(5, 3);
        assertEquals(8, result, "Spy calls real method");
        Mockito.verify(spyService).add(5, 3);
    }
}
Execution Trace - 4 Steps
StepActionSystem StateAssertionResult
1Test starts with dummy doubleCalculator created with dummy service that returns 0Assert result equals 0PASS
2Test runs with stub doubleCalculator created with stub service returning fixed 8 for addAssert result equals 8PASS
3Test runs with mock doubleCalculator created with mock service; add(5,3) stubbed to 8Assert result equals 8 and verify add(5,3) calledPASS
4Test runs with spy doubleCalculator created with spy wrapping real service; add method realAssert result equals 8 and verify add(5,3) calledPASS
Failure Scenario
Failing Condition: Mock verification fails because add method was not called
Execution Trace Quiz - 3 Questions
Test your understanding
Which test double returns a fixed value without real logic?
ASpy
BMock
CStub
DDummy
Key Result
Use the right test double for your goal: dummy for placeholders, stub for fixed returns, mock for interaction checks, and spy for partial real behavior with verification.