Reputation: 176
I used create-react-app to bootstrap this tiny project and then did an eject so I could inspect the package.json. Based on research I installed enzyme and the react-16 adapter. I've included the package.json just in case something is hosed up in there.
I have tried all three examples below and each one does in fact click the button but the expects on the mocks all FAIL. Lost an entire day yesterday trying to figure out why. What am I doing wrong or have configured wrong?
The Button component:
import React from 'react'
class Button extends React.Component {
handleClick = (arg) => {
console.log('handleClick', arg)
}
render() {
return (
<button type="button" onClick={this.handleClick}>
Click Me
</button>
)
}
}
The tests:
import React from 'react'
import Button from '../components/button'
import {shallow, configure} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
test('onClick called', () => {
const wrapper = shallow(<Button />)
wrapper.instance().onClick = jest.fn()
wrapper.update()
wrapper.instance().handleClick('junk')
expect(wrapper.instance().onClick).toBeCalledWith('junk')
})
test('onClick called', () => {
const mockOnClick = jest.fn();
const wrapper = shallow(<Button onClick={mockOnClick} />)
wrapper.find('button').simulate('click', 'junk')
expect(mockOnClick.mock.calls.length).toEqual(1)
})
test('onClick called', () => {
const mockOnClick = jest.fn()
const wrapper = shallow(<Button onClick={mockOnClick}/>)
wrapper.find('button').at(0).simulate('click', 'junk')
expect(mockOnClick).toHaveBeenCalled()
})
test console (first test)
console.log src/components/button.js:6
handleClick junk
● onClick called
expect(jest.fn()).toBeCalledWith(expected)
Expected mock function to have been called with:
["junk"]
But it was not called.
26 | wrapper.update()
27 | wrapper.instance().handleClick('junk')
> 28 | expect(wrapper.instance().onClick).toBeCalledWith('junk')
| ^
29 | })
30 |
31 | // test('onClick called', () => {
at Object.toBeCalledWith (src/__tests__/button-test.js:28:38)
test console (second test)
console.log src/components/button.js:6
handleClick junk
● onClick called
expect(received).toEqual(expected)
Expected value to equal:
1
Received:
0
33 | const wrapper = shallow(<Button onClick={mockOnClick} />)
34 | wrapper.find('button').simulate('click', 'junk')
> 35 | expect(mockOnClick.mock.calls.length).toEqual(1)
| ^
36 | })
37 |
38 | // test('onClick called', () => {
at Object.toEqual (src/__tests__/button-test.js:35:41)
test console (third test)
console.log src/components/button.js:6
handleClick junk
● onClick called
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called, but it was not called.
40 | const wrapper = shallow(<Button onClick={mockOnClick}/>)
41 | wrapper.find('button').at(0).simulate('click', 'junk') > 42 | expect(mockOnClick).toHaveBeenCalled()
| ^
43 | })
44 |
45 | // test('onClick called', () => {
at Object.toHaveBeenCalled (src/__tests__/button-test.js:42:23)
package.json
{
"name": "react-testing-demo",
"version": "0.1.0",
"private": true,
"dependencies": {
"@babel/core": "7.1.0",
"@svgr/webpack": "2.4.1",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "9.0.0",
"babel-jest": "23.6.0",
"babel-loader": "8.0.4",
"babel-plugin-named-asset-import": "^0.2.3",
"babel-preset-react-app": "^6.1.0",
"bfj": "6.1.1",
"case-sensitive-paths-webpack-plugin": "2.1.2",
"chalk": "2.4.1",
"css-loader": "1.0.0",
"dotenv": "6.0.0",
"dotenv-expand": "4.2.0",
"enzyme": "^3.7.0",
"enzyme-adapter-react-16": "^1.7.0",
"eslint": "5.6.0",
"eslint-config-react-app": "^3.0.5",
"eslint-loader": "2.1.1",
"eslint-plugin-flowtype": "2.50.1",
"eslint-plugin-import": "2.14.0",
"eslint-plugin-jsx-a11y": "6.1.2",
"eslint-plugin-react": "7.11.1",
"file-loader": "2.0.0",
"fork-ts-checker-webpack-plugin-alt": "0.4.14",
"fs-extra": "7.0.0",
"html-webpack-plugin": "4.0.0-alpha.2",
"identity-obj-proxy": "3.0.0",
"jest": "23.6.0",
"jest-pnp-resolver": "1.0.1",
"jest-resolve": "23.6.0",
"mini-css-extract-plugin": "0.4.3",
"optimize-css-assets-webpack-plugin": "5.0.1",
"pnp-webpack-plugin": "1.1.0",
"postcss-flexbugs-fixes": "4.1.0",
"postcss-loader": "3.0.0",
"postcss-preset-env": "6.0.6",
"postcss-safe-parser": "4.0.1",
"react": "^16.6.3",
"react-app-polyfill": "^0.1.3",
"react-dev-utils": "^6.1.1",
"react-dom": "^16.6.3",
"react-test-renderer": "^16.6.3",
"resolve": "1.8.1",
"sass-loader": "7.1.0",
"style-loader": "0.23.0",
"terser-webpack-plugin": "1.1.0",
"url-loader": "1.1.1",
"webpack": "4.19.1",
"webpack-dev-server": "3.1.9",
"webpack-manifest-plugin": "2.0.4",
"workbox-webpack-plugin": "3.6.3"
},
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
],
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx,ts,tsx}",
"!src/**/*.d.ts"
],
"resolver": "jest-pnp-resolver",
"setupFiles": [
"react-app-polyfill/jsdom"
],
"testMatch": [
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
"<rootDir>/src/**/?(*.)(spec|test).{js,jsx,ts,tsx}"
],
"testEnvironment": "jsdom",
"testURL": "http://localhost",
"transform": {
"^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest",
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
"^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
},
"transformIgnorePatterns": [
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$",
"^.+\\.module\\.(css|sass|scss)$"
],
"moduleNameMapper": {
"^react-native$": "react-native-web",
"^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy"
},
"moduleFileExtensions": [
"web.js",
"js",
"web.ts",
"ts",
"web.tsx",
"tsx",
"json",
"web.jsx",
"jsx",
"node"
]
},
"babel": {
"presets": [
"react-app"
]
}
}
Upvotes: 2
Views: 10060
Reputation: 38952
The button renders with its onclick handler set to this.handleClick
. Attempts to change this to change this after the fact are futile.
<button type="button" onClick={this.handleClick}>
Click Me
</button>
The first test variant mocks onClick
method of the Button
component; this is not a defined method of the Button
component.
// 1.
test('onClick called', () => {
const wrapper = shallow(<Button />)
wrapper.instance().onClick = jest.fn()
wrapper.update()
wrapper.instance().handleClick('junk')
expect(wrapper.instance().onClick).toBeCalledWith('junk')
})
The second and third variants forwards an onClick
prop to the Button
component; the Button
component doesn't expose an onClick
property.
//2.
test('onClick called', () => {
const mockOnClick = jest.fn();
const wrapper = shallow(<Button onClick={mockOnClick} />)
wrapper.find('button').simulate('click', 'junk')
expect(mockOnClick.mock.calls.length).toEqual(1)
})
//3.
test('onClick called', () => {
const mockOnClick = jest.fn()
const wrapper = shallow(<Button onClick={mockOnClick}/>)
wrapper.find('button').at(0).simulate('click', 'junk')
expect(mockOnClick).toHaveBeenCalled()
})
Since you're looking to test that handleClick
is called when button is clicked, you can have this.handleClick
called in a Closure.
This makes it so that at the time that the onclick handler gets called, it'll call the method in this.handleClick
at that time.
<button type="button" onClick={evt => this.handleClick(evt)}>
Click Me
</button>
The first test variant works with the above change.
The rest test variants will run successfully when onClick
prop in the Button
component is exposed.
class Button extends React.Component {
handleClick = (arg) => {
this.props.onClick(arg);
console.log('handleClick', arg)
}
//...
}
Button.propTypes = {
onClick: PropTypes.func.isRequired
}
Upvotes: 2