Writing Sentinel Mock Data for Terraform Policy Tests
An untested Sentinel policy is a liability that sits in your apply path. Here's how to generate mock data from real plans and write pass and fail fixtures that prove a policy actually works.
- #terraform
- #ai
- #sentinel
- #policy-as-code
- #testing
Most Sentinel content stops at writing the policy. The policy gets reviewed, merged, and dropped into the apply path — and nobody ever proves it does what it claims. That’s a problem twice over. An untested policy might wave through the exact violation it was meant to block, or it might false-positive and freeze every plan in the org. Both are bad days.
Sentinel policies are code, and they deserve to be tested like code: with sentinel test, against mock data captured from real Terraform plans, with explicit pass and fail fixtures. Here’s how to build that.
How Sentinel mocks work
Sentinel policies for Terraform read from imports like tfplan/v2, tfconfig/v2, tfstate/v2, and tfresources. To test a policy offline, you give it mock data — files that emulate those imports — generated from a real plan’s JSON. A policy you can’t run against representative mocks is a policy you don’t actually trust.
The workflow is: capture a real plan, generate mock files from it, trim them to the minimum that exercises your rule, and write fixtures that point the policy at the mocks.
Step one: mock only what the policy reads
Before generating anything, identify which imports your policy actually touches. A policy that reads only tfplan.resource_changes doesn’t need a tfstate mock. Mocking imports the policy never reads is wasted effort and noise. List the imports under test first, then produce exactly those mock files.
Step two: generate, then trim
You generate mock data from a real plan’s JSON (Terraform Cloud and the sentinel tooling can emit these mocks from a plan). The raw output is often huge — every resource in the plan, fully expanded. Resist the urge to keep it all. Trim the mock to the minimal set of resources that exercise your rule:
# policy: all S3 buckets must have encryption configured
import "tfplan/v2" as tfplan
violating_buckets = filter tfplan.resource_changes as _, rc {
rc.type is "aws_s3_bucket_server_side_encryption_configuration" is false and
rc.type is "aws_s3_bucket" and
rc.change.actions contains "create"
}
main = rule { length(violating_buckets) is 0 }
For this policy, your mock needs a couple of S3 buckets and their encryption configs — not the other 200 resources from the plan. A focused, 40-line mock that a reviewer can read beats a 4,000-line dump nobody understands.
Step three: the passing fixture
Write a fixture that points at compliant mocks and asserts the policy passes:
# test/s3-encryption/pass.hcl
mock "tfplan/v2" {
module {
source = "mocks/pass/mock-tfplan-v2.sentinel"
}
}
test {
rules = {
main = true
}
}
The compliant mock contains buckets that all have encryption configured. Running this asserts main = true.
Step four: the failing fixture — the part that matters
This is where most “tested” policies fall down. A policy that only has a passing test proves nothing: it might pass everything, including the violations it’s supposed to catch. You need a separate fixture, pointed at deliberately violating mocks, that asserts the policy fails:
# test/s3-encryption/fail.hcl
mock "tfplan/v2" {
module {
source = "mocks/fail/mock-tfplan-v2.sentinel"
}
}
test {
rules = {
main = false
}
}
The failing mock contains an unencrypted bucket. If this test passes — meaning the policy correctly returned main = false — you’ve proven the policy actually rejects bad input. That’s the test that gives you confidence to put the policy in the apply path.
Step five: the edge cases that break policies in the wild
Real plans contain shapes that trip up naive policies. Build mocks for them:
- Resources being destroyed —
change.actionsis["delete"], not["create"]. Does your rule accidentally flag or skip these? - Unknown/computed values — attributes that show as
nullor computed in the plan. Does your condition handle them without crashing? - Nested modules — resources under a
module_address. Does your traversal find them? - Empty plans — no resource changes at all. Does the policy pass cleanly, as it should?
Each of these deserves a mock and a fixture, because each is a real way policies misfire after they ship.
Let AI generate the mocks and fixtures — then sanitize
Trimming a plan into focused mocks and writing the pass/fail pairs is tedious, pattern-based work that AI handles well. A prompt like:
Here’s my Sentinel policy enforcing S3 encryption and a real plan JSON. It reads only
tfplan/v2. Generate a trimmed passing mock and a trimmed failing mock, pluspass.hclandfail.hclfixtures assertingmain = trueandmain = false. Add an edge-case mock for a bucket being destroyed.
A good answer returns the trimmed mocks and both fixtures, plus:
Heads up: the plan JSON includes account IDs and bucket ARNs — scrub these before committing the mock data to the repo.
That sanitization note is non-negotiable. Plan JSON embeds account IDs, ARNs, IPs, and tags; mock data committed to version control with real identifiers is a leak. The AI drafts the fixtures; you verify the policy genuinely fails on the violation and scrub the mocks before they land in git. Our Sentinel mock data prompt is structured around producing both fixtures and the edge cases, and our broader policy-as-code prompt covers the policy itself.
The takeaway
A Sentinel policy without a failing fixture is untested, no matter how many passing tests it has. Generate mocks from real plans, trim them to the rule under test, write a pass and a fail fixture, cover the edge cases that break policies in production, and sanitize before committing. Then your policy-as-code is actually proven rather than merely present. For more on enforcing standards in Terraform, browse our Terraform guides.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.