Keep a folder of sample manifests that should pass and should fail, run them through the policy tooling in CI on every policy change, and do a server side dry run against a test cluster as the last step. The goal is to find a wrong rule on a pull request, not during a production deploy.
Why test policies at all
A policy is code that can block every deploy you have. A typo in a CEL expression or a selector that matches too much can stop releases or, worse, quietly allow everything. The usual practice for code, which is tests in CI, applies here too.
Kyverno's documentation describes its command line tool as one that can validate and test policy behavior against resources before adding them to a cluster. The Kubernetes documentation for ValidatingAdmissionPolicy points to a CEL Playground for quickly trying expressions. Different tools, same idea.
Step 1: build a fixture set
Create a folder per policy with small, realistic manifests.
| Fixture | Expected result |
|---|---|
| Compliant workload | Pass |
| Workload violating the rule once | Fail |
| Workload violating only through an init container | Fail |
| Workload in an exempt namespace | Pass |
| Object type the policy should not touch | Pass, unaffected |
The init container and exempt namespace cases are the ones teams forget, and they are where real bugs live.
Step 2: run offline checks on every change
Run your policy tool's test command against the fixtures in CI. The check should fail the pull request when any expectation is not met. Keep expectations in files next to the policies so reviewers see both in one diff.
If you use ValidatingAdmissionPolicy and have no offline runner, you can still evaluate expressions against sample objects in a scratch cluster created for the job. Keep it small and disposable.
Step 3: run a server side dry run
An offline test checks logic. A server side dry run checks that the real API server, with its real admission chain, treats the object the way you expect. Run it against a test cluster that mirrors production policy versions.
kubectl apply --dry-run=server -f fixtures/should-fail/
kubectl apply --dry-run=server -f fixtures/should-pass/
The result should match your expectations. Failures in the pass folder or passes in the fail folder fail the build.
Step 4: test the bindings, not just the policy
A policy does nothing until it is bound. Test the binding's selectors and validation actions too. Confirm that a namespace you meant to cover is covered, and that Warn and Audit bindings do not deny.
Step 5: stage the rollout
- Merge the policy with Warn and Audit actions.
- Watch warnings and audit events for real workloads for a defined period.
- Fix or except what turns up, then switch to Deny in a separate change.
- Keep the fixtures forever. They are your regression tests when the policy is edited later.
Common mistakes
- Testing only the fail case, never the pass case, so a broken rule that denies everything looks fine.
- Running tests against a different Kubernetes version than production.
- Forgetting to test what happens when a policy errors. The failurePolicy setting decides whether errors reject requests or are ignored, so test that deliberately.
Where Safeguard fits
Safeguard's policies, gates and guardrails can be used in pull request checks, so rule changes and dependency changes face the same review. Pair that with the cluster tests above for your admission layer.
Try it yourself
Click through it at your own pace, or open it full screen. Watch how a pull request check reports a failure before merge.
Quick check
Is a dry run enough to test a policy?
It checks behavior on a real API server, but you also want offline fixtures for fast feedback and for edge cases like init containers.
What should I keep after a policy is enforced?
The fixtures. They protect you from regressions when someone edits the policy later.
Sources
Last reviewed: 19 October 2026.