Property-Based Testing for Python IaC with Hypothesis
Example-based tests check the cases you thought of; property-based tests generate the ones you did not. This guide, part of testing Python IaC under Python IaC fundamentals and strategy, uses Hypothesis to test the naming, tagging, and validation logic inside your infrastructure helpers.
Context
Most IaC bugs are not in the cloud calls — they are in the Python that computes names, CIDRs, and tags before the provider ever runs. That pure logic is ideal for property-based testing: assert invariants (a generated name is always DNS-safe, a subnet always fits its VPC) across thousands of inputs, complementing the mock-based unit tests you already run.
Prerequisites
- Python 3.9+ with
pytestandhypothesisinstalled - A pure function under test — name/tag/CIDR logic with no cloud calls
- A clearly stated invariant the function must always satisfy
# CLI: confirm the tools are available
pytest --version && python -c "import hypothesis; print(hypothesis.__version__)"
Implementation
Suppose a helper builds a resource name from a service and environment. The invariant: the result is lowercase, <= 63 characters, and contains only DNS-safe characters, for any inputs.
# test_naming.py — property test for a resource-name builder
# CLI: pytest test_naming.py
import re
from hypothesis import given, strategies as st
from naming import resource_name # the pure function under test
safe = re.compile(r"^[a-z0-9-]{1,63}$")
@given(service=st.text(min_size=1, max_size=40),
env=st.sampled_from(["dev", "staging", "prod"]))
def test_name_is_dns_safe(service: str, env: str) -> None:
name = resource_name(service, env)
# Invariant: whatever the input, the output is a valid DNS label.
assert safe.match(name), f"unsafe name: {name!r}"
If resource_name ever emits an uppercase letter or an over-long string, Hypothesis reports the exact minimal input that broke it.
Verification
Run the suite; a green run means the property held across every generated case. Record the failing seed when one fails so it is reproducible in CI.
# CLI: run and, on failure, re-run the printed seed deterministically
pytest test_naming.py -q
Gotchas & Edge Cases
Flaky properties hide real bugs. If a test only sometimes fails, the invariant is too weak or the function is non-deterministic; pin randomness and tighten the property.
Over-broad generators. Generating arbitrary Unicode may surface inputs your system will never see; constrain strategies to realistic inputs so failures are actionable.
Slow suites. Property tests run many cases; cap max_examples for fast feedback locally and raise it in nightly CI.
FAQ
Does this replace mock-based tests?
No — it complements them. Use property tests for pure logic and Pulumi mocks for resource wiring.
Can I property-test synthesized output?
You can assert invariants over CDKTF-synthesized JSON, but that overlaps snapshot testing; property tests shine on pure helpers.
How many examples are enough?
The default (100) catches most issues; raise it for critical invariants and lower it where feedback speed matters.
Related
- Unit Testing Pulumi Programs with Mocks — the mock-based counterpart for resource logic.
- Testing Python IaC — the parent cluster on testing strategy.
- Python Typing for Cloud Resource Definitions — types and properties together catch a class of IaC bugs.