Lesson 14: Testing - strvcom/frontend-academy-2022 GitHub Wiki

Speaker: Michal Jarnot

Resources


Installation

Jest

yarn add jest ts-jest ts-node -D

https://jestjs.io/docs/getting-started

Create jest.config.ts in the root of your project:

import nextJest from 'next/jest'
import { join } from 'path'
import { pathsToModuleNameMapper } from 'ts-jest'

import { compilerOptions } from './tsconfig.json'

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

//jestjs.io/docs/configuration
const customJestConfig = {
  // Add more setup options before each test is run
  // https://jestjs.io/docs/configuration#testenvironment-string
  testEnvironment: 'jest-environment-jsdom',
  modulePaths: ['<rootDir>/src'], // https://jestjs.io/docs/configuration#modulepaths-arraystring
  // https://jestjs.io/docs/configuration#modulenamemapper-objectstring-string--arraystring
  moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
    prefix: join('<rootDir>', compilerOptions.baseUrl),
  }),
}

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

Storybook

Install Storybook

npx sb init --builder webpack5

Add webpack plugin to support absolute imports:

yarn add tsconfig-paths-webpack-plugin -D

Update main.js in .storybook/

const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')

module.exports = {
  webpackFinal: async (config) => {
    config.resolve.plugins = [
      ...(config.resolve.plugins || []),
      new TsconfigPathsPlugin({
        extensions: config.resolve.extensions,
      }),
    ]
    return config
  },
  stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
  ],
  framework: '@storybook/react',
  core: {
    builder: '@storybook/builder-webpack5',
  },
}

Update preview.js in .storybook/

import { GlobalStyle } from '../src/features/ui/theme/global'

export const parameters = {
  actions: { argTypesRegex: '^on[A-Z].*' },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
}

const withStyles = (Story, context) => (
  <>
    <GlobalStyle />
    <Story {...context} />
  </>
)

export const decorators = [withStyles]

Cypress

Install Cypress:

yarn add cypress -D

Run Cypress and force it to create basic configuration:

npx cypress open

https://docs.cypress.io/guides/tooling/typescript-support#Install-TypeScript

Update cypress.config.js in root of your project:

/* eslint-disable import/no-default-export,@typescript-eslint/no-unused-vars,@typescript-eslint/no-misused-promises */
import { defineConfig } from 'cypress'
import http from 'http'
import next from 'next'

// TODO: .env
const BASE_URL = 'http://localhost:3000'
const NEXT_PUBLIC_API_URL = 'https://testproject-api-v2.strv.com/'

export default defineConfig({
  e2e: {
    baseUrl: BASE_URL,
    setupNodeEvents: async (on, config) => {
      config.env.api_url = NEXT_PUBLIC_API_URL

      const app = next({ dev: true })
      const handleNextRequests = app.getRequestHandler()
      await app.prepare()

      const customServer = new http.Server(async (req, res) => {
        return await handleNextRequests(req, res)
      })

      await new Promise<void>((resolve) => {
        customServer.listen(3000, () => {
          console.log(`> Ready on ${BASE_URL}`)
          resolve()
        })
      })

      // We are not actually fetching events on server. But you can intercept requests with nock
      // https://glebbahmutov.com/blog/mock-network-from-server/
      // on('task', {
      //   clearNock() {
      //     nock.restore()
      //     nock.cleanAll()
      //   },
      //
      //   nock(hostname: string, method: 'get' | 'post', path: string, statusCode: number, body: any) {
      //     nock.activate()
      //     nock(hostname)[method](path).reply(statusCode, body)
      //   },
      // })

      return config
    },
  },
})

Update tsconfig.ts in the root of the project:

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "baseUrl": ".",
    "paths": {
      "~/*": [
        "src/*"
      ]
    }
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx"
  ],
  "exclude": [
    "node_modules",
    "cypress",
    "cypress.config.ts"
  ]
}

Create tsconfig.ts in cypress/

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["es5", "dom"],
    "types": ["cypress", "node"],
    "baseUrl": ".",
    "paths": {
      "~/*": [
        "../src/*"
      ]
    }
  },
  "include": ["**/*.ts"]
}
⚠️ **GitHub.com Fallback** ⚠️