I'm using react, jest and enzyme with immutable. I'm trying to mount a component that fetching data from the API, and I'm having a little difficulties.
// FooListContainer.jsx
export default class FooListContainer extends Component {
constructor(props) {
super(props);
this.state = {
foos: List()
}
}
componetWillMount() {
manager.bringFooList()
.then(lst => this.setState({ foos: fromJS(lst) }))
.done();
}
render() {
return <FooList foos={this.state.foos} />
}
}
This is the ui component all it does is receive list and map them
// FooList.jsx
export default class FooList extends Component {
render() {
return (
<div>
{this.props.foos.map(item => <div>{item}</div>)}
</div>
);
}
}
Now I would like to test the data received from the fetch in FooListContainter is passed correctly to FooList.
// FooListContainer.test.jsx
describe('rendering', () => {
it('Should passed the data from the fetch to the FooList', () => {
const response = ['1', '2', '3'];
manager.bringFooList = jest.fn(() => {
return new Promise(resolve => {
return resolve(response);
});
})
const wrapper = mount(<FooListContainer />);
const fooList = wrapper.find(FooList);
expect(fooList.props().foos.size).toBe(3);
});
});
But the test fails because it expects the length to be 3 and it actual length is 0 from some reason.
I think that it has something to do with the fact that the fetch inside the container is async - so the test is not 'waiting' to the response and render is happening before the state change for the first time and the FooList receive an empty list.
I have tried to receive in the 'it' function an async callback as an argument and call it after the mount, like this:
// FooListContainer.test.jsx
describe('rendering', () => {
it('Should passed the data from the fetch to the FooList', (done) => {
const response = ['1', '2', '3'];
manager.bringFooList = jest.fn(() => {
return new Promise(resolve => {
return resolve(response);
});
})
const wrapper = mount(<FooListContainer />);
done();
const fooList = wrapper.find(FooList);
expect(fooList.props().foos.size).toBe(3);
});
});
But the above example did not work.
I would really appreciate every help you could give me.
Related
I need to test the following component that consumes a custom hook of mine.
import { useMyHook } from 'hooks/useMyHook';
const MyComponent = () => {
const myHookObj = useMyHook();
const handler = () => {
myHookObj.myMethod(someValue)
}
return(
<button onClick={handler}>MyButton</button>
);
};
This is my test file:
jest.mock('hooks/useMyHook', () => {
return {
useMyHook: () => {
return {
myMethod: jest.fn(),
};
},
};
});
describe('<MyComponent />', () => {
it('calls the hook method when button is clicked', async () => {
render(<MyComponent {...props} />);
const button = screen.getByText('MyButton');
userEvent.click(button);
// Here I need to check that the `useMyHook.method`
// was called with some `value`
// How can I do this?
});
});
I need to check that the useMyHook.method was called with some value.
I also want to test it from multiple it cases and it might be called with different values on each test.
How can I do this?
This is how I was able to do it:
import { useMyHook } from 'hooks/useMyHook';
// Mock custom hook that it's used by the component
jest.mock('hooks/useMyHook', () => {
return {
useMyHook: jest.fn(),
};
});
// Mock the implementation of the `myMethod` method of the hook
// that is used by the Component
const myMethod = jest.fn();
(useMyHook as ReturnType<typeof jest.fn>).mockImplementation(() => {
return {
myMethod: myMethod,
};
});
// Reset mock state before each test
// Note: is needs to reset the mock call count
beforeEach(() => {
myMethod.mockReset();
});
Then, on the it clauses, I'm able to:
it (`does whatever`, async () => {
expect(myMethod).toHaveBeenCalledTimes(1);
expect(myMethod).toHaveBeenLastCalledWith(someValue);
});
I have some code which works. However for my test I would like to mock the fetch that is done in the component.
The test
I am trying the following:
import ConnectedComponent from './Component';
import { render } from '#testing-library/react';
import user from '../__models__/user'; // arbitrary file for the response
// create a mock response
const mockSuccessResponse = user;
const mockJsonPromise = Promise.resolve(mockSuccessResponse);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
// Trying mock the refetch from http
jest.mock('./http', () => {
return {
refetch: () => ({
settingsFetch: () => mockFetchPromise,
})
}
});
it('renders', async () => {
const { getByText } = render(Component);
const title = await getByText('My title');
expect(title).toBeInTheDocument();
});
Error message
With this I receive the following error:
● Test suite failed to run
TypeError: (0 , _http.refetch)(...) is not a function
The Application code
This code is working fine in my application. To give you an example:
./http.js
import { connect } from 'react-refetch';
export async function fetchWithToken(urlOrRequest, options = {}) {
// some stuff
return response;
}
export const refetch = connect.defaults({
fetch: fetchWithToken,
});
./Component.jsx
import { refetch } from './http';
const Component = ({ settingsFetch }) => <AnotherComponent settingsFetch={settingsFetch} />);
const ConnectedComponent = refetch(
({
match: { params: { someId } },
}) => ({
settingsFetch: {
url: 'http://some-url/api/v1/foo'
}
})
)(Component)
export default ConnectedComponent;
How can I mock this function to return a mocked Promise as the response?
Update: It's getting close by doing the following:
jest.mock('../helpers/http', () => ({
refetch: () => jest.fn(
(ReactComponent) => (ReactComponent),
),
}));
Now the error reads:
Warning: Failed prop type: The prop `settingsFetch` is marked as required in `ConnectedComponent`, but its value is `undefined`.
Which means I will probably have to provide the mocked responses for the fetches in there somewhere.
Jest itself is in charge of the modules. So in the following example you will see that the module coming from '../http' can be mocked.
You can then overwrite the props of that module by first adding the default props, and after that overwrite the ones you need with your own.
jest.mock('../http', () => {
return {
refetch: function(hocConf) {
return function(component) {
component.defaultProps = {
...component.defaultProps,
settingsFetch: {},
// remember to add a Promise instead of an empty object here
};
return component;
};
},
};
});
I have a component:
RandomGif.js
import React, { Component } from "react";
import Gif from "./Gif";
import Loader from "./library/Loader";
import { fetchRandom } from "../resources/api";
class RandomGif extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
state = {
loading: false,
gif: null
};
componentDidMount() {
this.handleClick();
}
async handleClick() {
let gifContent = null;
try {
this.setState({
loading: true
});
const result = await fetchRandom();
if (!!result && result.data) {
gifContent = {
id: result.data.id,
imageUrl: result.data.images.downsized_large.url,
staticImageUrl: result.data.images.downsized_still.url,
title: result.data.title
};
}
} catch (e) {
console.error(e);
} finally {
this.setState({
loading: false,
gif: gifContent
});
}
}
render() {
const { gif, loading } = this.state;
const showResults = gif && !loading;
return (
<div className="random">
{!showResults && <Loader />}
<button className="btn" onClick={this.handleClick}>
RANDOMISE
</button>
{showResults && <Gif data={gif} />}
</div>
);
}
}
export default RandomGif;
If I call methods directly from the instance of this component, I can successfully test that the state is being updated. However, If I simulate a button click, nothing gets updated and the test fails. I've tried setImmediate and setTimeout tricks but those are not working.
So far I've not able to write a test case for:
Simulating button click.
Simulating lifecycle method.
This is what I've come up with so far.
RandomGif.spec.js
import React from "react";
import { shallow, mount } from "enzyme";
import RandomGif from "./RandomGif";
describe("Generate Random Gif", () => {
it("should render correctly.", () => {
const wrapper = shallow(<RandomGif />);
expect(wrapper).toMatchSnapshot();
});
it("should load a random GIF on calling handleSearch fn.", async () => {
const wrapper = mount(<RandomGif />);
const instance = wrapper.instance();
expect(wrapper.state("gif")).toBe(null);
await instance.handleClick();
expect(wrapper.state("gif")).not.toBe(null);
});
it("THIS TEST FAILS!!!", () => {
const wrapper = mount(<RandomGif />);
expect(wrapper.state("gif")).toBe(null);
wrapper.find('button').simulate('click');
wrapper.update()
expect(wrapper.state("gif")).not.toBe(null);
});
});
api.py
export const fetchRandom = async () => {
const url = `some_url`;
try {
const response = await fetch(url);
return await response.json();
} catch (e) {
console.error(e);
}
return null;
};
Please help me figure out the missing pieces of a puzzle called 'frontend testing'.
We need to mock fetchRandom so no real request will be sent during testing.
import { fetchRandom } from "../resources/api";
jest.mock("../resources/api"); // due to automocking fetchRandom is jest.fn()
// somewhere in the it()
fetchRandom.mockReturnValue(Promise.resolve({ data: { images: ..., title: ..., id: ...} }))
Since mocking is a Promise(resolved - but still promise) we need either setTimeout or await <anything> to make component's code realized this Promise has been resolved. It's all about microtasks/macrotasks queue.
wrapper.find('button').simulate('click');
await Promise.resolve();
// component has already been updated here
or
it("test something" , (done) => {
wrapper.find('button').simulate('click');
setTimeout(() => {
// do our checks on updated component
done();
}); // 0 by default, but it still works
})
Btw you've already did that with
await instance.handleClick();
but to me it looks the same magic as say
await 42;
And besides it works(look into link on microtasks/macrotasks) I believe that would make tests worse readable("what does handleClick return that we need to await on it?"). So I suggest use cumbersome but less confusing await Promise.resolve(); or even await undefined;
Referring to state and calling instance methods directly are both anti-patterns. Just a quote(by Kent C. Dodds I completely agree with):
In summary, if your test uses instance() or state(), know that you're testing things that the user couldn't possibly know about or even care about, which will take your tests further from giving you confidence that things will work when your user uses them.
Let's check rendering result instead:
import Loader from "./library/Loader";
...
wrapper.find('button').simulate('click');
expect(wrapper.find(Loader)).toHaveLength(1);
await Promise.resolve();
expect(wrapper.find(Loader)).toHaveLength(1);
expect(wrapper.find(Gif).prop("data")).toEqual(data_we_mocked_in_mock)
Let's get that altogether:
import {shallow} from "enzyme";
import Gif from "./Gif";
import Loader from "./library/Loader";
import { fetchRandom } from "../resources/api";
jest.mock( "../resources/api");
const someMockForFetchRandom = { data: { id: ..., images: ..., title: ... }};
it("shows loader while loading", async () => {
fetchRandom.mockReturnValue(Promise.resolve(someMockForFetchRandom));
const wrapper = shallow(<RandomGif />);
expect(wrapper.find(Loader)).toHaveLength(0);
wrapper.find('button').simulate('click');
expect(wrapper.find(Loader)).toHaveLength(1);
await Promise.resolve();
expect(wrapper.find(Loader)).toHaveLength(0);
});
it("renders images up to response", async () => {
fetchRandom.mockReturnValue(Promise.resolve(someMockForFetchRandom));
const wrapper = shallow(<RandomGif />);
wrapper.find('button').simulate('click');
expect(wrapper.find(Gif)).toHaveLength(0);
await Promise.resolve();
expect(wrapper.find(Gif).props()).toEqual( {
id: someMockForFetchRandom.data.id,
imageUrl: someMockForFetchRandom.data.images.downsized_large.url,
staticImageUrl: someMockForFetchRandom.data.images.downsized_still.url,
title: someMockForFetchRandom.data.title
});
});
I have component that load image and send image data up to parent component via props.handle function
How I can mock or call props.handle inside fileReader.onload
Maybe need use async, but I don't know how.
Was try repeat code from this question How do I test `image.onload` using jest in the context of redux actions (or other callbacks assigned in the action)
But it didn't help
ChildComponent.js:
class ChildComponent extends React.PureComponent {
constructor(props) {
super(props);
}
handleFileLoad = event => {
event.preventDefault();
const reader = new FileReader();
reader.onload = () => {
const data = reader.result;
this.props.parentHandleFunction(data)
}
reader.readAsDataURL(event.target.files[0]);
}
}
ChildComponent.propTypes = {
parentHandleFunction: PropTypes.func.isRequired,
};
ChildComponent.test.js:
describe('<ChildComponent />', () => {
let renderComponent;
let changeSpy;
beforeEach(() => {
changeSpy = jest.fn(value => value);
renderComponent = shallow(
<ChildComponent parentHandleFunction={changeSpy}/>,
);
});
it('should call handle file change', () => {
const childComponent = shallow(
renderComponent.find(ChildComponent).getElement(),
);
const file = new Blob(['image'], { type: 'image/jpeg' });
loadButton.find('input').simulate('change', {
preventDefault: () => {},
target: {
files: [file],
},
});
expect(changeSpy).toHaveBeenCalled();
});
})
Tests show errror:
'Expected mock function to have been called, but it was not called.'
update
I solve my problem by cuting onload logic in seprate function
## ChildComponent.js ##
class ChildComponent extends React.PureComponent {
constructor(props) {
super(props);
}
loadImage = data => {
const imageObject = {
url: data,
};
this.props.parentHandleFunction(
imageObject,
);
}
handleFileLoad = event => {
event.preventDefault();
const reader = new FileReader();
reader.onload = async () => this.loadImage(reader.result);
reader.readAsDataURL(event.target.files[0]);
}
}
ChildComponent.propTypes = {
parentHandleFunction: PropTypes.func.isRequired,
};
ChildComponent.test.js:
it('should call change spy function', () => {
renderComponent.instance().loadImage('mockImage');
renderComponent.update();
renderComponent.instance().forceUpdate();
expect(changeSpy).toHaveBeenCalled();
});
can anyone tell me how to wait in jest for a mocked promise to resolve when mounting a component that calls componendDidMount()?
class Something extends React.Component {
state = {
res: null,
};
componentDidMount() {
API.get().then(res => this.setState({ res }));
}
render() {
if (!!this.state.res) return
return <span>user: ${this.state.res.user}</span>;
}
}
the API.get() is mocked in my jest test
data = [
'user': 1,
'name': 'bob'
];
function mockPromiseResolution(response) {
return new Promise((resolve, reject) => {
process.nextTick(
resolve(response)
);
});
}
const API = {
get: () => mockPromiseResolution(data),
};
Then my testing file:
import { API } from 'api';
import { API as mockAPI } from '__mocks/api';
API.get = jest.fn().mockImplementation(mockAPI.get);
describe('Something Component', () => {
it('renders after data loads', () => {
const wrapper = mount(<Something />);
expect(mountToJson(wrapper)).toMatchSnapshot();
// here is where I dont know how to wait to do the expect until the mock promise does its nextTick and resolves
});
});
The issue is that I the expect(mountToJson(wrapper)) is returning null because the mocked api call and lifecycle methods of <Something /> haven't gone through yet.
Jest has mocks to fake time travelling, to use it in your case, I guess you can change your code in the following style:
import { API } from 'api';
import { API as mockAPI } from '__mocks/api';
API.get = jest.fn().mockImplementation(mockAPI.get);
jest.useFakeTimers(); // this statement makes sure you use fake timers
describe('Something Component', () => {
it('renders after data loads', () => {
const wrapper = mount(<Something />);
// skip forward to a certain time
jest.runTimersToTime(1);
expect(mountToJson(wrapper)).toMatchSnapshot();
});
});
Alternatively to jest.runTimersToTime() you could also use jest.runAllTimers()
As a workaround convert it from async to sync
jest.spyOn(Api, 'get').mockReturnValue({
then: fn => fn('hello');
});