Unit Tests in Serverless
Our serverless app is made up of two big parts; the code that defines our infrastructure and the code that powers our Lambda functions. We’d like to be able to test both of these.
On the infrastructure side, we want to make sure the right type of resources are being created. So we don’t mistakenly deploy some updates.
On the Lambda function side, we have some simple business logic that figures out exactly how much to charge our user based on the number of notes they want to store. We want to make sure that we test all the possible cases for this before we start charging people.
SST comes with built in support for writing and running tests. It uses Vitest internally for this.
Testing CDK Infrastructure
Let’s start by writing a test for the CDK infrastructure in our app. We are going to keep this fairly simple for now.
Add Vitest to the workspace. Run the following in the project root.
$ pnpm add --save-dev --workspace-root vitest
Add the following to stacks/test/StorageStack.test.ts
.
import { it } from "vitest";
import { initProject } from "sst/project";
import { App, getStack } from "sst/constructs";
import { StorageStack } from "../StorageStack";
import { Template } from "aws-cdk-lib/assertions";
it("Test StorageStack", async () => {
await initProject({});
const app = new App({ mode: "deploy" });
// WHEN
app.stack(StorageStack);
// THEN
const template = Template.fromStack(getStack(StorageStack));
template.hasResourceProperties("AWS::DynamoDB::Table", {
BillingMode: "PAY_PER_REQUEST",
});
});
This is a very simple CDK test that checks if our storage stack creates a DynamoDB table and that the table’s billing mode is set to PAY_PER_REQUEST
. This is the default setting in SST’s Table
construct. This test is making sure that we don’t change this setting by mistake.
Testing Lambda Functions
We are also going to test the business logic in our Lambda functions.
Create a new file in packages/core/test/cost.test.ts
and add the following.
import { expect, test } from "vitest";
import { calculateCost } from "../src/cost";
test("Lowest tier", () => {
const storage = 10;
const cost = 4000;
const expectedCost = calculateCost(storage);
expect(cost).toEqual(expectedCost);
});
test("Middle tier", () => {
const storage = 100;
const cost = 20000;
const expectedCost = calculateCost(storage);
expect(cost).toEqual(expectedCost);
});
test("Highest tier", () => {
const storage = 101;
const cost = 10100;
const expectedCost = calculateCost(storage);
expect(cost).toEqual(expectedCost);
});
This should be straightforward. We are adding 3 tests. They are testing the different tiers of our pricing structure. We test the case where a user is trying to store 10, 100, and 101 notes. And comparing the calculated cost to the one we are expecting.
Run Tests
Now let’s add a test script.
Add the following to the scripts
in the package.json
in your project root.
"test": "sst bind vitest run",
And we can run our tests by using the following command in the root of our project.
$ pnpm test
You should see something like this:
✓ packages/core/test/cost.test.js (3)
✓ stacks/test/StorageStack.test.js (1)
Test Files 2 passed (2)
Tests 4 passed (4)
Time 3.75s (in thread 91ms, 4116.79%)
And that’s it! We have unit tests all configured. These tests are fairly simple but should give you an idea of how to add more in the future. The key being that you are testing both your infrastructure and your functions.
Commit the Changes
Let’s commit our changes and push it to GitHub.
$ git add .
$ git commit -m "Adding unit tests"
$ git push
Now we are almost ready to move on to our frontend. But before we do, we need to ensure that our backend is configured so that our React app will be able to connect to it.
For help and discussion
Comments on this chapter