[Testing] Config jest to test Javascript Application -- Part 2

Setup an afterEach Test Hook for all tests with Jest setupTestFrameworkScriptFile

With our current test situation, we have a commonality between most of our tests. In each one of them, we're importing 'react-testing-library/cleanup-after-each' in our __tests__/calculator.js, and __tests__/auto-scaling-text.js, and __tests__/calculator-display.js.

 

__tests__/auto-scaling-text.js

复制代码
1 import 'react-testing-library/cleanup-after-each'
2 import React from 'react'
3 import {render} from 'react-testing-library'
4 import AutoScalingText from '../auto-scaling-text'
5 
6 test('renders', () => {
7     const {container} = render(<AutoScalingText />)
8     console.log(container.innerHTML)
9 })
View Code
复制代码

__tests__/calculator.js

复制代码
import 'react-testing-library/cleanup-after-each'
import React from 'react'
import {render} from 'react-testing-library'
import Calculator from '../../calculator'

test('renders', () => {
    render(<Calculator />)
  })
View Code
复制代码

__tests__/calculator-display.js.

复制代码
import 'react-testing-library/cleanup-after-each'
import React from 'react'
import {render} from 'react-testing-library'
import CalculatorDisplay from '../calculator-display'
import {createSerializer} from 'jest-emotion';
import * as emotion from 'emotion';

expect.addSnapshotSerializer(createSerializer(emotion));

test('mounts', () => {
  const {container} = render(<CalculatorDisplay value="0" />)
  expect(container.firstChild).toMatchSnapshot()
})
View Code
复制代码

 

There are some common code we use in all the tests files:

import 'react-testing-library/cleanup-after-each'

And also, emotion libaray will be used a lot in the future project, therefore, we want those code can be automaticlly import into each test file to reduce code duplication.

 

Create a test/setup-tests.js:

import 'react-testing-library/cleanup-after-each'

import {createSerializer} from 'jest-emotion';
import * as emotion from 'emotion';

expect.addSnapshotSerializer(createSerializer(emotion));

 

Then in the jest.config.js file, we config jest to import a file before running each test:

复制代码
module.exports = {
    testEnvironment: 'jest-environment-jsdom', //'jest-environment-node',
    moduleNameMapper: {
        '\\.module\\.css$': 'identity-obj-proxy',
        '\\.css$': require.resolve('./test/style-mock.js')
    }, 
    snapshotSerializers: ['jest-serializer-path'],
    // after jest is loaded
    setupTestFrameworkScriptFile: require.resolve('./test/setup-tests.js')
}
复制代码

 

Support custom module resolution with Jest moduleDirectories

Webpack’s resolve.modules configuration is a great way to make common application utilities easily accessible throughout your application. We can emulate this same behavior in Jest using the moduleDirectories configuration option.

 

For the calculator.js, it is using dynamic loading with webpack:

import React from 'react'
import loadable from 'react-loadable''

const CalculatorDisplay = loadable({
  loader: () => import('calculator-display').then(mod => mod.default),
  loading: () => <div style={{height: 120}}>Loading display...</div>,
})

 

In our test:

import React from 'react'
import {render} from 'react-testing-library'
import Calculator from '../../calculator'

test('renders', () => {
    const {container, debug} = render(<Calculator />);
    debug(container);
  })

It logs out:

复制代码
<div
          class="calculator"
        >
          <div
            style="height: 120px;"
          >
            Loading display...
          </div>
          <div
            class="calculatorKeypad"
          >
            <div
              class="inputKeys"
            >

...
复制代码

We can see it is not fully loaded yet.

 

We can solve the problem:

复制代码
import React from 'react'
import {render} from 'react-testing-library'
import loadable from 'react-loadable'
import Calculator from '../calculator'

test('renders', async () => {
    await loadable.preloadAll();
    const {container, debug} = render(<Calculator />);
    debug(container);
  })
复制代码

 

It show the error:

UnhandledPromiseRejectionWarning: Error: Cannot find module 'calculator-display' from
 'calculator.js'
    at Resolver.resolveModule 

The problem is because in the Calculator.js we import component as if there were a node module:

const CalculatorDisplay = loadable({
  loader: () => import('calculator-display').then(mod => mod.default),
  loading: () => <div style={{height: 120}}>Loading display...</div>,
})

but it's not a node module. It actually lives in the shared directory as calculator-display. The way that it works in the app is we have our webpack configuration set to resolve modules to node_modules, just like node would in a regular commonJS environment.

resolve: {
    modules: ['node_modules', path.join(__dirname, 'src'), 'shared']
}

 

