Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to test async componentDidMount() #14687

Closed
stonemonkey opened this issue Jan 24, 2019 · 6 comments
Closed

How to test async componentDidMount() #14687

stonemonkey opened this issue Jan 24, 2019 · 6 comments

Comments

@stonemonkey
Copy link

Do you want to request a feature or report a bug?

Question.

What is the current behavior?

I'm trying to test a statefull component (Applications) that has async componentDidMount();

import React from 'react';
import { create } from 'react-test-renderer';
import Applications from './Applications';

describe("Applications", () => {
    const renderComponent = (props = {}) => {
        const component = create(<Applications {...props} />);
        return component.getInstance();
    } 
      
    it("renders applications", () => {
        const instance = renderComponent();
        // Here i get "Invariant Violation: The `document` global was defined when React was initialized, but is not defined anymore. ..." error
    });
});

I tried to provide a callback (done()) to the test and use setImmediate(() => { done(); }, but I still get the error. If I don't call setImmediate() but use the callback variant of the test, I get "Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL." error.

@hg-pyun
Copy link

hg-pyun commented Jan 24, 2019

I have a question. When do you use async componentDidMount?

@stonemonkey
Copy link
Author

I have a question. When do you use async componentDidMount?

I'm not directly calling componentDidMount() in the test, I expect it to be called by the react test renderer when I call create(...).

The Applications component looks like this:

export default class Applications extends Component {
    constructor(props) {
        super(props);
        this.state = {
            loading: true,
            applications: null,
            failedToFetchApplications: false,
        };
    }
    async componentDidMount() {
        this.logService.debug("Applications->componentDidMount()");
        this.setState({
            loading: true,
        });
        if (this.props.currentUser) {
            await this.loadApplicationsIntoState();
        }
        this.setState({
            loading: false,
        });
    }
    // ...
    // loadApplicationsIntostate() fetches from an API
    // ...

@hg-pyun
Copy link

hg-pyun commented Jan 24, 2019

How about this? you can async test using wrapFunc.

export default class Applications extends Component {
    constructor(props) {
        super(props);
        this.state = {
            loading: true,
            applications: null,
            failedToFetchApplications: false,
        };
    }
    componentDidMount() {
        wrapFunc();
    }
    // wrapping function.
    async wrapFunc() {
        this.logService.debug("Applications->componentDidMount()");
        this.setState({
            loading: true,
        });
        if (this.props.currentUser) {
            await this.loadApplicationsIntoState();
        }
        this.setState({
            loading: false,
        });
    } 
    // ...
    // loadApplicationsIntostate() fetches from an API
    // ...

@stonemonkey
Copy link
Author

stonemonkey commented Jan 24, 2019

Actually I'm not trying to test the code within componentDidMount(), I just can't get over the "Invariant Violation" error.

Basically I need sort of an away to await componentDidMount() in the test. The only way I found yet is:

    const renderComponent = (props = {}) => {
        const component = create(<Applications {...props} />);
        return component.root;
    }    
    const delay = async (miliseconds = 0) => {
        await new Promise(resolve => { setTimeout(resolve, miliseconds); });
    };
    it("renders applications", async () => {
        const instance = renderComponent(); 
        await delay(2000); // this is a workaround for leaving async componentDidMount() finish 
    });

Sorry for the misleading title.

@ignatiusreza
Copy link

ignatiusreza commented Jan 25, 2019

Here's how we handle async componentDidMount in our project..

we have a spec helper called defer which looks like

// in spec/helpers/defer
export default () => {
  const d = {};
  d.promise = new Promise((resolve, _reject) => {
    d.resolve = (result) => {
      setTimeout(resolve, 0);
      return Promise.resolve(result);
    };
    d.reject = (rejection) => {
      setTimeout(resolve, 0);
      return Promise.reject(rejection);
    };
  });
  d.then = d.promise.then.bind(d.promise);
  d.catch = d.promise.catch.bind(d.promise);
  return d;
};

and then let's say that the component to be tested are something like:

class MyComponent extends Component {
  static propTypes = {
    asyncApiCall: PropTypes.func,
  }

  componentDidMount() {
    const { asyncApiCall } = this.props;

    asyncApiCall().then(
      () => /* do something */
    );
  }

  // etc...
}

in test, we'll use the helper function like:

it('test async componentDidMount', (done) => {
  const deferredApi = defer();
  const asyncApiCall = () => deferredApi.resolve();
  const wrapper = mount(<MyComponent asyncApiCall={asyncApiCall} />);

  deferredApiCall.then(() => {
    wrapper.update();

    // run some expect...

    done();
  });
});

@stonemonkey
Copy link
Author

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants