AWS Amplify Unit Test
Hello, happy holidays, and well wishes for the end of this year and the start of the next. This transitional period going into 2024 has me feeling the need to tidy things up. Get rid of old, unused domains, sunset some legacy projects, that sort of thing. In the spirit of cleaning up my digital presence, I'm redesigning https://joeyreyes.rocks, my music site and counterpart to this site. It's currently a pretty rough-looking Gatsby site deployed to Netlify.
The biggest thing about the site is the shows archive, which is managed through Contentful. I am keeping this portion of the setup as-is for now, but adding in some new fields to my Show
content model. I'm taking this opportunity to test out Contentful's environments feature.
The first architecture change I want to accomplish with this redesign is migrating from Gatsby to Next.js. Gatsby just seems like it's on its last leg, and Next.js seems to have won all of the market share and maturity, at least for the moment. We've also migrated to Next.js at work, so I'm already quite familiar with it.
The second thing I want to test out with this redesign is deployment alternatives to Netlify, specifically AWS Amplify. I love Netlify and all, but we use AWS for a lot of stuff at work, and it's just a bit more of an industry standard, so this is an effort to get more comfortable there. The landscape is full of deployment and hosting services - Netlify, Heroku, GitHub Pages, and of course Vercel, the turnkey solution for Next.js. Amplify is AWS's entry into this crowded space, tying together several distinct AWS frontend, backend, CI/CD, logging, and deployment services. Personally I'm a big fan of the GUI for environment variables, the deployment YAML file, and Git branch preview deployments. Of course, all of this can be managed through something like Terraform if you want to go the IaC route.
There's other stuff I'm toying with that I won't get into detail here - migrating DNS off of Google Domains, testing out Tailwind CSS, using Bun for local development. What I really wanted to write about in this blog post was running a Jest unit test in AWS Amplify, since it's not super straightforward how to run a simple little test.
The Test
I wrote this little <FormattedTime />
component, which accepts a date
in YYYY-MM-DD
format, and returns a readable date within a <time>
element:
// app/components/FormattedDate.jsimport { parseISO, format } from "date-fns";
export default function FormattedDate({ dateString }) { const date = parseISO(dateString); return <time datetime={dateString}>{format(date, "LLLL d, yyyy")}</time>;}
The idea is that it takes in a date format like 2010-02-04
and returns it as February 4, 2010
, which is what gets rendered onto the page as the following:
<time datetime="2010-02-04">February 4, 2010</time>
Here's the test I wound up writing:
// tests/formattedDate.test.jsimport FormattedDate from "../app/components/FormattedDate";import "@testing-library/jest-dom";import { render, screen } from "@testing-library/react";
describe("Date", () => { it("renders a date", () => { render(<FormattedDate dateString="2010-02-04" />); expect(screen.getByText("February 4, 2010")); });});
As mentioned before, I wrote this test using Jest and React Testing Library. I can run the test using bun run test
, which runs the following script (from my package.json
):
// package.json"test": "jest"
This command runs all of the tests in the tests
directory. In the case of the formattedDate
test, it's simply rendering the <FormattedDate />
component with the dateString
prop value we pass it, simulating a browser DOM rendering that component, and checking for the text value "February 4, 2010"
to be in that simulated browser DOM. Running it as-is, I get the following:
$ bun run test$ jest PASS tests/formattedDate.test.js Date ✓ renders a date (13 ms)
Test Suites: 1 passed, 1 totalTests: 1 passed, 1 totalSnapshots: 0 totalTime: 0.654 s, estimated 1 s
If I change the expected output:
// tests/formattedDate.test.jsimport FormattedDate from "../app/components/FormattedDate";import "@testing-library/jest-dom";import { render, screen } from "@testing-library/react";
describe("Date", () => { it("renders a date", () => { render(<FormattedDate dateString="2010-02-04" />); expect(screen.getByText("December 4, 2010")); // changed the month here });});
And rerun the test, then it (expectedly) fails:
$ bun run test$ jest FAIL tests/formattedDate.test.js Date ✕ renders a date (19 ms)
● Date › renders a date
TestingLibraryElementError: Unable to find an element with the text: December 4, 2010. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
Test Suites: 1 failed, 1 totalTests: 1 failed, 1 totalSnapshots: 0 totalTime: 0.771 s, estimated 1 s
Pretty straightforward, huh?
Now I want to run this test through the AWS Amplify CI. But the problem is that the built-in testing area is only for End to End (E2E) testing.
The solve is to add npm run test
to amplify.yml
:
# amplify.ymlbuild: commands: - npm run test && npm run build
The useful thing here is to add it before the actual npm run build
script. This way, if the unit test fails, it will stop the build and Amplify won't let you deploy until you revisit the test with a passing condition.