Any of these files inside of our src directory, if they're inside of a shared, they can actually be treated as if they were in node modules, which is a really handy thing for a giant project.

However, that poses a problem for us in Jest because Jest doesn't consume this webpack configuration. It doesn't resolve the modules the way webpack is resolving them.

The way to solve the project is by add those webpack resolve config into jest config as well:

复制代码
const path = require('path');

module.exports = {
    testEnvironment: 'jest-environment-jsdom', //'jest-environment-node',
    moduleDirectories: ['node_modules', path.join(__dirname, 'src'), 'shared'],
    moduleNameMapper: {
        '\\.module\\.css$': 'identity-obj-proxy',
        '\\.css$': require.resolve('./test/style-mock.js')
    }, 
    snapshotSerializers: ['jest-serializer-path'],
    // after jest is loaded
    setupTestFrameworkScriptFile: require.resolve('./test/setup-tests.js')
}
复制代码

the same in the webpack.config.js:

复制代码
const path = require('path')

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve('dist'),
    filename: 'bundle.js',
  },
  resolve: {
    modules: ['node_modules', path.join(__dirname, 'src'), 'shared'],
  },
  module: {
  
复制代码

 

Support a test utilities file with Jest moduleDirectories

Every large testbase has common utilities that make testing easier to accomplish. Whether that be a custom render function or a way to generate random data. Let’s see how we can make accessing these custom utilities throughout the tests easier using Jest’s moduleDirectories feature.

Sometime we are using different kinds of Providers:

复制代码
import React from 'react'
import {ThemeProvider} from 'emotion-theming'
import Calculator from './calculator'
import * as themes from './themes'

class App extends React.Component {
  state = {theme: 'dark'}
  handleThemeChange = ({target: {value}}) => this.setState({theme: value})
  render() {
    return (
      <ThemeProvider theme={themes[this.state.theme]}>
        <React.Fragment>
          <Calculator />
复制代码

 

For that we have to update our tests components to adopt the changes.

复制代码
import React from 'react'
import {render} from 'react-testing-library'
import CalculatorDisplay from '../shared/calculator-display'
import {ThemeProvider} from 'emotion-theming'
import {dark} from '../themes'

function renderWithProvider (ui, options) {
  return render(
    <ThemeProvider theme={dark}>
      {ui}
    </ThemeProvider>,
    options
    );
}

test('mounts', () => {
  const {container} = renderWithProvider(<CalculatorDisplay value="0" />)
  expect(container.firstChild).toMatchSnapshot()
})
复制代码

 

One thing we want to do to simplfiy the process is by creating a 'render' function with render the component with all the Providers which is necessary, therefore I don't need to worry about wirte Provider wrap every times inside the tests.

So we create a new file in test/calculator-test-util.js:

复制代码
import React from 'react'
import {render} from 'react-testing-library'
import {ThemeProvider} from 'emotion-theming'
import {dark} from '../src/themes'

function renderWithProviders (ui, options) {
  return render(
    <ThemeProvider theme={dark}>
      {ui}
    </ThemeProvider>,
    options
    );
}

export * from 'react-testing-library';
export {renderWithProviders as render};
复制代码

test:

import React from 'react'
import {render} from '../../test/calculator-test-util'
import CalculatorDisplay from '../shared/calculator-display'

test('mounts', () => {
  const {container} = render(<CalculatorDisplay value="0" />)
  expect(container.firstChild).toMatchSnapshot()
})

 

Now the 'calculator-test-util.js' can be used in multi files as well, so one thing we want further imporve is:

import {render} from '../../test/calculator-test-util'

As the project grows, the nested path will go crazy, so the way we want is:

import {render} from '.calculator-test-util'

To achieve that, we need to modify the jest.config.js:

复制代码
const path = require('path');

module.exports = {
    testEnvironment: 'jest-environment-jsdom', //'jest-environment-node',
    moduleDirectories: [
        'node_modules', 
        path.join(__dirname, 'src'), 
        'shared',
        path.join(__dirname, 'test'),
    ],
    moduleNameMapper: {
        '\\.module\\.css$': 'identity-obj-proxy',
        '\\.css$': require.resolve('./test/style-mock.js')
    }, 
    snapshotSerializers: ['jest-serializer-path'],
    // after jest is loaded
    setupTestFrameworkScriptFile: require.resolve('./test/setup-tests.js')
}
复制代码

 

Because in calculator-test-utils.js we export everything from 'react-testing-library', so we can replace everywhere we use it.

 

last thing is eslint shows the error:

[eslint] Unable to resolve path to module 'calculator-test-util'. [import/no-unresolved]

 Install:

npm i -D eslint-import-resolver-jest

Adjust the eslint config:

复制代码
module.exports = {
  extends: [
    'kentcdodds',
    'kentcdodds/import',
    'kentcdodds/webpack',
    'kentcdodds/jest',
    'kentcdodds/react',
  ],
  overrides: [
    {
      files: ['**/__tests__/**'],
      settings: {
        'import/resolver': {
          jest: {
            jestConfigFile: path.join(__dirname, './jest.config.js'),
          }
        }
      }
    }
  ]
}
复制代码

Now eslint can help with checking our package name is correct.

 

Step through Code in Jest using the Node.js Debugger and Chrome DevTools

Sometimes it can be a real challenge to determine what’s going on when testing your code. It can be really helpful to step through your code in a debugger. In this lesson we’ll see how to use Jest’s --runInBand flag with node’s --inspect-brk to debug our tests in Chrome’s debugger.

--runBand: make jest run in sequence

--inspect-brk: enable debugger in node for jest

Create script in package.json:

"test:debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand",

 

Run:

npm run test:debug

Open:

chrome://inspect/#devices

Then you can get broswer debugging experience.

 

Configure Jest to report code coverage on project files

Jest comes with code coverage reporting built-into the framework, let’s see how quick and easy it is to add code coverage reporting to our project and take a look at the generated report.

There maybe some folder we don't want to include into our test coverage to get a more actual coverage report. To do that in jest.config.js:

复制代码
const path = require('path');

module.exports = {
    testEnvironment: 'jest-environment-jsdom', //'jest-environment-node',
    moduleDirectories: [
        'node_modules', 
        path.join(__dirname, 'src'), 
        'shared',
        path.join(__dirname, 'test'),
    ],
    moduleNameMapper: {
        '\\.module\\.css$': 'identity-obj-proxy',
        '\\.css$': require.resolve('./test/style-mock.js')
    }, 
    snapshotSerializers: ['jest-serializer-path'],
    // after jest is loaded
    setupTestFrameworkScriptFile: require.resolve('./test/setup-tests.js'),
    collectCoverageFrom: ['**/src/**/*.js'],
}
复制代码

 

Set a code coverage threshold in Jest to maintain code coverage levels

Wherever you are at with code coverage you generally don’t want that level to go down. Let’s add coverage thresholds globally as well as in specific files to ensure we never drop below a certain level of coverage.

复制代码
const path = require('path');

module.exports = {
    testEnvironment: 'jest-environment-jsdom', //'jest-environment-node',
    moduleDirectories: [
        'node_modules', 
        path.join(__dirname, 'src'), 
        'shared',
        path.join(__dirname, 'test'),
    ],
    moduleNameMapper: {
        '\\.module\\.css$': 'identity-obj-proxy',
        '\\.css$': require.resolve('./test/style-mock.js')
    }, 
    snapshotSerializers: ['jest-serializer-path'],
    // after jest is loaded
    setupTestFrameworkScriptFile: require.resolve('./test/setup-tests.js'),
    collectCoverageFrom: ['**/src/**/*.js'],
    coverageThreshold: {
        global: {
            statements: 80,
            branchs: 80,
            lines: 80,
            functions: 80,
        },
        // for single file coverage threshold
        './src/shared/utils.js': {
            statements: 100,
            branchs: 80,
            lines: 100,
            functions: 100,
        }
    }
}
复制代码

 

Report Jest Test Coverage to Codecov through TavisCI

The coverage report generated by Jest is fantastic, but it’d be great to track that coverage over time and be able to review that coverage at a glance, maybe even put it up on a display in the office! Codecov.io is a fantastic service that can consume our code coverage report and it integrates great with GitHub. Let’s see how we can extend our existing Travis build configuration to send the coverage report to Codecov.io.

复制代码
// .travis.yml

sudo: false
language: node_js
cache:
  directories:
    - ~/.npm
notifications:
  email: false
node_js: '8'
install: npm install
script: npm run validate
after_script: npx codecov@3
branches:
  only: master
复制代码

 

posted @   Zhentiw  阅读(822)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2017-11-21 [ES6] The Iterator Protocol
2017-11-21 [Python] for.. not in.. Remove Deduplication
2017-11-21 [Python] String Join
2017-11-21 [Python] Slicing Lists
2015-11-21 [Falcor] Intro to JSON Graph
2014-11-21 [ES6] 09. Destructuring Assignment -- 2
2014-11-21 [ES6] 08. Destructuring Assignment -- 1
点击右上角即可分享
微信分享提示