My tests isn't working with react-testing-library with typescript

Viewed 6800

I've got an issue with react-testing-library with my React project on TypeScript.

I've been searching on many websites an answer, checking tutorials but I'm really stuck and can't find the answer, changing some configurations on babel.config.js, tsconfig.json and jest.config.js, installing new packages (not sure if I had to install @types for testing-library for example) but I'm not expert with all of this and I'm lost.

I've tried an easy test and it says :

Unexpected token, expected "," (11:47)

       9 | describe('Test SectionTitle', () => {
      10 |   test('renders message', () => {
    > 11 |     const { getByText } = render(<SectionTitle tag="h1">Mon titre</SectionTitle>)
         |                                                ^
      12 | 
      13 |     expect(getByText('Mon titre')).toBeInTheDocument()
      14 |   })

The structure of my project is as belows :

    .
    ├── public                      # Where build files are (shoudn't be this way)
    ├── src                         # Source files (alternatively `lib` or `app`)
    │   ├── assets                  # All images
    │   ├── components              # All the components
    │   │   ├── component.spec.ts   # Test files 
    │   │   ├── component.styled.ts # Style of the component with styled-components 
    │   │   ├── component.tsx       # Component file
    │   │   └── index.ts            # Choose what to export
    │   ├── routes                  # All routes with react-router-dom
    │   ├── screens                 # All screens
    │   │   ├── screen.spec.ts      # Test files 
    │   │   ├── screen.styled.ts    # Style of the screen with styled-components 
    │   │   ├── screen.meta.tsx     # Meta tag of the screen with react-helmet 
    │   │   ├── screen.tsx          # Screen file
    │   │   └── index.ts            # Choose what to export
    │   ├── sitemap                 # Sitemap generator (issue about missing some parameters, changefreq, priority, lastmod etc.)[https://github.com/kuflash/react-router-sitemap/issues/39]
    │   ├── style                   # Helpers, utils, variables CSS, global style etc
    │   ├── index.tsx               # Index file
    │   ├── setupTests.ts           # Packages to be imported for tests
    └── README.md
    └── webpack.config.js           # Config of webpack
    └── babel.config.js             # Config of babel
    └── jest.config.js              # Config of jest
    └── tsconfig.json               # Config for TypeScript like imported paths with '@'
    └── custom.d.ts                 # Adding Custom Type Definitions
    └── .prettierrc                 # Rules for prettier
    └── .prettierignore             # Files to be ignored for prettier
    └── .gitignore                  # Files to be ignored for git
    └── package.json                # List of packages and commands to run with yarn
    └── yarn.lock                   # Packages in the node_modules folder

Here's my package.json dependencies and scripts :

{
  "dependencies": {
    "framer-motion": "^1.11.0",
    "react": "^16.13.1",
    "react-cookie-consent": "^5.0.1",
    "react-dom": "^16.13.1",
    "react-ga": "^3.0.0",
    "react-helmet": "^6.0.0",
    "react-router-dom": "^5.1.2",
    "styled-components": "^5.1.0"
  },
  "scripts": {
    "prebuild": "yarn run sitemap",
    "build": "webpack --mode production",
    "start": "webpack-dev-server --mode development",
    "sitemap": "babel-node src/sitemap/sitemap-builder.js",
    "test": "jest -c jest.config.js --watch"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "./src/**/*.{ts,tsx}": [
      "prettier --write ."
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@babel/cli": "^7.8.4",
    "@babel/core": "^7.9.6",
    "@babel/node": "^7.10.1",
    "@babel/plugin-transform-typescript": "^7.10.1",
    "@babel/preset-env": "^7.9.6",
    "@babel/preset-react": "^7.10.1",
    "@testing-library/jest-dom": "^5.11.0",
    "@testing-library/react": "^10.4.3",
    "@types/jest": "^26.0.3",
    "@types/node": "^14.0.14",
    "@types/react": "^16.9.0",
    "@types/react-dom": "^16.9.0",
    "@types/react-helmet": "^6.0.0",
    "@types/react-router-dom": "^5.1.5",
    "@types/styled-components": "^5.1.0",
    "@types/testing-library__jest-dom": "^5.9.1",
    "@types/testing-library__react": "^10.2.0",
    "babel-loader": "^8.1.0",
    "css-loader": "^3.5.3",
    "file-loader": "^6.0.0",
    "husky": "^4.2.5",
    "jest": "^26.1.0",
    "lint-staged": "^10.2.2",
    "prettier": "^2.0.5",
    "react-router-sitemap": "^1.2.0",
    "source-map-loader": "^0.2.4",
    "style-loader": "^1.2.1",
    "ts-jest": "^26.1.1",
    "ts-loader": "^7.0.3",
    "tsconfig-paths-webpack-plugin": "^3.2.0",
    "typescript": "~3.7.2",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.11.0"
  }
}

Here's my jest.config.js :

module.export = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  transform: {
    "^.+\\.tsx?$": "ts-jest"
  },
  roots: ['<rootDir>/src'],
  testMatch: ['<rootDir>/src/**/>(*.)spec.{ts, tsx}'], // finds test
  moduleFileExtensions: ['ts', 'tsx', 'json', 'node'],
  testPathIgnorePatterns: ['/node_modules/', '/public/'],
  setupFilesAfterEnv: '<rootDir>/src/setupTests.ts'
};

Here is my setupTests.ts :

import '@testing-library/jest-dom/extend-expect'
import '@testing-library/react/cleanup-after-each'

This is my tsconfig.json :

{
  "compilerOptions": {
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "target": "es5",
    "module": "commonjs",
    "rootDir": "src",
    "outDir": "public",
    "jsx": "react",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "baseUrl": "src",
    "paths": {
      "@assets/*": [
        "assets/*"
      ],
      "@components/*": [
        "components/*"
      ],
      "@routes/*": [
        "routes/*"
      ],
      "@screens/*": [
        "screens/*"
      ],
      "@style/*": [
        "style/*"
      ]
    }
  },
  "include": [
    "src",
    "custom.d.ts"
  ]
}

This is my babel.config.js :

module.exports = {
  presets: ["@babel/preset-env", "@babel/preset-react"],
  plugins: ["@babel/plugin-transform-typescript"]
};

This is my webpack.config.js :

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

module.exports = {
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
    plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })]
  },
  devServer: {
    contentBase: path.join(__dirname, "public"),
    compress: true,
    port: 3000,
    historyApiFallback: true,
    publicPath: '/'
  },
  entry: path.resolve(__dirname, "src", "index.tsx"),
  output: {
    path: path.resolve(__dirname, "public"),
    filename: "bundle.js",
    publicPath: '/'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: ["babel-loader"]
      },
      {
        test: /\.ts(x?)$/,
        exclude: /node_modules/,
        use: [{ loader: "ts-loader" }]
      },
      {
        test: /\.css$/,
        use: [{ loader: "style-loader" }, { loader: "css-loader" }]
      },
      {
        test: /\.(png|svg|jpg|gif)$/i,
        use: ["file-loader"]
      },
      {
        enforce: "pre",
        test: /\.js$/,
        loader: "source-map-loader"
      }
    ]
  }
};

