enzyme icon indicating copy to clipboard operation
enzyme copied to clipboard

How can i test a dynamically/lazy loaded component on jest

Open sahithikol opened this issue 5 years ago • 24 comments

How can i test a dyanmically/lazy loaded component on jest I have a test component which loads as

import React, { lazy, Suspense } from "react";

const Component2 = lazy(() => import("./Component2"));
class Component1 extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isClicked: false
    };
  }
  click () {
    this.setState({ isClicked: true });
  };
  render() {
    return (
      <div>
        <div>component1 </div>
        <button onClick={this.click.bind(this)}>get component2</button>
        {this.state.isClicked && (
          <Suspense fallback={<div>loading...</div>}>
            <Component2 />
          </Suspense>
        )}
      </div>
    );
  }
}

export default Component1;

i tried to write tests for this as follows but could not get a success

import React from 'react';
import Enzyme, { shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Component1 from '../src/Component1.js';
Enzyme.configure({ adapter: new Adapter() })
describe('MyComponent', () => {
  it('should render correctly i mode', () => {
    const component = shallow(<Component1 />);
    expect(component).toMatchSnapshot();
  });

  it('should not have component2', () => {
    const component = shallow(<Component1 />);
    expect(component.find('Component2').exists()).toEqual(true);
  })

  it('should contain button getComponent2 and should have component2', ()=> {
    const component = shallow(<Component1 />);
    expect(component.instance().state.isClicked).toEqual(false);
    expect(component.find('button').exists());
    const button = component.find('button');
    button.simulate("click");
    expect(component.instance().state.isClicked).toEqual(true);
    expect(component.find('Component2').exists()).toEqual(true);
    expect(component.find('.component-2').exists()).toEqual(true);
  });  

});

sahithikol avatar Aug 01 '19 23:08 sahithikol

In general, I'd avoid using simulate.

In your code above, I'm not really clear what's going on - you're not actually using lazy or Suspense.

ljharb avatar Aug 03 '19 02:08 ljharb

sorry @ljharb i have updated the code added the code snippets where i use react lazy and suspense

sahithikol avatar Aug 03 '19 02:08 sahithikol

I'm also stuck on a similar issue. Can someone please help?

shridharkalagi avatar Aug 06 '19 07:08 shridharkalagi

@ljharb Can you please help?

shridharkalagi avatar Aug 08 '19 05:08 shridharkalagi

@ljharb i am stuck on this , can you please suggest

sahithikol avatar Aug 09 '19 00:08 sahithikol

Any luck on this @sahithikol ?

shridharkalagi avatar Aug 18 '19 11:08 shridharkalagi

no i could not make any progress on this @shridharkalagi any luck for you @ljharb please help

sahithikol avatar Sep 10 '19 20:09 sahithikol

@sahithikol Can you try again without using simulate, and using a wrapper.update() after invoking your onClick prop?

ljharb avatar Sep 11 '19 04:09 ljharb

@ljharb i have tried to use to call component.find('button').props().onClick() instead of simulate , can you please help how can i test this

sahithikol avatar Sep 17 '19 22:09 sahithikol

@ljharb i tried testing it this way

import React from 'react';
import Enzyme, { mount, shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import Component1 from '../src/Component1.js';
Enzyme.configure({ adapter: new Adapter() })
describe('MyComponent', () => {
  it('should render correctly i mode', () => {
    const component = shallow(<Component1 />);
    expect(component).toMatchSnapshot();
  });

  it('should contain button getComponent2 and should have component2', ()=> {
    const component = shallow(<Component1 />);
    expect(component.instance().state.isClicked).toEqual(false);
    expect(component.find('button').exists());
    component.find('button').prop('onClick')();
    component.update();
    expect(component.instance().state.isClicked).toEqual(true);

    expect(component.find('Component2').exists()).toEqual(true);
    expect(component.find('.component-2').exists()).toEqual(true);
  });

});

but it fails and the error message that i see when i try to debug is ReactDOMServer does not yet support Suspense @shridharkalagi any luck

sahithikol avatar Sep 19 '19 23:09 sahithikol

In general, I'd avoid using simulate.

@ljharb Why?

orpheus avatar Jan 21 '20 23:01 orpheus

@orpheus it does not actually simulate anything - it's a terrible API name that enzyme inherited from facebook's shallow renderer, before we realized how bad it is. All it does is invoke a prop (with an implicit transformation from the event name to the prop name), and sometimes provides a fake event object. It's much more explicit to directly invoke the prop you need.

ljharb avatar Jan 21 '20 23:01 ljharb

@sahithikol you can't shallow-render nor server-render a component that uses Suspense; instead, that component should avoid using Suspense unless it's already been mounted in a DOM.

ljharb avatar Jan 21 '20 23:01 ljharb

@ljharb what should be the solution for this? I see many issues about simular issues like: #2254 and #2196

johan-smits avatar Jan 28 '20 12:01 johan-smits

I believe there is no solution until React provides one, short of avoiding use of Suspense in a server-rendered component.

ljharb avatar Jan 28 '20 20:01 ljharb

@ljharb thanks for the feedback.

FYI @ramsy-leftclick

johan-smits avatar Jan 31 '20 12:01 johan-smits

Thanks for all the answers I found out that it works well with the react-testing-library though

sahithikolichala avatar Feb 04 '20 23:02 sahithikolichala

That's because r-t-l uses a full react DOM render; mount should work just fine as well, but you need shallow to be able to unit test and cover server rendering.

ljharb avatar Feb 04 '20 23:02 ljharb

Hi @sahithikol , I had a similar issue and it's solved with the help of this package synchronous promise

Just add this snippet to your test file:

import { SynchronousPromise } from 'synchronous-promise';

let __awaiter: Function;

beforeEach(() => {​​​​​
    __awaiter = SynchronousPromise.installGlobally(__awaiter);
}​​​​​);

afterEach(() => {​​​​​
    SynchronousPromise.uninstallGlobally();
}​​​​​);

AbdelrhmanMagdy avatar Jan 14 '21 14:01 AbdelrhmanMagdy

FYI the workaround that we used at our project.

  1. Extract Lazy and Suspense to a Loadable component. E.g.,
// Loadable.js
import React, { Suspense } from 'react';
import LazyLoadSpinner from '../LazyLoadSpinner';

const Loadable = (config = {}) => {
  const LazyLoadedComponent = React.lazy(config.loader);
  return props => (
    <Suspense fallback={<LazyLoadSpinner />}>
      <LazyLoadedComponent {...props} />
    </Suspense>
  );
};

export default Loadable;
  1. Use the Loadable component to lazy load your component. E.g., For the case in this issue
import React, { lazy, Suspense } from "react";
import Loadable from 'path-to-loadable';

const Component2 = Loadable({
  loader: () => import("./Component2"),
});
class Component1 extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isClicked: false
    };
  }
  click () {
    this.setState({ isClicked: true });
  };
  render() {
    return (
      <div>
        <div>component1 </div>
        <button onClick={this.click.bind(this)}>get component2</button>
        {this.state.isClicked && (
          <Suspense fallback={<div>loading...</div>}>
            <Component2 />
          </Suspense>
        )}
      </div>
    );
  }
}

export default Component1;
  1. Mock Loadable at your test case, i.e.,
jest.mock('../Loadable', () => {
  return function({ loader }) {
    loader();
    return function LoadableComponent() {
      return null;
    };
  };
});
  1. Now you can use 'LoadableComponent' to find the lazy-loaded component. i.e.,
expect(component.find('LoadableComponent').exists()).toEqual(true);

This is not a complete solution for Suspense&Lazy, but it should be sufficient for unit testing.

shinxi avatar Dec 24 '21 09:12 shinxi

what is component in the above line "component.find...."?

Anil-Bhimwal1 avatar Jul 07 '22 10:07 Anil-Bhimwal1

@Anil-Bhimwal1 it's an enzyme wrapper.

ljharb avatar Jul 07 '22 18:07 ljharb

@Anil-Bhimwal1 it's an enzyme wrapper.

can anyone help with jest, react-testing-library

Anil-Bhimwal1 avatar Jul 08 '22 06:07 Anil-Bhimwal1

@Anil-Bhimwal1 not on the repo for enzyme, which is neither of those two things.

ljharb avatar Jul 08 '22 06:07 ljharb