Describe the bug
Instead of returning an instance of the wrapper, wrapper.instance() is returning null
To Reproduce
Here is my package.json:
{
"name": "Personal Project",
"version": "5.0.18",
"description": "Testing practice",
"scripts": {
"start": "node index.js",
"lint": "eslint 'src/**'",
"test": "jest"
},
"jest": {
"testEnvironment": "jsdom",
"moduleNameMapper": {
"\\.(less)$": "identity-obj-proxy"
},
"setupTestFrameworkScriptFile": "./test/setup.js",
"snapshotSerializers": [
"enzyme-to-json/serializer"
]
},
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.0.0",
"@babel/plugin-proposal-function-bind": "^7.0.0",
"@babel/plugin-proposal-json-strings": "^7.0.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
"@babel/plugin-transform-react-constant-elements": "^7.0.0",
"@babel/plugin-transform-react-inline-elements": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@babel/runtime": "^7.0.0",
"autoprefixer": "^9.1.5",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^9.0.0",
"babel-loader": "^8.0.2",
"babel-plugin-lodash": "^3.3.4",
"cross-env": "^5.2.0",
"css-loader": "^1.0.0",
"enzyme": "^3.5.1",
"enzyme-adapter-react-16": "^1.4.0",
"enzyme-to-json": "^3.3.4",
"eslint": "^5.5.0",
"eslint-config-airbnb": "^17.1.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-loader": "^2.1.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsx-a11y": "^6.1.1",
"eslint-plugin-react": "^7.11.1",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"file-loader": "^2.0.0",
"html-webpack-plugin": "^3.2.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^23.5.0",
"less": "^3.8.1",
"less-loader": "^4.1.0",
"mkdirp": "^0.5.1",
"mobx-formatters": "^1.0.2",
"mobx-react-devtools": "^6.0.3",
"ncp": "^2.0.0",
"postcss-loader": "^3.0.0",
"prop-types": "^15.6.2",
"raw-loader": "^0.5.1",
"serve": "^10.0.0",
"sinon": "^6.2.0",
"source-map-loader": "^0.2.4",
"style-loader": "^0.23.0",
"url-loader": "^1.1.1",
"webpack": "^4.17.2",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.7",
"webpack-manifest-plugin": "^2.0.3",
"why-did-you-update": "^0.1.1"
},
"dependencies": {
"@babel/polyfill": "^7.0.0",
"axios": "^0.18.0",
"classnames": "^2.2.6",
"diff": "^3.5.0",
"draft-js": "^0.10.5",
"immutable": "^3.8.2",
"intersection-observer": "^0.5.0",
"jszip": "^3.1.5",
"lodash": "^4.17.10",
"mobx": "^5.1.0",
"mobx-react": "^5.2.6",
"mobx-utils": "^5.0.2",
"moment": "^2.22.2",
"papaparse": "^4.6.0",
"popper.js": "^1.14.4",
"qs": "^6.5.2",
"react": "^16.4.2",
"react-copy-to-clipboard": "^5.0.1",
"react-dom": "^16.4.2",
"react-dropzone": "^5.0.1",
"react-intersection-observer": "^6.2.2",
"react-resize-detector": "^3.1.1",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"react-scroll-lock-component": "^1.1.2",
"react-string-replace": "^0.4.1",
"react-virtualized": "^9.20.1",
"sanitize.css": "^7.0.3"
}
}
My setup file:
import enzyme from 'enzyme'
import EnzymeAdaptor from 'enzyme-adapter-react-16'
enzyme.configure({ adapter: new EnzymeAdaptor() })
global.sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
The component:
import React, { Component } from 'react'
import { func } from 'prop-types'
import classNames from 'classnames'
import { Text } from '../../shared'
import { TextRefresh } from '../Discovery.less'
class CountDownToRefresh extends Component {
state = { innerWidth: 0 }
componentDidMount() {
this.stateTimer = setInterval(this.addOne, 1000)
}
componentWillUnmount() {
clearInterval(this.stateTimer)
}
addOne = () => {
const { innerWidth } = this.state
if (innerWidth === 100) {
this.handleRefresh()
} else {
this.setState({ innerWidth: innerWidth + 1 })
}
}
handleRefresh = () => {
const { handleRefresh } = this.props
handleRefresh()
this.setState({ innerWidth: 1 })
}
get className() {
return classNames(TextRefresh)
}
render() {
const { innerWidth } = this.state
return (
<div className="Button Refresh" onClick={this.handleRefresh}>
<div style={{ width: `${innerWidth}%` }}>
<Text className={this.className}>Refresh</Text>
</div>
</div>
)
}
}
CountDownToRefresh.propTypes = {
handleRefresh: func.isRequired,
}
export default CountDownToRefresh
And finally the test:
import React from 'react'
import { shallow, mount } from 'enzyme'
import { spy } from 'sinon'
import CountDownToRefresh from '../components/CountDownToRefresh.jsx';
const handleRefresh = jest.fn()
spy(CountDownToRefresh.prototype, 'componentDidMount')
spy(CountDownToRefresh.prototype, 'componentWillUnmount')
const wrapper = shallow(<CountDownToRefresh handleRefresh={handleRefresh} />)
describe('CountDownToRefresh button', () => {
it('renders correctly', () => {
expect(wrapper).toMatchSnapshot()
})
it('calls wrapperDidMount', () => {
expect(CountDownToRefresh.prototype.componentDidMount.callCount).toEqual(1)
})
it('calls wrapperWillUnmount', () => {
expect(CountDownToRefresh.prototype.componentWillUnmount.callCount).toEqual(0)
wrapper.unmount()
expect(CountDownToRefresh.prototype.componentWillUnmount.callCount).toEqual(1)
})
it('method addOne works correctly', () => {
const instance = wrapper.instance()
instance.addOne()
expect(wrapper.state('innerWidth')).toEqual(1)
})
})
I want to test my addOne method, but it is failing because wrapper.instance() is returning null.
Here is my error message:
TypeError: Cannot read property 'addOne' of null
26 | it('method addOne works correctly', () => {
27 | const instance = wrapper.instance()
> 28 | instance.addOne()
| ^
29 | expect(wrapper.state('innerWidth')).toEqual(1)
30 | })
31 |
Expected behavior
I expect wrapper.instance() to return an instance of a stateful component.
Any help will be greatly appreciated.
A shallow wrapper isn't the component, it's what the component renders - so if you wrap CountDownToRefresh, the wrapper you get is around the div it renders - which has no instance.
hmm, although i would expect that to work. Does this work in an earlier version of enzyme 3, like v3.3?
Same issue the wrapper.instance() keeps returning null. Am I using shallow and instance() correctly? Is there another way to test a component's method? This method was one that I found on a blog.
@kmaitski i see the issue. You're creating the wrapper at module level - and one of your tests unmounts it.
All code under test should only ever appear in a beforeEach or and it. That means, you should be copy-pasting and creating the wrapper inside each it.
Wow that was it. Such a dumb mistake. I had thought that the unmount code would be constrained in that test. Thank you so much for your help I really appreciate it.
Most helpful comment
@kmaitski i see the issue. You're creating the wrapper at module level - and one of your tests unmounts it.
All code under test should only ever appear in a
beforeEachor andit. That means, you should be copy-pasting and creating the wrapper inside eachit.