Mock vs Stub vs Spy
Mocks verify interactions, stubs provide controlled responses, and spies observe or wrap real behavior. In Java backend tests, choosing the right test double keeps tests focused on either behavior or collaboration.
A stub replaces a dependency with predetermined behavior. You configure it to return specific values or throw exceptions, allowing the test to exercise the code under known conditions without relying on the real dependency.
A mock is primarily used to verify how the system under test interacts with a dependency. You can configure responses when necessary, but the important assertion is usually that a method was called with particular arguments, a particular number of times, or not called at all.
A spy wraps or records calls around a real object. It can execute the object's actual implementation while allowing the test to observe selected interactions or override particular methods. This makes spies useful when most real behavior is desirable but some observation or substitution is needed.
In Java, frameworks such as Mockito provide these capabilities through APIs such as `mock()`, `when(...).thenReturn(...)`, and `spy()`. The exact API differs by framework, but the conceptual distinction remains: stubs control inputs and outputs, mocks emphasize collaboration verification, and spies retain real behavior while adding observation or selective control.
Key points
- Stub: supplies controlled responses or exceptions.
- Mock: verifies interactions with a dependency.
- Spy: wraps a real object and can observe or selectively override behavior.
- Use test doubles to isolate the unit from external or nondeterministic dependencies.
Interview questions
What is the difference between a mock, a stub, and a spy?
A stub provides predefined results so the test can control a dependency. A mock is used primarily to verify interactions, such as whether a method was called with expected arguments. A spy wraps a real object and allows the test to observe or selectively alter its behavior. The choice depends on whether the test needs controlled output, interaction verification, or real behavior with observation.
Why would you use a stub in a unit test?
A stub makes a dependency predictable. For example, a service that depends on a repository can receive a stubbed repository that returns a known entity or throws a known exception. This lets the test focus on the service's behavior without requiring a database or other external system. It also makes edge cases easier to reproduce.
How does Mockito support mocks and stubs?
Mockito can create a mock with `mock(Type.class)`. A test can configure that mock with `when(...).thenReturn(...)` or related stubbing APIs. Interaction verification is performed with methods such as `verify(...)`. Thus, one Mockito object can both provide stubbed behavior and support interaction verification, even though the concepts describe different testing purposes.
What is a spy, and when can it be useful?
A spy wraps an existing real object so its real methods can normally execute while the test records or overrides selected behavior. It can be useful when most of an object's implementation is appropriate for the test but one method needs controlled behavior or its calls need verification. However, spies can make tests more coupled to implementation details and may execute unwanted side effects. A mock is often clearer when complete isolation is sufficient.
How do you decide whether to verify state or interactions?
Prefer state or observable behavior when the interaction itself is not part of the contract being tested. Interaction verification is useful when collaboration is an important behavioral requirement, such as ensuring a payment is charged exactly once or a notification is not sent after a failure. Excessive verification can couple tests to implementation details and make harmless refactoring break tests. The goal is to assert meaningful behavior rather than the internal sequence of method calls.