Mock

Glossary

A mock is a test double that is preconfigured with expectations about how it should be called, and which verifies those expectations during a test. Where a stub simply returns canned values, a mock asserts on the interactions – which methods were invoked, with what arguments, and how often.

How a mock works

A mock is supplied to the unit under test in place of a real collaborator, usually via dependency injection. It records every call it receives, and the test then checks those calls against its expectations. This is interaction verification: the test passes or fails based on the conversation between objects, not on any resulting state.

Most languages have a library that generates mocks so you do not hand-write the recording logic, but the shape is always the same – capture the calls, then assert on them. A double that records passively and leaves those assertions to the test is a spy; a mock goes further by baking the expectations in and failing itself when they are not met.

The tradeoff

Because a mock asserts on how a unit calls its collaborators, it couples the test to the implementation. A test that demands Save be called exactly once breaks the moment you batch two writes, rename the method, or reorder the steps – even when the externally observable behavior is identical. Over-mocked suites tend to be brittle and to restate the implementation back to itself, which is precisely what a test should not do.

For that reason, prefer a more realistic double and assert on the outcome wherever the outcome is observable. Use a fake plus state verification for collaborators like repositories, and reserve mocks for the narrow case where the call itself is the behavior under test – there is no state to inspect, so the interaction is the observable result.

Example

Sending a notification is a good fit for a mock: when an order is placed, the service is supposed to notify the customer, and that outgoing call is the whole point. There is no local state to assert on, so the test verifies the interaction directly.

// orders.hpp -- the abstraction and the unit under test.

// Notifier sends a message to a recipient.
class Notifier {
public:
  virtual ~Notifier() = default;
  virtual auto send(const std::string& to, const std::string& message) -> void = 0;
};

// OrderService places orders and confirms them to the customer.
class OrderService {
public:
  explicit OrderService(Notifier& notifier) : m_notifier(&notifier) {}

  auto place(const std::string& customer) -> void;

private:
  Notifier* m_notifier;
};
// A mock: records the recipients it was asked to notify.
class MockNotifier final : public Notifier {
public:
  auto send(const std::string& to, const std::string& /*message*/) -> void override {
    m_recipients.push_back(to);
  }

  auto recipients() const -> const std::vector<std::string>& { return m_recipients; }

private:
  std::vector<std::string> m_recipients;
};

TEST_CASE("place notifies the customer") {
  // Arrange
  MockNotifier notifier;
  OrderService service{notifier};

  // Act
  service.place("ada@example.com");

  // Assert
  REQUIRE(notifier.recipients() == std::vector<std::string>{"ada@example.com"});
}
package orders

// Notifier sends a message to a recipient.
type Notifier interface {
  Send(to, message string) error
}

// Service places orders and confirms them to the customer.
type Service struct {
  notifier Notifier
}

func NewService(notifier Notifier) *Service {
  return &Service{notifier: notifier}
}

// Place confirms the order to the customer.
func (s *Service) Place(customer string) error {
  // ... persist the order ...
  return s.notifier.Send(customer, "Your order is confirmed")
}
package orders_test

// mockNotifier records the recipients it was asked to notify.
type mockNotifier struct {
  recipients []string
}

func (m *mockNotifier) Send(to, message string) error {
  m.recipients = append(m.recipients, to)
  return nil
}

func TestPlaceNotifiesCustomer(t *testing.T) {
  t.Parallel()

  // Arrange
  notifier := &mockNotifier{}
  service := orders.NewService(notifier)

  // Act
  err := service.Place("ada@example.com")

  // Assert
  if err != nil {
    t.Fatalf("Place() returned an unexpected error: %v", err)
  }
  if len(notifier.recipients) != 1 || notifier.recipients[0] != "ada@example.com" {
    t.Errorf("notified %v, want one message to %q", notifier.recipients, "ada@example.com")
  }
}
# orders.py -- the abstraction and the unit under test.
from typing import Protocol

# Notifier sends a message to a recipient.
class Notifier(Protocol):
  def send(self, to: str, message: str) -> None: ...

# Service places orders and confirms them to the customer.
class Service:
  def __init__(self, notifier: Notifier) -> None:
    self._notifier = notifier

  def place(self, customer: str) -> None:
    # ... persist the order ...
    self._notifier.send(customer, "Your order is confirmed")
from unittest.mock import ANY, Mock

from orders import Notifier, Service

def test_place_notifies_the_customer():
  # Arrange
  notifier = Mock(spec=Notifier)
  service = Service(notifier)

  # Act
  service.place("ada@example.com")

  # Assert
  notifier.send.assert_called_once_with("ada@example.com", ANY)
// src/lib.rs -- the abstraction and the unit under test.

/// Notifier sends a message to a recipient.
pub trait Notifier {
  fn send(&self, to: &str, message: &str);
}

/// OrderService places orders and confirms them to the customer.
pub struct OrderService<'a> {
  notifier: &'a dyn Notifier,
}

impl<'a> OrderService<'a> {
  pub fn new(notifier: &'a dyn Notifier) -> Self {
    Self { notifier }
  }

  pub fn place(&self, customer: &str) {
    // ... persist the order ...
    self.notifier.send(customer, "Your order is confirmed");
  }
}
// tests/orders.rs -- a separate crate that sees only the public API.
use std::cell::RefCell;

use orders::{Notifier, OrderService};

// A mock: records the recipients it was asked to notify.
#[derive(Default)]
struct MockNotifier {
  recipients: RefCell<Vec<String>>,
}

impl Notifier for MockNotifier {
  fn send(&self, to: &str, _message: &str) {
    self.recipients.borrow_mut().push(to.to_string());
  }
}

#[test]
fn place_notifies_the_customer() {
  // Arrange
  let notifier = MockNotifier::default();
  let service = OrderService::new(&notifier);

  // Act
  service.place("ada@example.com");

  // Assert
  assert_eq!(*notifier.recipients.borrow(), ["ada@example.com"]);
}

Note

Notice the mock only asserts that the customer was notified, not what the rest of the system now looks like. If the test also cared about, say, the order being persisted, that part should be checked with a fake repository and a state assertion – not another mock.

References

  • Mocks Aren't Stubs – the reference on when interaction verification earns its place, and when it does not.
  • Mock Object – Gerard Meszaros’ catalog entry for the pattern.
  • Mock object – short overview of mocks and interaction testing.