Reputation: 797
history.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
In my .js
this is how I am using history.listen
import history from './history';
The following in the constructor:
history.listen(location => {
if (location.pathname === '/second') {
this.setState({
pathStep: 1,
});
} else if (location.pathname === '/') {
this.setState({
pathStep: 0,
});
}
});
Now I am struggling to have a valid test for that:
I tried to do the following:
jest.mock('./history', () => ({
listen: () => () => {
'/second';
},
}));
it('changes activeStep when called', () => {
expect(component.state().pathStep).toBe(1);
});
But even adding a console.log
after history.listen(location => {
I am not reaching my history.listen
. So I am curious what I am doing wrong
I also tried adding spyOn
to history.listen
, but keen to hear what is the best practice for this specific test
Upvotes: 3
Views: 4545
Reputation: 45820
If you mock history.listen
, you can get the callback that your component passes to it.
Then, you can call the callback directly to verify that your component responds correctly.
Here is a complete working example:
history.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
code.js
import * as React from 'react';
import history from './history';
export class SimpleComponent extends React.Component {
constructor(...args) {
super(...args);
this.state = { pathStep: 0 };
}
componentDidMount() {
this.unlisten = history.listen(location => {
if (location.pathname === '/second') {
this.setState({
pathStep: 1,
});
} else if (location.pathname === '/') {
this.setState({
pathStep: 0,
});
}
});
}
componentWillUnmount() {
this.unlisten();
}
render() { return null; }
}
code.test.js
import * as React from 'react';
import history from './history';
import { mount } from 'enzyme';
import { SimpleComponent } from './code';
test('SimpleComponent', () => {
const listenMock = jest.spyOn(history, 'listen');
const unlistenMock = jest.fn();
listenMock.mockReturnValue(unlistenMock);
const component = mount(<SimpleComponent />);
expect(component.state().pathStep).toBe(0); // Success!
const callback = listenMock.mock.calls[0][0]; // <= get the callback passed to history.listen
callback({ pathname: '/second' });
expect(component.state().pathStep).toBe(1); // Success!
callback({ pathname: '/' });
expect(component.state().pathStep).toBe(0); // Success!
component.unmount();
expect(unlistenMock).toHaveBeenCalled(); // Success!
})
Upvotes: 2
Reputation: 797
What I ended up doing was something like that.
On the very first mount it returns /
, on the second mount it returns /second
and from there it is back to the default state of 0(/
)
jest.mock('./history', () => ({
listen: jest
.fn()
.mockImplementationOnce(cb => {
cb({ pathname: '/' });
})
.mockImplementationOnce(cb => {
cb({ pathname: '/second' });
}),
}));
And the test itself (to test both /second
and /
in 1 single test)
it('changes pathStep when called', () => {
expect(component.state().pathStep).toBe(0);
component = mount(<MyComponent />);
expect(component.state().pathStep).toBe(1);
component = mount(<MyComponent />);
expect(component.state().pathStep).toBe(0);
});
But to make the test work that I asked initially something like that will suffice:
jest.mock('./history', () => ({
listen: cb => {
cb({ pathname: '/second' });
},
}));
I just had to pass a callback when mocking, so close to what I had before, but with some pairing managed to get it to work :)
Hope that it makes sense and will help somebody out in the future
Upvotes: 1