Pulumi revolutionizes infrastructure testing by letting teams use familiar programming languages and native test frameworks instead of learning DSL-specific tools. According to a Pulumi State of Cloud Engineering 2022 survey, teams that implement comprehensive infrastructure testing reduce deployment failures by 45% and recover from incidents 60% faster than those relying on manual verification. According to research by Puppet in their State of DevOps Report 2023, organizations practicing infrastructure as code with automated testing see 5x higher deployment frequency and 3x lower change failure rates. For QA engineers and DevOps teams, Pulumi’s approach means unit tests with mocks run 60x faster than integration tests — one team cut their 20-minute suite down to 20 seconds — while property tests catch compliance violations before deployment happens.
TL;DR
- Pulumi’s killer feature: use your language’s native test frameworks (Jest, pytest, Go testing) instead of learning HCL-specific tools
- Unit tests with
pulumi.runtime.setMocks()run 60x faster than integration tests—one team cut suite time from 20 minutes to 20 seconds- Property tests (CrossGuard) catch policy violations during deployment, not after—shift-left for infrastructure
Best for: Teams using TypeScript, Python, or Go who want CI/CD-integrated infrastructure testing
Skip if: You have simple infrastructure (<10 resources) or prefer Terraform’s ecosystem
Read time: 11 minutes
Testing infrastructure code in 2026 isn’t optional—it’s table stakes. But here’s what most guides won’t tell you: Pulumi’s testing approach is fundamentally different from Terraform’s, and that difference is why companies are switching.
While Terraform teams reach for external tools like Terratest or rely on terraform validate, Pulumi lets you write tests in the same language as your infrastructure. Your existing pytest knowledge? It works. Your Jest setup? It works. Your team’s CI/CD pipeline? No special plugins needed.
The Testing Pyramid for Infrastructure
Before diving into code, understand where each test type fits:
| Test Type | Speed | What It Validates | When It Runs |
|---|---|---|---|
| Unit Tests | ~20 seconds | Logic, configuration, resource properties | Every commit |
| Property Tests | During deployment | Policy compliance, security rules | pulumi up |
| Integration Tests | 5-30 minutes | Real cloud behavior, end-to-end flows | Pre-merge, nightly |
Most teams get this wrong: they skip unit tests (“infrastructure is different”) and rely solely on integration tests. The result? Slow feedback loops and flaky CI pipelines.
“Infrastructure testing with Pulumi follows the same quality standards as application testing — 60x faster unit tests mean there’s no excuse to skip them.” — Yuri Kan, Senior QA Lead
Unit Testing with Mocks
Pulumi’s mocking system intercepts all cloud provider calls, letting you test resource configuration without provisioning anything.
TypeScript with Jest
import * as pulumi from "@pulumi/pulumi";
import "jest";
describe("S3 bucket configuration", () => {
let infra: typeof import("./index");
beforeAll(() => {
pulumi.runtime.setMocks({
newResource: (args: pulumi.runtime.MockResourceArgs) => ({
id: `${args.name}-id`,
state: {
...args.inputs,
arn: `arn:aws:s3:::${args.inputs.bucket || args.name}`,
},
}),
call: (args) => args.inputs,
});
});
beforeEach(async () => {
infra = await import("./index");
});
it("enables versioning on production buckets", (done) => {
infra.dataBucket.versioning.apply(versioning => {
expect(versioning?.enabled).toBe(true);
done();
});
});
it("blocks public access by default", (done) => {
infra.dataBucket.acl.apply(acl => {
expect(acl).toBe("private");
done();
});
});
});
Critical pattern: Import your infrastructure module after setting up mocks. The beforeAll/beforeEach pattern ensures mocks are active before Pulumi resources are instantiated.
Python with pytest
import unittest
import pulumi
class InfraMocks(pulumi.runtime.Mocks):
def new_resource(self, args: pulumi.runtime.MockResourceArgs):
state = {
**args.inputs,
"arn": f"arn:aws:s3:::{args.inputs.get('bucket', args.name)}",
}
return [f"{args.name}_id", state]
def call(self, args: pulumi.runtime.MockCallArgs):
return {}
pulumi.runtime.set_mocks(InfraMocks(), preview=False)
# Import AFTER mocks are set
from infra import data_bucket
class TestS3Bucket(unittest.TestCase):
@pulumi.runtime.test
def test_versioning_enabled(self):
def check_versioning(versioning):
self.assertTrue(versioning.get("enabled"))
return data_bucket.versioning.apply(check_versioning)
@pulumi.runtime.test
def test_encryption_configured(self):
def check_encryption(rules):
self.assertIsNotNone(rules)
self.assertGreater(len(rules), 0)
return data_bucket.server_side_encryption_configuration.apply(
lambda c: check_encryption(c.rules) if c else self.fail("No encryption")
)
The @pulumi.runtime.test decorator handles Pulumi’s async Output types, making assertions cleaner.
Property Tests with CrossGuard
Property tests enforce invariants during deployment. Unlike unit tests that run in isolation, property tests see the actual resource graph Pulumi builds.
import * as policy from "@pulumi/policy";
const securityPolicies = new policy.PolicyPack("security", {
policies: [
{
name: "s3-no-public-read",
description: "S3 buckets must not allow public read access",
enforcementLevel: "mandatory",
validateResource: policy.validateResourceOfType(
aws.s3.Bucket,
(bucket, args, reportViolation) => {
if (bucket.acl === "public-read" ||
bucket.acl === "public-read-write") {
reportViolation("S3 bucket has public read access");
}
}
),
},
{
name: "ec2-approved-instance-types",
description: "EC2 instances must use approved types",
enforcementLevel: "mandatory",
validateResource: policy.validateResourceOfType(
aws.ec2.Instance,
(instance, args, reportViolation) => {
const approved = ["t3.micro", "t3.small", "t3.medium"];
if (!approved.includes(instance.instanceType)) {
reportViolation(
`Instance type ${instance.instanceType} not approved. ` +
`Use: ${approved.join(", ")}`
);
}
}
),
},
],
});
Run policies with: pulumi up --policy-pack ./policy
When policies shine: Enforcing organization-wide standards across all stacks. One policy pack, every team benefits.
Integration Testing with Automation API
When you need to verify actual cloud behavior—not just configuration—use integration tests with Pulumi’s Automation API.
import { LocalWorkspace } from "@pulumi/pulumi/automation";
import * as aws from "@aws-sdk/client-s3";
describe("S3 infrastructure", () => {
let stack: any;
const stackName = `test-${Date.now()}`;
beforeAll(async () => {
stack = await LocalWorkspace.createOrSelectStack({
stackName,
projectName: "s3-test",
program: async () => {
const bucket = new aws.s3.Bucket("test-bucket", {
versioning: { enabled: true },
});
return { bucketName: bucket.id };
},
});
await stack.setConfig("aws:region", { value: "us-west-2" });
await stack.up({ onOutput: console.log });
}, 300000); // 5 minute timeout
afterAll(async () => {
await stack.destroy({ onOutput: console.log });
await stack.workspace.removeStack(stackName);
});
it("creates a bucket with versioning", async () => {
const outputs = await stack.outputs();
const s3Client = new aws.S3Client({ region: "us-west-2" });
const versioning = await s3Client.send(
new aws.GetBucketVersioningCommand({
Bucket: outputs.bucketName.value,
})
);
expect(versioning.Status).toBe("Enabled");
});
});
Warning: Integration tests provision real resources. Always:
- Use unique stack names with timestamps
- Implement proper cleanup in
afterAll - Set appropriate timeouts (cloud operations are slow)
- Run in isolated AWS accounts or with budget alerts
AI-Assisted Approaches
In 2026, AI tools accelerate infrastructure testing significantly. Here’s where they excel.
What AI does well:
- Generating mock configurations from resource schemas
- Writing property validation rules from security requirements
- Creating test data variations for edge cases
- Suggesting assertions you might have missed
What still needs humans:
- Deciding which resources are critical to test
- Understanding business context for validation rules
- Reviewing AI-generated tests for false confidence
- Architecting the overall testing strategy
Useful prompt for generating unit tests:
Given this Pulumi resource definition:
const bucket = new aws.s3.Bucket("data-bucket", {
versioning: { enabled: true },
serverSideEncryptionConfiguration: {
rule: {
applyServerSideEncryptionByDefault: {
sseAlgorithm: "aws:kms",
},
},
},
lifecycleRules: [{
enabled: true,
transitions: [{ days: 30, storageClass: "STANDARD_IA" }],
}],
});
Generate Jest unit tests that verify:
1. Versioning is enabled
2. KMS encryption is configured
3. Lifecycle transitions are set correctly
Use Pulumi's setMocks pattern and handle Output types properly.
Decision Framework
When to Use Unit Tests
This approach works best when:
- You have complex conditional logic in resource creation
- Multiple environments share infrastructure code with different configs
- Your team already uses TypeScript/Python testing frameworks
- You need fast feedback in CI (< 1 minute)
Consider alternatives when:
- Infrastructure is purely declarative with no logic
- You’re prototyping and expect frequent changes
- The team is new to Pulumi (start with property tests)
When to Use Property Tests
This approach works best when:
- Enforcing security/compliance across all stacks
- You have organization-wide standards to maintain
- Teams operate independently but share requirements
- You need deployment-time guardrails
Consider alternatives when:
- Policies are stack-specific (use unit tests)
- You need to test actual cloud behavior (use integration)
- Your policies require external data (complex)
When to Use Integration Tests
This approach works best when:
- Testing interactions between cloud services
- Validating IAM permissions work as expected
- Verifying network connectivity and DNS resolution
- Pre-production smoke tests
Consider alternatives when:
- You need fast feedback (use unit tests)
- You’re testing configuration, not behavior (use unit tests)
- Cost or time constraints are tight
Measuring Success
| Metric | Baseline | Target | How to Track |
|---|---|---|---|
| Unit test coverage | 0% | 80%+ of conditional logic | Coverage tools (nyc, coverage.py) |
| Unit test runtime | N/A | < 60 seconds | CI pipeline metrics |
| Property test violations | Unknown | 0 in production | Policy pack reports |
| Integration test flakiness | High | < 5% failure rate | CI failure analysis |
| Mean time to detect issues | Hours/days | Minutes | Incident tracking |
Warning signs it’s not working:
- Unit tests pass but deployments fail—mocks don’t match reality
- Integration tests take > 30 minutes—too slow for meaningful feedback
- Property violations discovered in production—policies aren’t comprehensive
- High test maintenance burden—over-specified tests
Common Pitfalls
1. Testing Implementation, Not Behavior
// Bad: Testing internal structure
expect(bucket.tags).toEqual({ Name: "my-bucket", Env: "prod" });
// Good: Testing meaningful behavior
expect(bucket.versioning?.enabled).toBe(true);
2. Forgetting Output Unwrapping
Pulumi resources return Output<T>, not T. Always use .apply() or the test decorator.
# This will fail silently
def test_bad(self):
self.assertEqual(bucket.arn, "expected-arn") # Comparing Output object!
# This works
@pulumi.runtime.test
def test_good(self):
return bucket.arn.apply(lambda arn: self.assertIn("s3", arn))
3. Incomplete Mocks
If your tests pass but deployments fail, your mocks might not match real provider behavior.
newResource: (args) => {
// Realistic: include computed fields
if (args.type === "aws:s3/bucket:Bucket") {
return {
id: args.name,
state: {
...args.inputs,
arn: `arn:aws:s3:::${args.inputs.bucket}`,
bucketDomainName: `${args.inputs.bucket}.s3.amazonaws.com`,
region: "us-west-2",
},
};
}
// Generic fallback
return { id: args.name, state: args.inputs };
}
What’s Next
Start small: pick one critical resource (your production database, main S3 bucket, or VPC configuration) and write three unit tests for it. Use the Jest/pytest patterns above.
Once comfortable, add a policy pack with your top security requirement. That’s shift-left in action—catching issues before they reach production.
Related articles:
- Terraform Testing and Validation Strategies
- Test Automation Pyramid Strategy
- CloudFormation Template Testing
- Test Reporting in CI/CD
External resources:
Official Resources
FAQ
What makes Pulumi testing different from Terraform testing?
Pulumi uses native language test frameworks (Jest for TypeScript, pytest for Python) with mock infrastructure providers, so tests run in milliseconds without provisioning real resources. Terraform requires HCL-specific tools like Terratest. Pulumi’s mocking system intercepts resource creation calls, letting you verify configurations without cloud credentials or costs.
What are Pulumi unit tests and how do I write them?
Pulumi unit tests use pulumi.runtime.setMocks() to intercept resource creation and return mock values. Write tests in your native framework (Jest/pytest/go test), call your Pulumi program, and assert on resource properties. This verifies all resources before any infrastructure is provisioned.
What is CrossGuard in Pulumi?
CrossGuard is Pulumi’s policy-as-code engine that validates resources during pulumi up before provisioning. Define policies in TypeScript or Python using the @pulumi/policy SDK — rules check resource configurations and fail the deployment if violations occur.
When should I use integration tests vs unit tests in Pulumi?
Use unit tests (with mocks) for resource configuration validation, dependency graph checks, naming conventions, policy compliance — runs in seconds, no cloud costs. Use integration tests for actual provisioning verification. Aim for 90% unit / 10% integration split.
See Also
- Cost Estimation Testing for Infrastructure as Code: Complete Guide - Master cost estimation testing for IaC with Infracost, terraform…
- Infrastructure as Code Testing: Validation Strategies for Terraform and Ansible - IaC testing: Terraform validation, Terratest, kitchen-terraform,…
- CircleCI Testing Best Practices - Master CircleCI testing workflows: config optimization, parallel…
- Docker Image Testing and Security: Complete Guide to Container Vulnerability Scanning - Master Docker image security with Trivy, Snyk, and Grype. Learn…
