Snapshots, Property Tests, and the Modern Test Toolbox
Unit tests and integration tests are not the whole story. Here is what else belongs in a serious test suite.
Snapshots, Property Tests, and the Modern Test Toolbox
Most engineering teams cover unit tests and maybe integration tests, then stop. Snapshot tests, property based tests, mutation tests, and contract tests each catch a different class of bug that traditional testing misses. Understanding which tool catches which class of problem is the difference between a test suite that builds confidence and one that passes CI while hiding production failures.
What you need to know
- Snapshot tests catch unintended UI and output changes; they are fast to create but expensive to maintain if misused
- Property based tests generate hundreds of random inputs and find edge cases your hand written tests never would have considered
- Mutation testing checks whether your tests are actually doing their job by introducing deliberate bugs and verifying the suite catches them
- Contract tests ensure that the API contract between services matches on both sides, catching integration failures before deployment
- Most codebases benefit from two or three of these tools strategically applied, not all of them everywhere
The core argument
The failure mode of most test suites is not that they are too small; it is that they test what the developer expected to happen rather than what could happen. A hand written test for a function that formats a date will test the obvious cases: a normal date, a null input, a leap year. It probably will not test the case where the timezone offset causes the date to flip to the previous day, or where an unusual locale changes the decimal separator in unexpected ways. Property based testing generates hundreds of random inputs automatically and finds the edge cases you forgot to imagine.
I introduced property based testing to the Nexli codebase for the fee calculation logic. Fee calculations involve currency rounding, additions based on percentages, conditional discounts, and edge cases around line items worth zero. Hand written tests covered the normal cases well. Property based tests (using fast-check for TypeScript) found three rounding errors within the first run that were present in production but had never been triggered by the normal test inputs. The fix was two lines. The alternative discovery path was a confused parent with an incorrect invoice.
Snapshot testing gets misused more than any other tool. The pattern is: write a component, run the snapshot test, commit the snapshot, and from then on any change to the component output requires updating the snapshot. The problem is that snapshot updates become a mechanical step in every PR, and the "does the snapshot look right" review stops happening because reviewers stop reading diffs they can regenerate. Used well, snapshots are for stable, thoroughly designed outputs where unexpected changes are genuinely a signal of a problem. Used badly, they are a ceremony that adds noise to every PR without catching anything meaningful.
Common mistakes
Treating snapshot failures as "update and move on." A failing snapshot test is either a legitimate problem (something changed that should not have) or a test that should not exist (testing implementation details rather than behavior). The answer is not automatically to update the snapshot.
Using property based tests without understanding what properties you are actually testing. Property based tests require you to define the invariant you are testing: "this function always returns a positive number" or "this function produces the same result regardless of input order." Without a clear invariant, the test does not communicate intent.
Running mutation tests on every PR. Mutation testing is computationally expensive. Run it on a schedule (weekly or before major releases) rather than blocking every push on it.
Building contract tests without tooling. Contract tests between services need a broker (Pact is the standard choice) to store the contracts and verify them independently on each side. Without tooling, "contract test" is just a named integration test that breaks silently when either service changes.
Skipping snapshot tests entirely because they are "too heavy on maintenance." Snapshot tests for critical rendering logic (email templates, PDF generation, API response shapes) are low maintenance and catch real production regressions. The problem is not snapshots; it is applying them too broadly to volatile components.
Where to start
Add property based tests to your most complex business logic. Fee calculations, discount rules, permission logic, anything with multiple conditional paths. Use fast-check (TypeScript/JavaScript) or Hypothesis (Python). Start with one function and the results will make the value obvious.
Audit your existing snapshot tests. If you have them, answer: when did a snapshot test last catch a real bug rather than a deliberate change? If the answer is "never," reassess whether the snapshots are covering things that should change vs things that should be stable.
Add contract tests at your service boundaries. If you have two services where an API contract change could break production silently, that boundary is where contract testing earns its place. Pact is the standard tooling. A single boundary contract test prevents an entire class of integration failures between teams.
FAQ
Frequently asked
- What is a property based test?
- Are snapshot tests worth the maintenance?
- What is mutation testing?
- How does contract testing differ from integration testing?
- Do I need all of these testing approaches?
Author
Why Yashveer Singh is the right hire here
The right hire for the work in this article is someone who has done it, written about it, and is willing to back it up with their name. That is me. Yashveer Singh. Founder of Yashveer Labs. New Delhi. The work I have shipped is on the homepage. The work I am writing about is the work I do. There is no mismatch between the page and the engineer behind it.