Reputation: 463
I am using enzyme
and jest
to test an app that updates the state
and DOM
after a certain time after it's mounted (using setTimout()
). But, how do I make sure that time has passed and perform the check?
I tried using jest.advanceTimersByTime()
but it gave me an warning. Warning: An update to %s inside a test was not wrapped in act(...).
So I did just that but still nothing changes.
const someComponents = () => {
return (<div className="child"></div>);
}
const App = () => {
const [remove, setRemove] = useState(false);
setTimeout(() => setRemove(true), 5000);
return (
<div className="App">
{ remove &&
<someComponent/>
}
</div>
);
};
and as for my test:
import React from 'react';
import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils';
import { mount } from 'enzyme';
describe('test', () => {
it('should remove child at end of timer', () => {
const wrapper = mount(<App />);
expect(wrapper.find('.child')).toHaveLength(1);
act(() => {
jest.advanceTimersByTime(1000000);
wrapper.update(); //Do I need this?
console.log(wrapper.debug())// Still shows child in DOM
expect(wrapper.find('.child')).toHaveLength(0);
});
};
}
After 5s someComponent
should have unmounted but its still there when I try to test it. Also what is the correct way to access remove
and setRemove
with enzyme? There is not a lot of info on using enzyme
with react hooks
.
Upvotes: 1
Views: 3147
Reputation: 19762
There appears to be a problem where current testing libraries aren't handling useEffect
properly (at the moment), so the test will always fail. You can find the currently tracked issue here.
On a side note, your hook example needs to utilize useEffect
in order to work.
import React, { useState, useEffect } from "react";
const Example = () => <div className="child">child</div>;
const App = () => {
const [showChild, setShowChild] = useState(true);
useEffect(() => {
const timer = setTimeout(() => setShowChild(false), 5000);
return () => {
clearTimeout(timer);
};
}, []);
return <div className="App">{showChild && <Example />}</div>;
};
export default App;
Working example:
That said, a workaround is to use classes for the time being.
Here's a local example showcasing the hook issue and a working class example: https://github.com/mattcarlotta/hooks-versus-classes
To install:
git clone [email protected]:mattcarlotta/hooks-versus-classes.git
and enter.cd hooks-versus-classes
.yarn install
or npm install
.yarn test
or npm run test
to run the tests (they'll all pass, but one will throw a console.error
and it's asserted to not function properly).yarn dev
or npm run dev
to run the project.Working class code example:
App.js
import React, { Component } from 'react';
const Example = () => <div className="child">child</div>;
class App extends Component {
state = { showChild: true };
componentDidMount = () => this.setTimer(); // setup timeout on mount
componentWillUnmount = () => this.clearTimer(); // remove timeout in case of component unmount
clearTimer = () => clearTimeout(this.timeout); // clear timeout
timer = () => this.setState({ showChild: false }, () => this.clearTimer()); // update state and clear timeout
setTimer = () => (this.timeout = setTimeout(this.timer, 5000)); // setup a 5s timeout
render = () => <div>{this.state.showChild && <Example />}</div>;
}
export default App;
App.test.js
import App from './App';
describe('App', () => {
it('initially renders a child component', () => {
expect(wrapper.find('.child')).toHaveLength(1);
});
it('removes child component after a 5 second timeout', () => {
jest.advanceTimersByTime(5000);
expect(wrapper.find('.child')).toHaveLength(0);
});
});
Upvotes: 1