0
0
JUnittesting~15 mins

Why different doubles serve different purposes in JUnit - Automation Benefits in Action

Choose your learning style9 modes available
Verify behavior of different test doubles in a payment processing system
Preconditions (3)
Step 1: Use DummyPaymentGateway to create a PaymentProcessor instance and call processPayment with any amount
Step 2: Use StubPaymentGateway that returns true for processPayment and verify PaymentProcessor accepts payment
Step 3: Use FakePaymentGateway that simulates real payment logic and verify PaymentProcessor behavior
Step 4: Use SpyPaymentGateway to record if processPayment was called and verify the call count
Step 5: Use MockPaymentGateway to expect processPayment call with specific amount and verify interaction
✅ Expected Result: Each test double behaves according to its purpose: Dummy is a placeholder, Stub returns canned data, Fake has working logic, Spy records calls, Mock verifies interactions
Automation Requirements - JUnit 5 with Mockito
Assertions Needed:
Assert that PaymentProcessor processes payment correctly with Stub and Fake
Verify Spy records method calls
Verify Mock expects specific method calls with correct parameters
Best Practices:
Use Mockito for creating mocks and spies
Use clear and descriptive test method names
Use assertions from JUnit Jupiter Assertions
Keep tests independent and focused on one double per test
Automated Solution
JUnit
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

import org.junit.jupiter.api.Test;

interface PaymentGateway {
    boolean processPayment(double amount);
}

class PaymentProcessor {
    private final PaymentGateway gateway;

    public PaymentProcessor(PaymentGateway gateway) {
        this.gateway = gateway;
    }

    public boolean makePayment(double amount) {
        return gateway.processPayment(amount);
    }
}

// Dummy: does nothing, just placeholder
class DummyPaymentGateway implements PaymentGateway {
    public boolean processPayment(double amount) {
        return false; // not used
    }
}

// Stub: returns fixed value
class StubPaymentGateway implements PaymentGateway {
    public boolean processPayment(double amount) {
        return true; // always success
    }
}

// Fake: simple working implementation
class FakePaymentGateway implements PaymentGateway {
    public boolean processPayment(double amount) {
        return amount > 0; // success if amount positive
    }
}

// Spy and Mock will be created by Mockito

public class PaymentProcessorTest {

    @Test
    void testWithDummy() {
        PaymentGateway dummy = new DummyPaymentGateway();
        PaymentProcessor processor = new PaymentProcessor(dummy);
        // Dummy does nothing, result is false
        assertFalse(processor.makePayment(100));
    }

    @Test
    void testWithStub() {
        PaymentGateway stub = new StubPaymentGateway();
        PaymentProcessor processor = new PaymentProcessor(stub);
        assertTrue(processor.makePayment(100));
    }

    @Test
    void testWithFake() {
        PaymentGateway fake = new FakePaymentGateway();
        PaymentProcessor processor = new PaymentProcessor(fake);
        assertTrue(processor.makePayment(50));
        assertFalse(processor.makePayment(0));
    }

    @Test
    void testWithSpy() {
        PaymentGateway real = new StubPaymentGateway();
        PaymentGateway spy = spy(real);
        PaymentProcessor processor = new PaymentProcessor(spy);

        processor.makePayment(200);
        processor.makePayment(300);

        verify(spy, times(2)).processPayment(anyDouble());
    }

    @Test
    void testWithMock() {
        PaymentGateway mock = mock(PaymentGateway.class);
        when(mock.processPayment(150)).thenReturn(true);

        PaymentProcessor processor = new PaymentProcessor(mock);
        boolean result = processor.makePayment(150);

        assertTrue(result);
        verify(mock).processPayment(150);
    }
}

This test class shows how different test doubles serve different purposes.

Dummy is a placeholder that does nothing; it helps satisfy dependencies but is not used.

Stub returns fixed data to simulate a successful payment.

Fake has simple logic to simulate real behavior.

Spy wraps a real object and records method calls, verified with verify().

Mock is a fully controlled double where we set expectations and verify interactions.

Each test method focuses on one double type, uses clear assertions, and follows best practices for readability and maintainability.

Common Mistakes - 4 Pitfalls
Using Dummy as if it returns meaningful data
Not verifying interactions when using Mocks
Using real implementations instead of Fakes for complex logic
Not isolating tests and mixing multiple doubles in one test
Bonus Challenge

Now add data-driven testing with 3 different payment amounts using Stub and verify results

Show Hint