Testing Time with an Injected Clock
Pass a clock into time-dependent code, test expiry boundaries to the millisecond with node:test, and see why sleep-based tests flake.
Writing tests that catch real bugs: unit versus integration scope, mocks and fakes, flaky-test diagnosis, boundary values, contract tests and deterministic async code.
Official reference: Node.js test runner
Pass a clock into time-dependent code, test expiry boundaries to the millisecond with node:test, and see why sleep-based tests flake.
See a 100% covered function ship a boundary bug, read line and branch numbers from Node's coverage report, and set thresholds that help.
Choose which user journeys deserve browser-level tests, push the rest down the stack, and keep the E2E suite small, stable and owned.
Test failure cases on purpose: assert the exact exception and message, probe edge inputs like NaN, and check that failures leave no damage.
Watch fast unit tests pass while a real SQLite test catches a swapped column and an untranslated unique-constraint error, then split the work.
Reproduce a shared temp file, a fixed port and module-level state breaking parallel node:test runs, then give each test its own resources.
A real TDD session in Python's unittest: watch each test fail for the right reason, pass with minimal code, then refactor safely under green.
Use node:test snapshots on a receipt, see a diff where a real bug hides among cosmetic changes, and keep snapshots small and readable.
Replace sprawling inline fixtures with a test data builder in Python, compare it with an object mother, and avoid the mystery-guest default.
A hand-built modal passed axe with zero violations, then failed four keyboard and dialog checks in Playwright; a native dialog passed all five.
Split each test into arrange, act and assert so a failure names one broken behavior, shown on an order-cancellation service with node:test.
Replace sleeps with deferred promises so an async test decides which response lands first, catching stale-result races on every run.
A refactor breaks a test that spies on private helpers while a real bug slips past it; asserting results and stored state catches both cases.
Test just below, at and just above every limit: a cart cap with a < that should be <=, plus a 50.00 threshold that floats quietly miss.
Consumer-driven contracts in practice: a shipping service records what it needs from orders, and a renamed field fails before deploy.
SQLite accepted 'abc' as a price and a 16-character SKU that PostgreSQL rejects; test on the engine production uses, here via PGlite.
Group inputs that behave alike, test one value per group, then check the code agrees: Python's int() also accepts '1_0', '+5' and ' 7 '.
Test debounces, cooldowns and retry backoff with node:test mock timers, and avoid the Date.now and promise traps that make fake clocks lie.
A shared SQLite fixture makes tests pass alone and fail together; fix it with fresh state per test, commit-proof rollbacks and addCleanup.
Diagnose flaky tests with evidence: measure the failure rate, compare a test alone and in the suite, and replace sleeps that fail 23 runs in 100.
When to reach for a stub, a mock or a fake, shown on a password-reset service, plus a contract test that caught a fake more lenient than SQLite.
Stryker plants small bugs in code to see if tests notice: a shipping rule with 100% line coverage scored 72.73% until boundary tests went in.
Ramp load on a service with a 4-connection pool: throughput levels off near 190 req/s, and a fixed 300 req/s arrival rate ends in 85% timeouts.
Property tests generate hundreds of inputs and shrink failures to tiny examples: fast-check finds JavaScript's default sort and a CSV bug fast.
Write a regression test that fails for the original bug, select tests from the diff, and see when an import graph misses the one that breaks.
Test what each user must not do: a table-driven access matrix for an invoice API catches a missing ownership check that happy paths miss.
Unit, integration and end-to-end tests differ in what runs for real; four green pricing tests missed a coupon bug that one API-level test caught.
A green run with a skipped Stripe test and coverage that drops from 72.88% to 55.17% once test files are excluded: how to report what really ran.
Screenshot tests with Playwright: freeze the clock and viewport, and see why a 2% tolerance that hides a timestamp lets a wrong price through.
I would start with risks: wrong totals, duplicate orders, inventory changes and payment failures. Unit tests cover pricing rules and boundary values. Integration tests cover persistence, service contracts and idempotency. A small set of end-to-end tests covers important customer journeys with controlled test data. I would include timeout and retry scenarios, verify observable outcomes and avoid using real payment details. The goal is confidence in behavior, not merely a coverage percentage.
Likely follow-up: How would you test a successful payment followed by a failed response?
First capture the failing seed, logs, environment and execution order. I would look for shared mutable data, real network dependencies, wall-clock assumptions and asynchronous work that the test does not await. Reproduce with repetition and isolation, then replace arbitrary sleeps with observable readiness conditions. Control clocks and randomness when appropriate. Temporary quarantine may protect delivery, but it should have an owner and a tracked fix rather than silently hiding a regression.
Likely follow-up: Why can retrying every failed test conceal defects?
Use a test double when the dependency is slow, costly, nondeterministic or difficult to drive into a failure state. A stub provides canned responses, while a mock can verify interactions. Prefer assertions on observable behavior unless the interaction itself is the contract. Too many mocks can reproduce implementation details and miss real incompatibilities, so complement isolated tests with integration or contract checks using realistic boundaries.
Likely follow-up: What can an in-memory database hide compared with production?
Check the observable agreement between consumer and provider: required fields, types, status codes, headers and relevant error formats. Include missing or optional data and compatibility expectations. Avoid asserting undocumented incidental ordering or internal implementation details. Run provider verification against the expectations consumers actually rely on. Contract tests help catch incompatible changes, but they do not replace full integration checks for authentication, networking or real data behavior.
Likely follow-up: How would you evolve a response without breaking existing clients?
Await the operation and ensure the test runner observes rejected promises or callback failures. Control external dependencies and wait for a meaningful state change rather than an arbitrary sleep. For time-based behavior, a controlled clock can make tests fast and deterministic, but account for how the runtime schedules pending work. Include success, failure and cancellation where relevant. Clean up timers, listeners and background work so they do not affect later tests.
Likely follow-up: How can a test accidentally pass before an asynchronous assertion runs?
Gather the relevant input, expected outcome and actual behavior, then reproduce the failure in a controlled environment. Reduce it to the smallest case that still shows the defect. Write a test at the lowest useful boundary that captures the broken behavior, and verify that it fails before the fix. After the change, confirm it passes and consider nearby edge cases. Remove sensitive production details from fixtures while preserving the properties needed to reproduce the issue.
Likely follow-up: When would you need an integration test rather than a unit test?
Start with a workload model: request mix, arrival rate, payloads and data size. Set explicit latency and error-rate objectives and include warmup and steady-state periods. Observe tail latency, throughput, saturation and dependency behavior rather than only averages. Ensure the load generator is not the bottleneck. Separate normal-load validation from stress testing, and document environment differences so results are interpreted correctly. Repeat measurements to distinguish changes from noise.
Likely follow-up: Why can average latency hide a serious user experience problem?
Identify what changed, the user journeys affected and the consequences of failure. Prioritize high-impact paths such as access control, data integrity and core transactions, including important failure cases. Reuse reliable automated checks and target exploratory testing at uncertainty. Communicate what was covered, what remains untested and the residual risk to the release owner. Do not claim that a passing smoke suite proves the whole release is safe.
Likely follow-up: How would a change to a shared library alter your priorities?
No questions match that filter.
Prefer multiple choice? All 11 Software Testing MCQs with answers →