Testing infrastructure

Unit and integration tests for infra.

Advanced12 min · lesson 11 of 12

Because a Pulumi program is code, you can test it with the language’s normal test tools — a genuine advantage of the model. Two levels: unit tests mock the cloud and assert on resource inputs (“the bucket is never public,” “every instance has a tag”) in milliseconds with no deployment; integration tests actually deploy a stack to a sandbox, assert against the real resources, then tear it down.

index.test.ts
import * as pulumi from "@pulumi/pulumi";
pulumi.runtime.setMocks({ // no real cloud calls
newResource: (a) => ({ id: `${a.name}-id`, state: a.inputs }),
call: () => ({}),
});
describe("infra", () => {
it("never makes a public bucket", async () => {
const infra = await import("./index");
const acl = await promiseOf((infra.logs as any).acl);
expect(acl).not.toBe("public-read");
});
});

Integration and property tests

For higher confidence, the integration test framework deploys the stack to a throwaway environment, runs assertions (the endpoint responds, the bucket rejects public access) against live infrastructure, and destroys it. And CrossGuard doubles as property testing — running the policy pack in a test asserts invariants hold for whatever the program generates, not just the cases you hand-wrote.

terminal
$ npm test # fast unit tests, mocked, no deploy
PASS ./index.test.ts (0.9s)
$ go test ./integration # deploys to a sandbox, asserts, destroys
Unit-test the invariants, integration-test sparingly
Mocked unit tests are fast and free — assert your security invariants there and run them on every commit. Integration tests create real, billable resources and are slow, so reserve them for critical paths and always ensure teardown runs even on failure (a finally/defer destroy), or a failed test run leaks live infrastructure and cost.