Enzyme: wrapper.instance() returns null for a stateful component

Created on 5 Sep 2018  路  5Comments  路  Source: enzymejs/enzyme

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.

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 beforeEach or and it. That means, you should be copy-pasting and creating the wrapper inside each it.

All 5 comments

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rexonms picture rexonms  路  3Comments

ivanbtrujillo picture ivanbtrujillo  路  3Comments

abe903 picture abe903  路  3Comments

ahuth picture ahuth  路  3Comments

blainekasten picture blainekasten  路  3Comments