Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions aws-typescript/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
import * as infra from "./infra";
Copy link
Member Author

@justinvp justinvp Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's necessary to separate out the resources into a separate module, because we need to export them to be able to interrogate them in the tests. If we exported them from index.ts then they'd show up as stack outputs, which isn't what we want.

This does make the template more complex, but I can't see a better way to handle it. A potential upside is that it does demonstrate how you can organize your resources into separate modules.

I'm using the generic name infra here for this module. We could pick a name related to the type of resource we're creating. In this case, we're creating a Bucket, so we could name this something like storage instead of infra. However, there's some concern new users might think storage has to do with some kind of Pulumi-specific storage so I'd prefer to keep it a generic name. Also, not all templates are going to be creating storage-related resources, and I'd prefer a generic name that could apply consistently to all of our templates (including the regular typescript template, which doesn't create any cloud resources). Happy to consider alternative generic names beside infra, though.


// Create an AWS resource (S3 Bucket)
const bucket = new aws.s3.BucketV2("my-bucket");

// Export the name of the bucket
export const bucketName = bucket.id;
// Export the name of the bucket.
export const bucketName = infra.bucket.id;
10 changes: 10 additions & 0 deletions aws-typescript/infra.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";

// Create an AWS resource (S3 Bucket) with tags.
export const bucket = new aws.s3.BucketV2("my-bucket", {
tags: {
"Name": "My bucket",
},
});
7 changes: 7 additions & 0 deletions aws-typescript/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @type {import('ts-jest').JestConfigWithTsJest} **/
module.exports = {
testEnvironment: "node",
transform: {
"^.+.tsx?$": ["ts-jest", {}],
},
};
6 changes: 6 additions & 0 deletions aws-typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@
"name": "${PROJECT}",
"main": "index.ts",
"devDependencies": {
"@types/jest": "^29.5.14",
"@types/node": "^18",
"jest": "^29.7.0",
"ts-jest": "^29.2.5",
"typescript": "^5.0.0"
},
"dependencies": {
"@pulumi/aws": "^6.0.0",
"@pulumi/awsx": "^2.0.2",
"@pulumi/pulumi": "^3.113.0"
},
"scripts": {
"test": "jest"
}
}
51 changes: 51 additions & 0 deletions aws-typescript/tests/infra.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import * as pulumi from "@pulumi/pulumi";
import "jest";

// Test helper to convert a Pulumi Output to a Promise.
// This should only be used in tests.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could elaborate a bit more here why this should only be used in tests, maybe something along the lines of:

Suggested change
// This should only be used in tests.
// This should only be used in tests. Outputs track more information
// than Promises, such as the dependencies between resources.
// https://www.pulumi.com/docs/iac/concepts/inputs-outputs/#outputs

function promiseOf<T>(output: pulumi.Output<T>): Promise<T> {
return new Promise(resolve => output.apply(resolve));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I experimented with custom jest matchers here #868

This keeps the "magic" of Outputs, and avoids the (slightly wrong) conversion to promises here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not arguing for including these custom matchers in this PR. I was curious to see what it would look like. I think it's something we can follow up with if there are is enough demand to warrant the extra work. We could package the matchers in a library, that can be (optionally) used for writing tests.

}

describe("infrastructure", () => {
// Define the infra variable as a type whose shape matches the that of the
// to-be-defined infra module.
let infra: typeof import("../infra");

beforeAll(() => {
// Put Pulumi in unit-test mode, mocking all calls to cloud-provider APIs.
pulumi.runtime.setMocks({
// Mock calls to create new resources and return a canned response.
newResource: (args: pulumi.runtime.MockResourceArgs) => {
// Here, we're returning a same-shaped object for all resource types.
// We could, however, use the arguments passed into this function to
// customize the mocked-out properties of a particular resource.
// See the unit-testing docs for details:
// https://www.pulumi.com/docs/iac/concepts/testing/unit/
return {
id: `${args.name}-id`,
state: args.inputs,
};
},

// Mock function calls and return an empty response.
call: (args: pulumi.runtime.MockCallArgs) => {
return {};
},
});
});

beforeEach(async () => {
// Dynamically import the infra module.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could mention that this needs to happen after the mocks have been setup, although it is already captured by the use of beforeEach vs beforeAll.

Suggested change
// Dynamically import the infra module.
// Dynamically import the infra module. This needs to happen
// after the mocks have been setup.

infra = await import("../infra");
});

// Example test. To run, uncomment and run `npm test`.
// describe("bucket", () => {
// it("must have a name tag", async () => {
// const tags = await promiseOf(infra.bucket.tags);
// expect(tags).toBeDefined();
// expect(tags).toHaveProperty("Name");
// });
// });
});
5 changes: 1 addition & 4 deletions aws-typescript/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,5 @@
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.ts"
]
}
}
Loading