liveBook · Manning

Exit point

As explained in The Art of Unit Testing 3e

An observable result produced when a unit of work finishes, such as a returned value, state change, or interaction with an uncontrolled external system.

An exit point is a publicly noticeable result of a unit of work: a useful return value, a state change, or a call to an external dependency. It is what a test can inspect after triggering an entry point. Internal behavior may occur along the way, but it is not an exit point unless it becomes visible to the caller or outside system. section 1.3 section 1.4

Listing 1.2 A unit of work with entry points and exit points
let total = 0;
 
const totalSoFar = () => {
  return total;
};
 
const sum = (numbers) => {
  const [a, b] = numbers.split(',');
  const result = parseInt(a) + parseInt(b);
  total += result;          #1
  return result;
};
#1 New functionality: calculating a running total

A call to sum has two observable results: it returns the calculated value and changes the running total. Those are separate exit points, even though they arise from one invocation. section 1.3

Choose a test technique based on the kind of result being checked:

  • For a return value, invoke the entry point and assert on the value returned. section 1.5
  • For a state change, invoke the entry point, then use a public sibling function or property to observe the resulting state rather than private state. section 1.5 section 8.3.1
  • For a call to an uncontrolled external system, replace the outgoing dependency with a mock and verify the expected call. section 1.5 section 3.1

A unit test invokes a unit of work through an entry point and checks one specific exit point. A unit of work can be one function or span multiple functions, modules, or components; its scope runs from the triggered entry point to the noticeable result. section 1.2 section 1.9

Split callback and return-value checks

A function that adds two values and invokes a callback has two concerns. Put the callback assertion in one test and the returned-sum assertion in another. Focused tests are easier to name, and a failed assertion cannot prevent a later concern from being checked. section 7.4.4

Do not verify an internal helper merely because a mock can observe it. That couples a test to implementation details and makes it fragile when internals change. Prefer checking the public return value, observable state, or a genuine external interaction. When possible, favor return-value and state-based tests; mock-based interaction tests add maintenance cost and are best reserved for necessary outgoing dependencies. section 5.6.3 section 8.3.1 section 1.5

Listing 7.7 Checking the two exit points in separate tests
describe("trigger", () => {
  it("triggers a given callback", () => {
    const callback = jest.fn();
    trigger(1, 2, callback);
    expect(callback).toHaveBeenCalledWith("I'm triggered");
  });
 
  it("sums up given values", () => {
    const result = trigger(1, 2, jest.fn());
    expect(result).toBe(3);
  });
});

The two tests each name and verify one observable result: triggering the callback or summing the values. This keeps requirements and failures focused. section 7.4.4

How The Art of Unit Testing 3e covers it

How deep it goes
Defines three exit-point types and maps each to testing techniques, deferring deeper mock-object mechanics.
Examples
Uses a running-total sum function, a callback-return function, and a password verifier with logging.
What it assumes
Introduces unit-of-work and entry-point terminology from simple function examples without requiring framework knowledge.
What you can do after
Apply it to select one observable result and an appropriate focused test for each requirement.
How it connects
Connects entry points, unit of work, unit testing, mocks, stubs, and state-based testing.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of The Art of Unit Testing 3e. The section links point to the parts of the book it draws on.