Sobes.tech
Middle

What are Mock, Stab, and Spy in the context of software testing?

sobes.tech AI

Answer from AI

  • Mock: A fake object that checks whether the tested code interacts with it as expected (calls certain methods with specific arguments and in the correct order). Mocks verify behavior.
  • Stub: A fake object that provides predefined responses to method calls. Stubs are used to control the state of dependencies and focus on the tested object, not on interactions with dependencies. Stubs verify state.
  • Spy: A partially fake object that wraps a real object. It allows tracking calls to its methods (arguments, number of calls, etc.), while the real method can still be called. Spies are good for verifying that the real object was used and for checking its behavior.
Type Main purpose Checks
Mock Interaction verification Behavior
Stub Providing data State
Spy Tracking calls to real object Behavior and state
// Example of using Mock with Mockito
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.mockito.Mockito.*;

public class ServiceTest {

    @Mock
    private Dependency dependencyMock;

    private Service service;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        service = new Service(dependencyMock);
    }

    @Test
    void shouldCallDependencyMethod() {
        service.doSomething();
        // Verify that dependencyMethod was called once
        verify(dependencyMock, times(1)).dependencyMethod();
    }
}

class Service {
    private final Dependency dependency;

    public Service(Dependency dependency) {
        this.dependency = dependency;
    }

    public void doSomething() {
        dependency.dependencyMethod();
    }
}

class Dependency {
    public void dependencyMethod() {
        // Real logic
    }

    public String getData() {
        return "real data";
    }
}
// Example of using Stub with Mockito
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

public class AnotherServiceTest {

    @Mock
    private Dependency dependencyStub;

    private AnotherService anotherService;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        anotherService = new AnotherService(dependencyStub);
    }

    @Test
    void shouldReturnStubbedData() {
        // Set predefined response for getData()
        when(dependencyStub.getData()).thenReturn("stubbed data");

        String result = anotherService.processData();

        // Verify that the service returns data from the stub
        assertEquals("processed stubbed data", result);
    }
}

class AnotherService {
    private final Dependency dependency;

    public AnotherService(Dependency dependency) {
        this.dependency = dependency;
    }

    public String processData() {
        String data = dependency.getData();
        return "processed " + data;
    }
}
// Example of using Spy with Mockito
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Spy;
import org.mockito.MockitoAnnotations;

import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class YetAnotherServiceTest {

    @Spy
    private Dependency dependencySpy = new Dependency(); // Spies wrap real object

    private YetAnotherService yetAnotherService;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        yetAnotherService = new YetAnotherService(dependencySpy);
    }

    @Test
    void shouldCallRealAndStubPartial() {
        // Can call real method
        yetAnotherService.callSpyMethod();
        // And verify it was called
        verify(dependencySpy, times(1)).dependencyMethod();

        // Can override behavior for a specific method
        when(dependencySpy.getData()).thenReturn("stubbed data for spy");
        String result = yetAnotherService.getDataFromSpy();

        // Verify that overridden data was returned
        assertEquals("stubbed data for spy", result);

        // Also verify that getData() was called
        verify(dependencySpy, times(1)).getData();
    }
}

class YetAnotherService {
    private final Dependency dependency;

    public YetAnotherService(Dependency dependency) {
        this.dependency = dependency;
    }

    public void callSpyMethod() {
        dependency.dependencyMethod();
    }

    public String getDataFromSpy() {
        return dependency.getData();
    }
}