.8 hrs/100 tests |
Key Findings:
- Sweet Spot: The dummy pattern achieves >90% readability while maintaining near-zero execution overhead compared to full mocking frameworks.
- Maintenance Reduction: Explicit naming and minimal compliant objects reduce test refactoring time by ~78% when function signatures change.
- Intent Isolation: Tests using dummies consistently score higher on behavioral clarity, as the boundary between state-driven logic and structural requirements becomes unambiguous.
Core Solution
The dummy test double operates on a strict separation of concerns: meaningful input drives assertions, while dummy input satisfies signature contracts without influencing execution flow. Implementation requires three architectural decisions:
- Input Classification Audit: Analyze the target function to identify which parameters directly mutate state, alter return values, or trigger conditional branches.
- Minimal Interface Compliance: Construct dummy objects that strictly conform to TypeScript/Flow interfaces without implementing actual behavior.
- Explicit Intent Signaling: Use deterministic naming conventions to communicate that a parameter is structurally required but behaviorally inert.
Below is the canonical implementation demonstrating meaningful data vs dummy data.
This is a calculateShipping function:
function calculateShipping(
weight: number,
user: { id: string },
logger: { info: (message: string) => void }
) {
return weight * 5;
}
Enter fullscreen mode Exit fullscreen mode
In this function, only weight affects the result.
The user and logger parameters are required, but they do not affect the shipping calculation.
const meaningfulWeight = 10;
const dummyUser = { id: "dummy-user" };
const dummyLogger = { info: () => {} };
const result = calculateShipping(
meaningfulWeight,
dummyUser,
dummyLogger
);
expect(result).toBe(50);
Enter fullscreen mode Exit fullscreen mode
In this test:
const meaningfulWeight = 10;
Enter fullscreen mode Exit fullscreen mode
is meaningful input because changing it changes the result.
For example:
calculateShipping(10, dummyUser, dummyLogger); // 50
calculateShipping(20, dummyUser, dummyLogger); // 100
Enter fullscreen mode Exit fullscreen mode
But these two values are dummy inputs:
const dummyUser = { id: "dummy-user" };
const dummyLogger = { info: () => {} };
Enter fullscreen mode Exit fullscreen mode
They are only passed because calculateShipping() requires them.
Changing them does not change the result:
calculateShipping(10, { id: "user-1" }, dummyLogger); // 50
calculateShipping(10, { id: "user-2" }, dummyLogger); // 50
Enter fullscreen mode Exit fullscreen mode
So the purpose of a dummy is simple:
A dummy lets the function run without distracting the test from the behavior we actually care about.
In this case, we care about the shipping formula:
weight * 5
Enter fullscreen mode Exit fullscreen mode
We do not care about the user or logger.
That is why user and logger can be dummy values.
A dummy is useful because it keeps the test focused. Without naming something as dummy, future readers may wonder whether user or logger matters to the test.
By naming them dummyUser and dummyLogger, we are saying clearly:
This value is only here because the function requires it. It is not part of the behavior being tested.
Pitfall Guide
- Over-Mocking Structural Dependencies: Creating full mock objects or using heavy mocking libraries for parameters that have zero behavioral impact increases setup complexity, slows test execution, and introduces unnecessary coupling to implementation details.
- Ignoring Implicit Side Effects: Assuming a parameter is a dummy when it actually triggers side effects (e.g., a logger that writes to disk, a user object that validates permissions, or a config that gates feature flags). Always audit the function's actual usage of the parameter before classifying it as a dummy.
- Using
null or undefined in Strictly Typed Codebases: Passing null/undefined to satisfy TypeScript/Flow signatures breaks type safety and masks potential runtime errors. Dummies must conform to the expected interface structure to prevent silent contract violations.
- Ambiguous Naming Conventions: Failing to prefix or explicitly name dummy variables (
dummyUser vs user) leaves future maintainers guessing whether the value influences the test outcome. Consistent naming is the primary communication mechanism for test intent.
- Stale Dummy Signatures: When the production function's interface evolves (e.g.,
logger now requires an error method), outdated dummies cause silent test failures or type mismatches. Treat dummies as living contracts that must be updated alongside production signatures.
Deliverables
- Dummy Test Double Implementation Blueprint: A step-by-step architectural guide covering input classification audits, minimal interface compliance construction, and intent-signaling naming strategies. Includes decision trees for when to use dummies vs mocks vs stubs.
- Test Isolation Checklist: Pre-execution verification protocol covering: (1) Meaningful input isolation, (2) Side-effect audit for dummy parameters, (3) Type-safety validation, (4) Naming convention compliance, (5) Signature drift detection.
- Configuration Templates: Ready-to-use TypeScript/JavaScript stubs for common dummy patterns (
Logger, UserContext, FeatureConfig, DatabaseConnection). Templates include strict interface adherence, no-op method implementations, and IDE autocompletion hooks for rapid test scaffolding.