[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
import 'react-testing-library/cleanup-after-each'
import React from 'react'
import {render} from 'react-testing-library'
import AutoScalingText from '../auto-scaling-text' test('renders', () => {
const {container} = render(<AutoScalingText />)
console.log(container.innerHTML)
})
__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 />)
})
__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()
})
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
[Testing] Config jest to test Javascript Application -- Part 2的更多相关文章
- [Testing] Config jest to test Javascript Application -- Part 1
Transpile Modules with Babel in Jest Tests Jest automatically loads and applies our babel configurat ...
- [Testing] Config jest to test Javascript Application -- Part 3
Run Jest Watch Mode by default locally with is-ci-cli In CI, we don’t want to start the tests in wat ...
- Web.config Transformation Syntax for Web Application Project Deployment
Web.config Transformation Syntax for Web Application Project Deployment Other Versions Updated: Ma ...
- JavaScript Application Architecture On The Road To 2015
JavaScript Application Architecture On The Road To 2015 I once told someone I was an architect. It’s ...
- 转:Transform Web.Config when Deploying a Web Application Project
Introduction One of the really cool features that are integrated with Visual Studio 2010 is Web.Conf ...
- spring cloud config的bootstrap.yml与application.proterties的区别
bootstrap.yml 和application.yml 都可以用来配置参数 bootstrap.yml可以理解成系统级别的一些参数配置,这些参数一般是不会变动的 application.ym ...
- Unit Testing a zend-mvc application
Unit Testing a zend-mvc application A solid unit test suite is essential for ongoing development in ...
- JavaScript Web Application summary
Widget/ HTML DOM (CORE) (local dom) DOM, BOM, Event(Framework, UI, Widget) function(closure) DATA (c ...
- JavaScript Libraries In A TypeScript Application, Revisited
If you haven’t already gotten involved with it, you’ll probably know that TypeScript is becoming inc ...
随机推荐
- systemverilog(3)之Randomize
what to randomize? (1) primary input data <==one data (2)encapsulated input data <== muti grou ...
- django第三天(路由基础和路由分配)
路由基础 url(正则路径,视图函数地址,默认关键字参数,路由别名) 路由由上而下匹配, ""可以匹配任意路由 "^$"来匹配"/" url ...
- 算法学习记录-排序——冒泡排序(Bubble Sort)
冒泡排序应该是最常用的排序方法,我接触的第一个排序算法就是冒泡,老师也经常那这个做例子. 冒泡排序是一种交换排序, 基本思想: 通过两两比较相邻的记录,若反序则交换,知道没有反序的记录为止. 例子: ...
- git2--常用命令
Git 命令详解及常用命令 Git作为常用的版本控制工具,多了解一些命令,将能省去很多时间,下面这张图是比较好的一张,贴出了看一下: 关于git,首先需要了解几个名词,如下: ? 1 2 3 4 Wo ...
- hdu3594 Cactus
仙人掌入门简单题. 先看一篇文档. #include <iostream> #include <cstring> #include <cstdio> using n ...
- 【编程工具】Sublime Text3 之 Emmet 插件的详细使用的方法
这篇关于 Emmet 插件使用的博文之前就想写了,今天刚好闲暇时间,就花了一些时间进行了总结. 我们都这道 Emmet 这款插件在前端设计里被称为神器,确实,神器称号名不虚传.因为这款插件可以帮助我们 ...
- 【管理】个人主义—>集体主义
导读:这个月作为学术部负责人,暴露了很多问题,个人的,集体的!我需要思考的,有很多.现在,我反思图书馆丢书这件事的处理方案:我虽然站在了管理层,却做着员工干的事儿.以一种个人主义.英雄主义去做事儿.却 ...
- iOS转场动画初探
一般我们就用两种转场push和present present /** 1.设置代理 - (instancetype)init { self = [super init]; if (self) { se ...
- HackerRank# Hexagonal Grid
原题地址 铺瓷砖的变种,做法也是类似 假设地板长下面这样,灰色的是无法填充的空洞,初始时可以把N块之外的地板填充成灰色的,便于边界处理 假设现在从后向前已经处理完了一部分,绿色的砖块代表已经遍历过了, ...
- LA 并查集路径压缩
题目大意:有n个节点,初始时每个节点的父亲节点都不存在.有两种操作 I u v:把点节点u的父亲节点设为v,距离为|u-v|除以1000的余数.输入保证执行指令前u没有父亲节点. E u:询问u到根节 ...