Adding a Cypress test suite to your full stack Next.js app
Next.js is undoubtedly a powerful way to build React apps. With its recent addition of API routes, it can actually serve as a full stack toolkit to build web applications.
Recently, I started helping out with a full stack Next.js app (database and all) and wanted to add end-to-end integration tests. Cypress was the natural framework to reach for, and I was surprised to find that there weren’t very many guides out there for adding it to Next.js apps. Specifically, there weren’t very many guides for adding Cypress to Next.js apps that interact with databases.
This post is my stab at one of those guides.
Installing the packages
First off, we need to add the necessary package to run Cypress:
$ yarn add cypress --dev
We’ll also add a handy package called start-server-and-test
which will be used to automate booting up and tearing down the app when the test suite runs:
$ yarn add start-server-and-test --dev
And finally, we’ll add a package called dotenv-flow
which lets us easily manage environment variables in development and test environments:
$ yarn add dotenv-flow --dev
Creating the test database
We’ll want to create a separate test database so we don’t clobber development databases with test data. This will heavily vary depending on your use case, but the general steps should be more or less the same:
- Create a new local test database and user.
- Keep its database connection string in
.env.test.local
which will automatically be used in test environments thanks todotenv-flow
. - Use the connection string to connect to and interact with your database in your app.
Example for PostgreSQL
$ createdb my_app_test
$ psql my_app_test
psql_test=# create user YOUR_USERNAME with password 'YOUR_PASSWORD';
psql_test=# grant all privileges on database my_app_test to YOUR_USERNAME;
DATABASE_URL="postgresql://YOUR_USERNAME:YOUR_PASSWORD@localhost:5432/my_app_test"
Adding the scripts
Now let’s add some scripts to package.json
to automate running Cypress in a test environment.
{
"scripts": {
"dev:test": "next dev -p 3001",
"cy:open-only": "cypress open",
"cy:run-only": "cypress run",
"cy:open": "NODE_ENV=test dotenv-flow -- start-server-and-test dev:test 3001 cy:open-only",
"cy:run": "NODE_ENV=test dotenv-flow -- start-server-and-test dev:test 3001 cy:run-only"
}
}
A couple points of interest here:
- We add a
dev:test
command that starts our Next.js app on port 3001. This is to avoid any port conflicts when, say, we want to run the test suite at the same time we have a development environment running. - We specifically set
NODE_ENV=test
when running the main Cypress commands to havedotenv-flow
automatically read our.env.test.local
file to grab the database connection string. - We have
cy:open
andcy:run
to easily let us access the Cypress Electron app or just run in the command line.
Running Cypress for the first time
Now let’s finally run Cypress. The first run will generate files in your project that will serve as the foundation of your suite.
$ yarn cy:open
If you haven’t used Cypress before, this will open an Electron app that gives you a nice UI to configure and run your tests. At this point, you should see a new cypress/
directory in your project as well as a cypress.json
. Let’s tweak that cypress.json
file to look like this:
{
"baseUrl": "http://localhost:3001"
}
This is so Cypress automatically prepends any paths with that URL when we want to run cy.visit()
.
If you’ve made it this far, you’re now ready to start writing your integration tests and running them locally, congrats! 🎉
Configuring CI
This is another step that will vary depending on your use case, but for the sake of example, let’s set up a new workflow in GitHub Actions that will run our Cypress suite on every pull request.
Add a new workflow file at .github/workflows/e2e_tests.yml
:
name: E2E tests (Cypress suite)
on: [pull_request]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: "Setup Postgres database"
uses: harmon758/postgresql-action@v1
with:
postgresql version: "13"
postgresql db: $
postgresql user: $
postgresql password: $
- name: "Setup Yarn dependencies"
uses: bahmutov/npm-install@v1
with:
install-command: yarn --frozen-lockfile
- name: "Run migrations"
run: yarn db:migrate
env:
DATABASE_URL: $
- name: "Run Cypress"
uses: cypress-io/github-action@v2
env:
DATABASE_URL: $
with:
command: yarn cy:run
This example workflow makes a couple assumptions:
- You have
POSTGRES_DB
,POSTGRES_USER
,POSTGRES_PASSWORD
, andDATABASE_URL
added to your GitHub repo’s secrets. - You have a
db:migrate
script setup inpackage.json
which migrates your database and sets up the correct schema for your project.
Now try opening a new PR and celebrate the fact that your test suite now runs on each PR. 🎉
Wrapping up
It took me a minute to piece together this particular configuration when I first went through it, so I hope this post can serve as a solid reference for anyone looking to setup end-to-end tests for their Next.js app. Special thanks to Ash Connolly’s post on setting up Cypress in Next.js. I used his guide as a stepping stone to get to where this ended up.
And finally, feedback is welcome! Feel free to reach out for any comments, questions, or suggestions.
Happy coding.