Reputation: 1749
I have a react component that renders conditionally (renders if data is fetched otherwise returns null) and I want to test this with jest & enzyme
. The problem that I'm having is I want to test one of the methods in the class but .instance()
keeps returning null so it doesn't allow me to test the instance.
my code looks something like this
export default class MyComponent extends React.Component<Props, State> {
componentDidMount() {
this.props.fetchData.then(() =>
this.setState({ loaded: true });
);
}
methodThatIWantToTest() {
//do some stuff here
}
render() {
if (this.loaded) {
// render stuff here
} else {
return null;
}
}
}
and in the test I want to test
describe('myComponent', () => {
it('should do some stuff', () => {
const shallowWrapper = shallow(<MyComponent {...props}/>);
const method = shallowWrapper.instance().methodThatIWantToTest();
....such and such
});
});
but it looks like MyComponent
only returns null so shallowWrapper.instance()
returns null as well. I tried shallowWrapper.update()
and many other things but it seems it doesn't want to render at all.. How do I wait for my component to be updated and then starts expect
statement?
has anyone had a similar issue as mine and know how to work around this?
Upvotes: 4
Views: 2645
Reputation: 222493
It is render
result and not an instance that is null
. shallowWrapper.instance()
is an instance of component class, it cannot be null
for stateful component. As the reference states:
Returns (React 16.x)
ReactComponent: The stateful React component instance.
null: If stateless React component was wrapped.
While shallowWrapper.html()
will be initially null
indeed.
There is a mistake in original code, it should be this.state.loaded
and not this.loaded
:
MyComponent extends React.Component {
state = { loaded: false };
componentDidMount() {
this.props.fetchData.then(() => {
this.setState({ loaded: true });
});
}
methodThatIWantToTest() {
//do some stuff here
}
render() {
if (this.state.loaded) {
return <p>hi</p>;
} else {
return null;
}
}
}
componentDidMount
and methodThatIWantToTest
should be preferably considered different units. They belong to different tests. In case methodThatIWantToTest
is called in lifecycle hooks, it may be stubbed in componentDidMount
test:
it('should fetch data', async () => {
const props = { fetchData: Promise.resolve('data') };
const shallowWrapper = shallow(<MyComponent {...props}/>);
expect(shallowWrapper.html()).toBe(null);
await props.fetchData;
expect(shallowWrapper.html()).toBe('<p>hi</p>');
});
Then the method can be tested separately. Lifecycle hooks can be disabled to reduce number of moving parts:
it('should do some stuff', () => {
const shallowWrapper = shallow(<MyComponent {...props}/>, {disableLifecycleMethods: true});
const result = shallowWrapper.instance().methodThatIWantToTest();
expect(result).toBe(...);
});
Upvotes: 1
Reputation: 45810
Here is a working example:
myComponent.js
import * as React from 'react';
export default class MyComponent extends React.Component {
constructor(...props) {
super(...props);
this.state = { loaded: false };
}
componentDidMount() {
this.props.fetchData().then(() =>
this.setState({ loaded: true })
);
}
methodThatIWantToTest() {
return 'result';
}
render() {
if (this.state.loaded) {
return <div>loaded</div>;
} else {
return null;
}
}
}
myComponent.test.js
import * as React from 'react';
import { shallow } from 'enzyme';
import MyComponent from './myComponent';
describe('myComponent', () => {
it('should do some stuff', async () => {
const fetchData = jest.fn(() => Promise.resolve());
const props = { fetchData };
const shallowWrapper = shallow(<MyComponent {...props}/>);
expect(shallowWrapper.html()).toBe(null);
expect(shallowWrapper.instance().methodThatIWantToTest()).toBe('result');
// pause the test and let the event loop cycle so the callback
// queued by then() within componentDidMount can run
await Promise.resolve();
expect(shallowWrapper.html()).toBe('<div>loaded</div>');
});
});
Upvotes: 0