Skip to main content

Testing with Jest

We recommend using Jest to test Faust.js apps. Jest is a JavaScript testing framework that uses minimal config, and provides a simple API for writing tests.

Setup

The fastest way to get started is to clone our Faust.js example in the Getting Started section. We'll assume you are starting from there. Now, let's setup Jest!

Install Dependencies

Our Getting Started example uses Next.js 12, which has a built in configuration for Jest.

To setup Jest, install the following dependencies:

npm install --save-dev jest@next jest-environment-jsdom@next @testing-library/react @testing-library/jest-dom
note

We are installing jest and jest-environment-jsdom at the next tag. Faust.js uses ES modules under the hood, which is only supported in jest@next and jest-environment-jsdom@next currently.

Create The Jest Config File

In your root directory, create a jest.config.js file with the following contents:

jest.config.js
// jest.config.js
const nextJest = require('next/jest');

const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
});

// Add any custom config to be passed to Jest
const customJestConfig = {
// Add more setup options before each test is run
// setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
// if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
moduleDirectories: ['node_modules', '<rootDir>/src'],
testEnvironment: 'jest-environment-jsdom',
};

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig);

Create Test Script

Finally, add a script in your package.json file to run Jest tests:

package.json
{
// ... Rest of package.json
"scripts": {
"test": "jest"
}
}

That's it! Now you can create tests and run Jest with npm test.

Example

Let's create a basic test case to see how testing works in a Faust.js app with Jest.

Take the following Header.tsx component:

src/components/Header.tsx
import React from 'react';
import styles from 'scss/components/Header.module.scss';
import Link from 'next/link';
import { client, MenuLocationEnum } from 'client';

interface Props {
title?: string;
description?: string;
}

function Header({
title = 'Headless by WP Engine',
description,
}: Props): JSX.Element {
const { menuItems } = client.useQuery();
const links = menuItems({
where: { location: MenuLocationEnum.PRIMARY },
}).nodes;

return (
<header>
<div className={styles.wrap}>
<div className={styles['title-wrap']}>
<p className={styles['site-title']}>
<Link href="/">
<a>{title}</a>
</Link>
</p>
{description && <p className={styles.description}>{description}</p>}
</div>
<div className={styles.menu}>
<ul>
{links?.map((link) => (
<li key={`${link.label}$-menu`}>
<Link href={link.url ?? ''}>
<a href={link.url}>{link.label}</a>
</Link>
</li>
))}
<li>
<Link href="https://github.com/wpengine/faustjs">
<a
className="button"
href="https://github.com/wpengine/faustjs"
>
GitHub
</a>
</Link>
</li>
</ul>
</div>
</div>
</header>
);
}

export default Header;

As a generic example, let's create a test case that checks for the rendered navigation items. We'll test both the hardcoded items and the items received from GraphQL via GQty. Create a file called src/components/Header.test.tsx.

We'll start off by mocking the Faust.js client module to return a mock GraphQL query result:

import React from 'react';
import { render } from '@testing-library/react';
import Header from './Header';

jest.mock('client', () => {
const { MenuLocationEnum } = jest.requireActual('../client');

return {
client: {
useQuery: () => ({
menuItems: () => ({
nodes: [
{
label: 'Home',
url: 'http://localhost:3000/',
},
{
label: 'About',
url: 'http://localhost:3000/about',
},
],
}),
}),
},
MenuLocationEnum,
};
});

Then, we'll add a test case to ensure that our navigation items are accounted for:

test('Header component renders menu items', () => {
const { getAllByRole } = render(<Header />);

const menuItems = getAllByRole('listitem');
expect(menuItems).toHaveLength(3);

const menuNames = menuItems.map((item) => item.textContent);

expect(menuNames).toEqual(['Home', 'About', 'GitHub']);
});

This leaves you with a final Header.test.tsx file that looks like:

src/components/Header.test.tsx
import React from 'react';
import { render } from '@testing-library/react';
import Header from './Header';

jest.mock('client', () => {
const { MenuLocationEnum } = jest.requireActual('../client');

return {
client: {
useQuery: () => ({
menuItems: () => ({
nodes: [
{
label: 'Home',
url: 'http://localhost:3000/',
},
{
label: 'About',
url: 'http://localhost:3000/about',
},
],
}),
}),
},
MenuLocationEnum,
};
});

it('Header component renders menu items', () => {
const { getAllByRole } = render(<Header />);

const menuItems = getAllByRole('listitem');
expect(menuItems).toHaveLength(3);

const menuNames = menuItems.map((item) => item.textContent);

expect(menuNames).toEqual(['Home', 'About', 'GitHub']);
});

Finally, Use npm run test to run the test and see that it passes.