How to test Next.js pages and components with Jest for Sitecore Headless Development

The primary and important goal of any development process is to build production-ready applications. To achieve this, it is essential to write code that is not only fulfills the functional requirements but also remains stable and reliable over the time. 

Testing serves as a safeguard, ensuring that applications function as expected even as new updates are made during the development. 

Adopting a Test-driven development (TDD) approach can help maximize your confidence in your code and minimize time spent debugging and resolving bugs that could have slipped to production.

What Is Jest?

Jest is a popular testing framework that is widely used by different JavaScript frameworks. It provides a suite of testing features such as a powerful test runner, automatic mocking, and snapshot testing.

Jest is majorly used to work with react-native based web applications and with react and Next.js. It mostly focuses on simplicity while doing any unit testing for front end application.

Let's install the Jest dev dependencies in your Next js application so that we can test our components using Jest.

Install Dependencies

Install the necessary dependencies as devDependencies by running the following command:


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

Running the above npm command should install the relevant packages and update your package.json file with the following:
"devDependencies": {
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "babel-jest": "^29.4.2",
    "jest": "^29.4.2",
    "jest-environment-jsdom": "^29.4.2"
  }

Configurations

Before you start writing test cases, it's crucial to configure Jest according to your specific testing requirements. This involves creating and customizing the jest.config.js and jest.setup.js files in the application root directory, which serves as the foundation for your testing setup.

In the root directory, create a new jest.config.js file. Then, add the following code to configure Jest accordingly. This configuration might be different according to your project.
module.exports = {
  collectCoverage: true,
  // on node 14.x coverage provider v8 offers good speed and more or less good report
  coverageProvider: 'v8',
  collectCoverageFrom: [
    '**/*.{js,jsx,ts,tsx}',
    '!**/*.d.ts',
    '!**/*.style.ts',
    '!**/*.styles.ts',
    '!**/*.type.ts',
    '!**/*.types.ts',
    '!**/*.mock.ts',
    '!**/node_modules/**',
    '!<rootDir>/out/**',
    '!<rootDir>/.next/**',
    '!<rootDir>/.next-container/**',
    '!<rootDir>/.storybook/**',
    '!<rootDir>/.generated/**',
    '!<rootDir>/*.config.js',
    '!<rootDir>/coverage/**',
    '!<rootDir>/scripts/**',
    '!<rootDir>/src/stories/**',
    '!<rootDir>/src/temp/**',
    '!<rootDir>/src/theme/**',
    '!<rootDir>/src/lib/**',
    '!<rootDir>/src/logger/**',
    '!<rootDir>/src/Scripts.tsx',
    '!<rootDir>/src/middleware.ts',
    '!<rootDir>/src/pages/**',
  ],
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  testEnvironment: 'jsdom',
  moduleNameMapper: {
    // Handle CSS imports (with CSS modules)
    // https://jestjs.io/docs/webpack#mocking-css-modules
    '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',

    // Handle CSS imports (without CSS modules)
    '^.+\\.(css|sass|scss)$': '<rootDir>/__mocks__/styleMock.js',

    // Handle image and font imports
    // https://jestjs.io/docs/webpack#handling-static-assets
    '^.+\\.(jpg|jpeg|png|gif|svg|bmp|webp|avif|eot|otf|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
      '<rootDir>/__mocks__/fileMock.js',

    // Handle module aliases
    '^@/components/(.*)$': '<rootDir>/components/$1',
  },
};
Add this into jest.setup.js:

import '@testing-library/jest-dom';

The final configuration we need to address is adding the npm test command to the package.json file.

"test": "jest",
"test:coverage": "jest --coverage"


Now, we are all set to write the unit test cases. In this example we are going to write test case for a simple Content Block component, with a heading and rich text block.

Code snippet - ContentBlock.tsx
import { Typography, Box, Theme } from '@mui/material';
import { Text, RichText, Field, TextField } from '@sitecore-jss/sitecore-jss-nextjs';
import { ComponentProps } from 'lib/component-props';
import { Container } from './ContentBlock.styles';

export type ContentBlockProps = ComponentProps & {
  fields?: {
    heading?: TextField;
    content?: Field<string>;
  };
};

/**
 * A simple Content Block component, with a heading and rich text block.
 * This is the most basic building block of a content site, and the most basic
 * JSS component that's useful.
 */

const ContentBlock = (props: ContentBlockProps): JSX.Element => {
  const { fields } = props;
  const heading = fields?.heading;
  const content = fields?.content;
  const headingValue = heading?.value;
  const contentValue = content?.value;

  return (
    <>
      {headingValue || contentValue ? (
        <Container>
          <Box>
            {headingValue && (
              <Typography
                variant="h4"
                fontWeight={'bold'}
                color={(theme: Theme) => theme?.palette?.themeColor?.main}
                paddingTop={'1rem'}
                gutterBottom
              >
                <Text field={heading} />
              </Typography>
            )}
            {contentValue && <RichText className="contentblock-content" field={content} />}
          </Box>
        </Container>
      ) : null}
    </>
  );
};

export default ContentBlock;
Let's write a test to see if all the elements are rendering correctly. Create one test file for this component. For example - ContentBlock.test.tsx and add the following code.
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { render, screen } from '@testing-library/react';
import ContentBlock from './ContentBlock';

describe('ContentBlock component', () => {
  it('renders heading and content if both are provided in the fields prop', () => {
    const fields = {
      heading: {
        value: 'Test Heading',
      },
      content: {
        value: 'Test Content',
      },
    };
    // @ts-ignore
    const { getByRole } = render(<ContentBlock fields={fields} />);
    const headingElement = getByRole('heading', { level: 4 });
    expect(headingElement).toBeInTheDocument();
    expect(headingElement.tagName).toBe('H4');
    expect(headingElement).toHaveTextContent('Test Heading');
    expect(screen.getByText('Test Content')).toBeInTheDocument();
  });

  it('renders only heading if content is not provided in the fields prop', () => {
    const fields = {
      heading: {
        value: 'Test Heading',
      },
    };
    // @ts-ignore
    render(<ContentBlock fields={fields} />);
    expect(screen.getByText('Test Heading')).toBeInTheDocument();
    expect(screen.queryByText('Test Content')).not.toBeInTheDocument();
  });

  it('renders only content if heading is not provided in the fields prop', () => {
    const fields = {
      content: {
        value: 'Test Content',
      },
    };
    // @ts-ignore
    render(<ContentBlock fields={fields} />);
    expect(screen.getByText('Test Content')).toBeInTheDocument();
    expect(screen.queryByText('Test Heading')).not.toBeInTheDocument();
  });

  it('renders nothing if fields prop is not provided', () => {
    // @ts-ignore
    render(<ContentBlock />);
    expect(screen.queryByText('Test Heading')).not.toBeInTheDocument();
    expect(screen.queryByText('Test Content')).not.toBeInTheDocument();
  });
});
The above code is self explanatory. Where we have checked three cases as below - 
  • Only Heading is provided.
  • Only Content is provided.
  • Nothing is provided.
We have also covered the expect like heading should be in H4 tag. 

Now, run the following command to execute the test.

npm run test

You should see a similar response if the test passes. Furthermore, you can enhance your testing approach by incorporating snapshot tests and end-to-end tests.


The Jest configuration file also includes the test coverage configurations. To get the test coverage report run the following npm command.

npm run test:coverage

Comments

Popular posts from this blog

Setup Sitecore XM Cloud Dev Environment using Docker

Sitecore Content Hub - Triggers, Actions and Scripts

All Blog Posts - 2023