munotes®

Unit Testing Techniques: Drivers, Stubs and Test Doubles

Get access to whole semester resourcesSemester Pass

Chapter Thirty-Four

Syllabus topic Module 1, "Software Testing Strategies: Unit Testing: purpose, techniques"

Pages 188 to 192 of 622

In one line

A unit rarely runs on its own: something must call it and it usually calls other things; unit testing techniques supply a driver to call the unit and check its results, and stubs or other test doubles to stand in for everything the unit depends on.

In the wording a student can write in an examination: a test driver is a "software module used to invoke a module under test and, often, provide test inputs, control and monitor execution, and report test results"; a stub is a "skeletal or special-purpose implementation of a software module, used to develop or test a module that calls or is otherwise dependent on it" (both ISO/IEC/IEEE 24765). Together they form the test harness around the unit. Test double is the general term for "any kind of pretend object used in place of a real object for testing purposes", and there are five kinds: dummy, fake, stub, spy and mock (Meszaros's vocabulary, as Fowler gives it).

Why a unit needs a harness

Take ExamReg's fee payment. The unit FeeService.pay looks up the student, works out how many days late the form is from today's date, computes the fee, charges the payment gateway and emails a receipt. Testing it for real would mean a real student database, waiting for the right date, a real payment and a real email. None of that belongs in a unit test: it is slow, it is not repeatable, and the payment would cost real money.

So the unit is tested inside a harness, which the vocabulary calls "scaffolding code written for the purpose of exercising lower-level code when the higher-level code that will ultimately exercise it is not yet available". The harness has two sides.

  • Above the unit, a driver. It plays the part of whatever will eventually call the unit: it sets up the inputs, calls the unit, and checks and reports the result. In modern practice the test itself is the driver.
  • Below the unit, stubs and other doubles. They play the parts of whatever the unit calls: the database, the clock, the gateway, the mailer. They are controlled by the test, so the unit can be exercised completely and repeatably.
A driver calls the unit under test, which calls a fake, a stub, a mock, a spy, and is given a dummy it never calls

Figure 34.1 The unit test environment: a driver above the unit, test doubles below it

The five test doubles

Fowler's essay takes its vocabulary from Gerard Meszaros, who "uses the term Test Double as the generic term for any kind of pretend object used in place of a real object for testing purposes. The name comes from the notion of a Stunt Double in movies." Meszaros then defined five kinds.

DoubleFowler's definitionIn the worked example
Dummy"passed around but never actually used. Usually they are just used to fill parameter lists."The audit log, which pay never touches but the constructor requires
Fake"actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example)."An in-memory student store
Stub"provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test."A clock whose today() always answers 20 November 2026
Spy"stubs that also record some information based on how they were called."A mailer that keeps every message it was asked to send
Mock"objects pre-programmed with expectations which form a specification of the calls they are expected to receive."The payment gateway, which must be charged exactly once, with the right amount
munotes.in188

Unit Testing Techniques: Drivers, Stubs and Test Doubles

The standard vocabulary has an older, looser entry for the last: a mock object is one of the "temporary dummy objects created to aid testing until the real objects become available" (ISO/IEC/IEEE 24765). In everyday speech the words are used loosely; as Fowler puts it, "all sorts of words are used: stub, mock, fake, dummy". Meszaros's names are worth learning because each says what the double does.

State verification and behaviour verification

The doubles fall into two groups by how a test uses them to decide pass or fail. Fowler draws the distinction exactly.

  • State verification "means that we determine whether the exercised method worked correctly by examining the state of the SUT and its collaborators after the method was exercised." (SUT is the system under test.) The test checks results: the amount returned, the messages the spy recorded.
  • Behaviour verification means "we instead check to see if the order made the correct calls on the warehouse", in his example; in ours, whether the fee service made the right call on the gateway. The test checks interactions.

"Of these kinds of doubles, only mocks insist upon behavior verification. The other doubles can, and usually do, use state verification."

Worked example: one unit, five doubles

Python's standard library includes unittest.mock, which, in its documentation's words, "allows you to replace parts of your system under test with mock objects and make assertions about how they have been used." The program uses it for the stub, the mock and the dummy, and writes the fake and the spy by hand, so that what each double is stays visible. The fee rules are the book's fixed ones; 10 days late with one backlog paper is Rs 800 + Rs 150 + Rs 500.

from datetime import date
from unittest.mock import Mock, sentinel

FORM_FEE, BACKLOG_FEE = 800, 150

def total_fee(days_late, backlog_papers, concession):
    if days_late < 0 or days_late > 15:
        raise ValueError("form not accepted")
    if not isinstance(backlog_papers, int) or backlog_papers < 0:
        raise ValueError("backlog papers must be a whole number, 0 or more")
    fee = FORM_FEE + BACKLOG_FEE * backlog_papers - (FORM_FEE if concession else 0)
    late = 0 if days_late == 0 else (100 if days_late <= 7 else 500)
    return fee + late

class FeeService:                                     # the unit under test
    def __init__(self, students, clock, gateway, mailer, audit_log):
        self.students, self.clock = students, clock
        self.gateway, self.mailer, self.audit_log = gateway, mailer, audit_log

    def pay(self, roll_no, last_date):
        student = self.students.find(roll_no)
        days_late = max(0, (self.clock.today() - last_date).days)
        amount = total_fee(days_late, student["backlog"], student["concession"])
        receipt = self.gateway.charge(roll_no, amount)
        self.mailer.send(student["email"], f"Paid Rs {amount}; receipt {receipt}")
        return amount

    def refund(self, roll_no, amount):                # not exercised by this test
        self.audit_log.write(f"refund {roll_no} {amount}")
        return self.gateway.refund(roll_no, amount)

class FakeStudentStore:                               # FAKE: really works, but only in memory
    def __init__(self):
        self.rows = {}
    def add(self, roll_no, **row):
        self.rows[roll_no] = row
    def find(self, roll_no):
        return self.rows[roll_no]

class MailerSpy:                                      # SPY: records how it was called
    def __init__(self):
        self.sent = []
    def send(self, to, text):
        self.sent.append((to, text))

# the DRIVER: set up the doubles, call the unit, check what happened
students = FakeStudentStore()
students.add("2026CS014", backlog=1, concession=False, email="s014@college.example")
clock = Mock()                                        # STUB: a canned answer for today()
clock.today.return_value = date(2026, 11, 20)
gateway = Mock()                                      # MOCK: the calls it receives are verified
gateway.charge.return_value = "R-1042"
mailer = MailerSpy()
service = FeeService(students, clock, gateway, mailer, audit_log=sentinel.audit_log)  # DUMMY

amount = service.pay("2026CS014", last_date=date(2026, 11, 10))
print("amount returned:", amount)                                 # state verification
gateway.charge.assert_called_once_with("2026CS014", 1450)         # behaviour verification
print("gateway charged:", gateway.charge.call_args)
print("mails sent:", len(mailer.sent), "to", mailer.sent[0][0])
print("receipt text:", mailer.sent[0][1])

class CarelessFeeService(FeeService):                 # a defect: forgets the backlog paper
    def pay(self, roll_no, last_date):
        student = self.students.find(roll_no)
        days_late = max(0, (self.clock.today() - last_date).days)
        amount = total_fee(days_late, 0, student["concession"])
        self.gateway.charge(roll_no, amount)
        return amount

gateway = Mock()
CarelessFeeService(students, clock, gateway, MailerSpy(), sentinel.audit_log).pay(
    "2026CS014", last_date=date(2026, 11, 10))
try:
    gateway.charge.assert_called_once_with("2026CS014", 1450)
except AssertionError as problem:
    print("the mock rejects the careless version:", str(problem).splitlines()[0])
    print("  actually called:", gateway.charge.call_args)
munotes.in189

Unit Testing Techniques: Drivers, Stubs and Test Doubles

amount returned: 1450
gateway charged: call('2026CS014', 1450)
mails sent: 1 to s014@college.example
receipt text: Paid Rs 1450; receipt R-1042
the mock rejects the careless version: expected call not found.
  actually called: call('2026CS014', 1300)

Each double did one job, and together they made a unit test out of something that would otherwise need a database, a calendar, a bank and a mail server.

  • The fake store answered the lookup with a real, working record, held in memory.
  • The stub clock gave the same "today" on every run, so the test is repeatable: the form is 10 days late whenever the test runs, not only on 20 November.
  • The mock gateway was never charged real money, and the test verified the call it received: exactly once, for student 2026CS014, for Rs 1,450. That is behaviour verification.
  • The spy mailer kept the receipt instead of sending it, so the test could read its address and text afterwards. That is state verification on a double.
  • The dummy filled a parameter the test never needed. sentinel supplies a unique object that fits the purpose: if the code ever did use it, the test would fail loudly.
munotes.in190

Unit Testing Techniques: Drivers, Stubs and Test Doubles

The second half of the program shows what behaviour verification is for. A careless version of pay forgets the backlog paper and charges Rs 1,300. The mock's check, assert_called_once_with, reports "expected call not found" and shows the call it actually received, pointing straight at the missing Rs 150.

Choosing doubles well

Doubles are powerful, and Fowler's essay warns about the cost of using them too freely. A test built on mocks checks how a unit talks to its collaborators, so "Mockist tests are thus more coupled to the implementation of a method. Changing the nature of calls to collaborators usually cause a mockist test to break." A few rules of thumb follow.

  • Use the real thing when it is cheap and deterministic. total_fee is a plain function; the test above calls the real one rather than doubling it.
  • Double what is slow, costly, uncontrollable or unfinished: payment, email, the clock, a network service, a unit not yet written.
  • Mock only the calls that matter to the requirement. The gateway charge matters; how many times the student store is read does not, so it is a fake, not a mock.
  • Make time and randomness injectable, as the clock is here. A unit that reads the system clock directly cannot be tested for "10 days late" without waiting ten days; this is the testability point of Chapter Twenty-One, on how quality factors shape testing.

What it does not mean

A stub is not a mock. A stub supplies answers; a mock also checks the calls it receives. The title of Fowler's essay says it: mocks aren't stubs.

Test doubles do not prove the real collaborators work. The mock gateway accepts any call the test allows; whether the real gateway does is a question for integration testing (Chapter Thirty-Seven).

A driver is not only for procedural code. In a test framework, each test method is a driver; the framework runs them all.

More doubles do not make a better test. Each one couples the test to the unit's design, so use the fewest that make the test fast and repeatable.

Quick revision

  • Test driver: invokes the module under test, provides inputs, monitors execution and reports results. Stub: a skeletal implementation standing in for a module the unit calls. Together, the test harness.
  • Test double (Meszaros, via Fowler): any pretend object used in place of a real one for testing.
  • Dummy: passed but never used. Fake: works, with a shortcut (in-memory database). Stub: canned answers. Spy: a stub that records how it was called. Mock: pre-programmed with expectations of the calls it should receive.
  • State verification: check the state after the call. Behaviour verification: check the calls made; only mocks insist on it.
  • unittest.mock: Mock, return_value, assert_called_once_with, call_args, sentinel.
  • Mock-heavy tests are coupled to the implementation: double only what is slow, costly, uncontrollable or unfinished.
munotes.in191

Unit Testing Techniques: Drivers, Stubs and Test Doubles

Test yourself

1. What are a test driver and a stub? Why are they needed in unit testing? A driver is a module that calls the unit under test with test inputs, monitors it and reports results; a stub is a simplified stand-in for a module that the unit calls. They are needed because a unit cannot run alone: its caller and its collaborators may not exist yet, or may be slow, costly or uncontrollable.

2. Name and explain the five kinds of test double. A dummy is passed but never used, only filling a parameter list. A fake has a working but simplified implementation, such as an in-memory database. A stub gives canned answers to the calls made in the test. A spy is a stub that also records how it was called. A mock is pre-programmed with expectations of the calls it should receive, and the test checks them.

3. Distinguish state verification from behaviour verification. State verification checks whether the unit worked by examining the state of the unit and its collaborators after the call, such as the amount returned. Behaviour verification checks whether the unit made the correct calls on its collaborators, such as charging the gateway once with the right amount; only mocks insist on it.

4. In the worked example, why is the clock replaced by a stub? Because the fee depends on how many days late the form is, and a unit that reads the real clock would give a different result on different days. The stub returns a fixed date, so the test is repeatable and can test 10 days late without waiting.

5. What is the risk of using mocks for everything? Mock-based tests check how the unit calls its collaborators, so they are coupled to its implementation: changing the calls, even without changing the behaviour, breaks the tests. Real objects should be used where they are cheap and deterministic.

munotes.in192

The rest of this subject

These notes are cut from the University's printed syllabus. Open the syllabus itself, or the past papers, for the same subject.

Issue
Done!