Mock vs Spy in Unit Testing

Mocks provide fully controlled collaborators, while spies wrap real implementations and let you selectively override behavior. The distinction is mainly about whether the test replaces the collaborator's implementation or retains real behavior.

A mock is a test double whose behavior is defined by the test rather than by the real collaborator. In Java, a mocking framework such as Mockito can create a mock and configure return values with `when(...).thenReturn(...)`. Calls do not execute the collaborator's production implementation unless explicitly configured to do so.

A spy is usually created from a real object and retains that object's actual behavior by default. Mockito spies therefore allow a test to exercise real methods while replacing selected interactions when needed. This makes spies useful when most of the real implementation is desirable but one dependency or behavior needs controlled treatment.

Both mocks and spies can verify interactions, such as whether a method was called with particular arguments. The important difference is that interaction verification does not determine whether an object is a mock or spy; the key distinction is whether the underlying real implementation normally executes.

Mocks generally provide stronger isolation because the test controls the collaborator completely. Spies can make tests more coupled to implementation details and can accidentally execute real side effects, so they should be used deliberately, especially around I/O, persistence, networking, or expensive computation.

Key points

Interview questions

What is the difference between a mock and a spy?

A mock is a replacement object whose behavior is controlled by the test. A spy wraps a real object and normally calls its actual implementation. With Mockito, a mock commonly requires explicit stubbing for the behavior the test needs, while a spy can use real behavior unless a method is overridden. Both can support verification of interactions.

Why might you choose a mock instead of a spy in a unit test?

A mock gives the test stronger isolation because the collaborator's production implementation does not normally run. This is useful when the collaborator performs I/O, network calls, database operations, or expensive work. It also makes the test's expected collaborator behavior explicit. A spy is more appropriate when retaining most real behavior is intentional.

How does Mockito create and configure mocks and spies?

Mockito can create a mock with `mock(Type.class)` and configure it with stubbing such as `when(mock.method()).thenReturn(value)`. A spy can be created with `spy(realObject)`, which preserves the real object's behavior unless an interaction is stubbed. For spies, `doReturn(...).when(spy).method(...)` is often useful because it avoids invoking the real method during stubbing. Verification can then be performed with Mockito's `verify` API.

What are the design concerns with using spies extensively?

Frequent use of spies can indicate that a class has too many responsibilities or that its methods are difficult to isolate. Because real code executes by default, a spy can also introduce hidden coupling to implementation details and unintended side effects. Tests may become fragile when internal method structure changes even though externally visible behavior remains correct. Prefer clear boundaries and dependency injection when those changes can provide cleaner isolation.

Can both mocks and spies verify interactions? What does that verification actually prove?

Yes, both can be verified with a framework such as Mockito. Verification establishes that a specified interaction occurred, such as a method being called with particular arguments. It does not by itself prove that the collaborator's implementation produced the correct result. A good unit test therefore combines interaction assertions with appropriate state or returned-value assertions where those behaviors matter.