enzymejs / enzyme

JavaScript Testing utilities for React
https://enzymejs.github.io/enzyme/
MIT License
19.95k stars 2.01k forks source link

Cannot use mount in tests in Storybook without setTimeout #2350

Open najeebarshad opened 4 years ago

najeebarshad commented 4 years ago

I have had a look at multiple similar issues but unfortunately haven't able to find anything that solves my problem.

Current behavior

I have unit tests that use Enzyme's mount and shallow. I am importing these tests inside my storybook files, and running them. However I get the error Cannot read property '_reactInternalFiber' of null in any of my unit tests using mount. Shallow is fine. The weird thing is, if I wrap the unit test calls inside a setTimeout, it works perfectly. Please see some code snippets below to demonstrate what I mean.

Please note the code below is very stripped down, I actually have more tests and more parts in my storybook files. But I actually stripped my code down to this level of simplicity and the issue still exists.

component.test.js

import React from "react";
import expect from "expect";
import { describe, it } from "storybook-addon-specifications";
import { shallow, mount } from "enzyme";
import Component from "./component.tsx";

const test = () => 
  describe('Component Test', () => {
    it("should render the component", () => {
      const wrapper = mount(<Component />);
      expect(wrapper.find(".classname")).toHaveLength(1);
    });
  });

export default test;

component.stories.js

import React from "react";
import { storiesOf } from "@storybook/react";
import { specs } from "storybook-addon-specifications";
import test from "./component.test";

storiesOf("Component", module)
  .add("Default", () => {

    // specs(test); // reactInternalFiber error happens in this situation

    // The tests run fine like this:
    setTimeout(() => {
      specs(test);
    }, 0);

    return (
      <Component />
    );
  });

export default {
  title: "Component",
  component: Component,
};

Expected behavior

To not need setTimeout to be able to run unit tests inside storybook.

Your environment

Package.json

{
...
  "devDependencies": {
    "@babel/core": "^7.7.7",
    "@babel/preset-env": "^7.7.7",
    "@babel/preset-react": "^7.7.4",
    "@storybook/addon-a11y": "^5.2.8",
    "@storybook/addon-actions": "^5.2.8",
    "@storybook/addon-knobs": "^5.2.8",
    "@storybook/addon-links": "^5.2.8",
    "@storybook/addon-storysource": "^5.2.8",
    "@storybook/addons": "^5.2.8",
    "@storybook/react": "^5.2.8",
    "@storybook/theming": "^5.2.8",
    "@types/lodash": "^4.14.149",
    "@types/react": "^16.9.19",
    "@types/react-custom-scrollbars": "^4.0.6",
    "@types/react-day-picker": "^5.3.0",
    "@types/react-dom": "^16.9.5",
    "@types/react-transition-group": "^4.2.3",
    "@typescript-eslint/eslint-plugin": "^2.16.0",
    "@typescript-eslint/parser": "^2.16.0",
    "babel-loader": "^8.0.6",
    "css-loader": "^3.4.1",
    "enzyme": "^3.11.0",
    "enzyme-adapter-react-16": "^1.15.2",
    "eslint": "^6.8.0",
    "eslint-config-prettier": "^6.10.0",
    "eslint-plugin-import": "^2.20.0",
    "eslint-plugin-node": "^11.0.0",
    "eslint-plugin-prettier": "^3.1.2",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-react": "^7.17.0",
    "eslint-plugin-react-hooks": "^2.3.0",
    "expect": "^24.9.0",
    "husky": "^4.2.1",
    "lint-staged": "^10.0.7",
    "ncp": "^2.0.0",
    "node-sass": "^4.13.0",
    "npm-run-all": "^4.1.5",
    "plop": "^2.5.3",
    "prettier": "1.19.1",
    "react-test-renderer": "^16.12.0",
    "resolve-url-loader": "^3.1.1",
    "sass-loader": "^8.0.0",
    "sinon": "^8.0.4",
    "source-map-loader": "^0.2.4",
    "storybook-addon-specifications": "^2.1.5",
    "style-loader": "^1.1.2",
    "ts-loader": "^6.2.1",
    "typescript": "^3.7.4",
    "url-loader": "^3.0.0",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10"
  },
  "dependencies": {
    "ag-grid-community": "^22.1.1",
    "ag-grid-react": "^22.1.1",
    "lodash": "^4.17.15",
    "moment": "^2.24.0",
    "prop-types": "^15.7.2",
    "react-custom-scrollbars": "^4.2.1",
    "react-day-picker": "^7.4.0",
    "react-focus-on": "^3.3.0",
    "react-toastify": "^5.5.0",
    "react-transition-group": "^4.3.0",
    "shortid": "^2.2.15"
  },
  "peerDependencies": {
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "react-router-dom": "^5.1.2"
  }
...
}

API

Version

library version
enzyme 3.11.0
react 16.12.0
react-dom 16.12.0
react-test-renderer 16.12.0

Adapter

ljharb commented 4 years ago

Are you sure there's only one copy of React in your storybook bundle? Storybook tends to include one of its own.

najeebarshad commented 4 years ago

There are two copies of react in my node_modules, the one coming from peerDependecies (node_modules/react) and one inside storybook (node_modules/@storybook/react). How would this impact storybook?

ljharb commented 4 years ago

There can only be one copy of React in the browser at a time, basically. So, you'll need to ensure that your top-level react dep dedupes with everything in your tree.

najeebarshad commented 4 years ago

That makes sense. This concept is new to me however, how should I ensure that only one copy of react is even being loaded?