CoursesOPA & RegoIntermediate

Policy testing & coverage

Trust your policies before shipping.

Advanced12 min · lesson 8 of 12

As a policy library grows, you need confidence that rules do what you think and that your tests exercise them. opa test --coverage reports which lines of Rego your tests actually hit, so you can find untested branches — a deny condition no test triggers is a rule you are trusting blind. Combined with a test per compliant and violating case, coverage turns “I think this policy works” into “every path is tested.”

terminal
$ opa test . --coverage --format=json | jq '.coverage'
85.7
$ opa test . -v # see each test’s pass/fail
# aim to cover every deny branch: compliant input, each violation, edge cases

Linting and formatting

opa fmt formats Rego canonically (run it in CI to keep policy readable), and opa check does strict type/compile checking to catch errors like referencing a nonexistent rule. Regal, a dedicated Rego linter, adds style and bug-pattern checks (including the undefined-field traps). Treating policy like any codebase — formatted, linted, type-checked, tested with coverage — is what makes a large policy library trustworthy.

terminal
$ opa fmt --write policy/ # canonical formatting
$ opa check policy/ # compile + strict type checks
$ regal lint policy/ # style + common Rego bug patterns
Coverage gaps are silent policy holes
An untested deny branch might contain a logic error (a wrong operator, an undefined-field trap) that makes it never fire — so the policy looks present but enforces nothing for that case. Use coverage to find those branches and add tests until every violation path is exercised. In policy code especially, an untested rule is worse than no rule, because it creates false confidence.