CI for Next.js with GraphQL by Cypress and GitHub Actions
CI is used to run automated testings before merging a change into the main branch. For Node.js application, I set up GitHub Action to run
- Cypress component testings for frontend testings
- Mock the response of GraphQL
- Jest testings for GraphQL resolvers
- Use a docker compose for a MySQL and a Firebase emulator on local and GitHub Action environments
- Unit testings for firebase storage rules
But I couldn't figure out these things
- How to implement Google Sign In of Firebase for Cypress component testings
- I used
signInWithRedirect
andgetRedirectResult
offirebase/auth
- Couldn't click any UI components even I used
cy.origin
norchromeWebSecurity: false
- I used
Usually, granular unit tesitngs are helpful to increase the reusability of But I chose to test only integration testings of a page level or a resolver level to make sure behaviors only.
Testings
Cypress
How to set up is described in the Next.js official document.
Because I wanted to mock some behaviors and reuse functions defined on production code, I used component testings instead of e2e testings. Besides, I couldn't figure out how to sign in by Google before implementing a test case.
Compare to use jest for a browser testings, cypress component testings are useful not to install and set up many of modules for jest, and easier to emulator DOM events.
There are a few things that I need to look into when I implemented component testings.
- Cypress doesn't support [multiple tabs](https:// docs.cypress.io/guides/references/trade-offs#Multiple-tabs).
- For handling the URL parameters of a next.js, there is a react router.
Testings GraphQL resolvers with TypeGraphQL by Jest
How to set up jest for Next.js application is described in the Next.js official document.
But I just used it for server side testings, so I didn't install @testing-library/react
nor @testing-library/jest-dom
.
To use TypeGraphQL, it requires to set up a few things
For TypeScript configurations
First, install a few packages
1npm install --save-dev @types/jest ts-jest ts-node
Then update jest.config.ts to next. Note that preset
will be updated later.
1+ import { pathsToModuleNameMapper } from "ts-jest";
2+ const { compilerOptions } = require("./tsconfig");
3
4const customJestConfig = {
5 // Add more setup options before each test is run
6 // setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
7 // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
8+ preset: "ts-jest",
9+ moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths),
10 moduleDirectories: ["node_modules", "<rootDir>/"],
11 // testEnvironment: "jest-environment-jsdom",
12};
Enable to use async and await on jest files
Just following this document.
At first, use --experimental-vm-modules
option on node to start a jest.
1- jest --watch
2+ node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --watch
Then use default-esm
preset in jest.config.ts
.
1- "preset": "ts-jest",
2+ "preset": "ts-jest/presets/default-esm",
For TypeGraphQL
For type-graphql
, import reflect-metadata has to be imported before running test cases.
Add a line to import it on jest.setup.ts
loaded on setupFilesAfterEnv
.
Error: jest is not defined
An error jest is not defined
occured when I tried to use jest.setTimeout
function.
Add import { jest } from "@jest/global";
to solve this, which is discussed in this comment.
Get a file path for a fixture file
To get a fixture file from the relative path of the test file, use path.resolve
and url.fileURLToPath
1import path from "path";
2import { fileURLToPath } from "url";
3
4path.resolve(
5 path.dirname(fileURLToPath(import.meta.url)),
6 "../../fixtures/video.mp4"
7);
For firebase of jest
If firebase-admin/auth
is used, there was an error like next.
To avoid this error, use getAuth
from a firebase-admin/auth
package.
1 SyntaxError: The requested module 'firebase-admin' does not provide an export named 'auth'
2
3 at async Runtime.linkAndEvaluateModule (node_modules/jest-runtime/build/index.js:828:5)
Also, in order to connect to firebase auth and storage emulators from jest testings, define following environment variables. This is also from this github issue.
FIREBASE_AUTH_EMULATOR_HOST
FIREBASE_STORAGE_EMULATOR_HOST
Suggest jest functions by VSCode with Cypress
Suggest jet functions of test files didn't work correctly with Cypress. It's because chai functions are installed by Cypress and they are suggested. And this was not a bug but a configuration issue, and other person also reported in the GitHub issue.
For my case, I put every jest test cases into __tests__
directory, so define tsconfig.json
in the directory and define typeAcquisition
to include jest
1{
2 "extends": "../tsconfig.json",
3 "typeAcquisition": {
4 "include": ["jest"]
5 },
6 "include": ["**/*.ts"]
7}
Containers for MySQL and Firebase emulators
Run a MySQL as a container
Use Docker Compose to build a MySQL container.
In order to initialize a schema for this application, prisma
is used to manage initiailze schemas.
To access a different MySQL container when jest runs, prepare a different env file and mount it to .env
file on prisma container.
Run a Firebase emulator as a container
Build an image for a firebase emulator as a container. I set up by simple Dockerfile like next.
1FROM node:18.2
2
3EXPOSE 4000
4EXPOSE 9099
5EXPOSE 9199
6
7RUN npm install -g firebase-tools
8
9RUN apt update && apt install -y default-jre
10
11ENTRYPOINT [ "firebase" ]
And in order to access the container from the host, add host: 0.0.0.0
to firebase.json
by following a few comments like this or this.
1{
2 "storage": {
3 "rules": "storage.rules"
4 },
5 "emulators": {
6 "auth": {
7 "port": 9099,
8+ "host": "0.0.0.0"
9 },
10 "storage": {
11 "port": 9199,
12+ "host": "0.0.0.0"
13 },
14 "ui": {
15 "enabled": true,
16+ "host": "0.0.0.0"
17 }
18 }
19}
GitHub Actions
Cypress component testings
Follow this setup on GitHub Actions to run cypress component testings.
Docker layer caches of GitHub Actions
There is an article to cache docker images by a Dockerfile. But in order to cache docker layer images for a docker compose, it's required to use a different way. And there is another GitHub Action to do so. I just used it to cache them.
Errors during eslint
Functions naming useXXX
even on a server side is recognized as a React hook and made an error.
1334:5 Error: React Hook "useGenericAuth" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function. react-hooks/rules-of-hooks
I couldn't find a good way to exclude these react hooks only for specific directories like code of server sides, so I just added next line of the function using the function to disable the rule temporarily.
1// eslint-disable-next-line react-hooks/rules-of-hooks