The component SectionTitle.tsx :

import React from 'react'
import SectionTitleWrapper from './SectionTitle.styled'

interface IProps {
  children: JSX.Element[] | JSX.Element | string
  tag: 'h1' | 'h2'
}

const SectionTitle = (props: IProps) => <SectionTitleWrapper as={props.tag}>{props.children}</SectionTitleWrapper>

export default SectionTitle

And the test SectionTitle.spec.tsx :

import React from 'react'
import { render, cleanup } from '@testing-library/react'
import SectionTitle from './SectionTitle'

afterEach(cleanup)

describe('Test SectionTitle', () => {
  test('renders message', () => {
    const { getByText } = render(<SectionTitle tag="h1">Mon titre</SectionTitle>)

    expect(getByText('Mon titre')).toBeInTheDocument()
  })
})

To summarize, the website is working, the build is working too but not the tests :/ Thanks in advance for helping me !

2 Answers

I've forgotten an 's' in module.exports in my jest config. I've added this in my jest config :

  moduleNameMapper: {
    '^@assets/(.*)$': '<rootDir>/src/assets/$1',
    '^@components/(.*)$': '<rootDir>/src/components/$1',
    '^@routes/(.*)$': '<rootDir>/src/routes/$1',
    '^@screens/(.*)$': '<rootDir>/src/screens/$1',
    '^@style/(.*)$': '<rootDir>/src/style/$1'
  }

And I've upgraded TypeScript to this version : ~3.8.0

Better to use as following:

interface IProps {
  children: React.ReactNode | React.ReactNode[];
  tag: 'h1' | 'h2';
}

Because:

type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

UPD: Or, maybe you have to pass children as a first parameter and a wrapper component in the render options, like this:

const { getByText } = render(<>Mon titre</>, {
  wrapper: SectionTitle as React.FunctionComponent,
});
Related