Test in layers, cheapest first: confirm the resolved versions, run dependency consistency checks, build, run unit and integration tests, smoke test the running service, then rescan. Stop at the first failure. Each layer catches a different kind of break, and the later ones are slower and costlier.
Layer 1: did the right version land
Before running any tests, read the lock file diff and the resolved tree. You are checking three things: the vulnerable package is at a fixed version on every path, nothing unexpected moved, and nothing was added or dropped that you did not intend.
Tools help. For npm, the ls command prints the installed tree, and the npm documentation notes it reports extraneous, missing and invalid packages. For Python, pip check verifies installed packages have compatible dependencies and exits with a non-zero code when a requirement is missing or has the wrong version. In Maven, the enforcer plugin has a dependency convergence rule, which fails the build when two dependencies need different versions of the same artifact.
Layer 2: build and static checks
Compile, type check and lint. Many breaking changes show up here: removed functions, changed signatures, new required arguments. Treat deprecation warnings as information. A new warning after an upgrade is the library telling you the next break is coming.
Layer 3: automated tests
| Test type | What it catches | Typical gap |
|---|---|---|
| Unit tests | Direct API changes | Mocks hide real library behavior |
| Integration tests | Real interaction with the library | Often thin around parsing and crypto |
| Contract tests | Changed request and response shapes | Only if you wrote them |
| Snapshot tests | Output format changes | Easy to update blindly |
A green suite proves less than it seems when coverage around the upgraded library is thin. If the library parses input, handles dates, or does anything with encryption, add a test for your usage before the upgrade, so the same test can run on both sides of it.
Layer 4: runtime smoke test
Start the service and exercise the paths that use the library. Look at startup logs for new warnings, and watch memory and latency on a staging copy. Some failures only show at runtime: a changed default, a missing optional module, a different serialization format.
Layer 5: rescan and record
Rescan the built artifact or image, not just the lock file. Confirm the finding is gone and that the upgrade did not introduce new ones. Record the result with the pull request.
- Resolved versions match intent.
- Consistency checks pass.
- Build and lint pass with no new unexplained warnings.
- Tests pass, with added tests for risky areas.
- Smoke test passes in staging.
- Rescan shows the finding closed.
- Rollback is ready (see how to roll back a bad dependency upgrade merge).
When tests pass but production breaks
Roll out in stages if you can: one instance, then a slice of traffic, then everything. Keep the previous build available. For the broader question of risk, the guide on what green CI does not prove about a fix PR explains the gaps.
Try it yourself
Click through it at your own pace, or open it full screen. Watch how a pull request is checked before merge, and where your own tests would slot in.
Where Safeguard fits
Safeguard scans dependencies, runs reachability analysis to show which findings your code can actually reach, and can open fix pull requests through autonomous remediation. That last feature is off by default and every category starts on Manual, so a person approves each change. You can try a free scan on a repository first.
Quick check
What is the first thing to check after an upgrade?
The resolved versions: that the fixed version is present on every path and nothing unexpected changed.
Do passing tests prove the upgrade is safe?
No. They prove only what the tests cover. Add tests for risky areas and smoke test the running service.
Sources
Last reviewed: 19 October 2026.