ReactJs - ReferenceError: fetch is not defined - reactjs

After some changes on an existing component, I am having trouble in the jest tests.
Basically, I added a call on a componentDidMount function to a function that does a "fetch" internally and now I am getting an error when running jest tests
fetch is being called in the utils/index.ts and this one is being called from the MyComponent.tsx
componentDidMount() {
this.props.actions.requestLoadA();
this.props.actions.requestLoadB();
// Problematic call HERE
this.getXPTOStatuses("something");
}
getXPTOStatuses = (something: string) => {
HttpUtility.get(`/api/getXPTOStatuses?param=${something}`)
.then(response => handleFetchErrors(response))
.then(data => {
// ....
})
.catch(error => {
// show the error message to the user -> `Could not load participant statuses. Error: '${error.message}'`
});
}
and the get(...)
public static get = (url: string) => fetch(url, { method: "GET", headers: { Accept: "application/json" }, credentials: "same-origin" });
and the jest test in the cause:
MyContainer.test.tsx
describe("Risk Import Container Tests", () => {
let props: MyContainerProps;
beforeEach(() => {
props = initialProps;
});
it("Matches the snapshot", () => {
const props = initialProps;
const tree = shallow(<MyContainer {...props} />);
expect(tree).toMatchSnapshot();
});
});

By default, Enzymes's shallow function calls componentDidMount. So, this would naturally call through to where fetch is used.
You have 2 options, mock fetch somehow.
Or use the shallow option disableLifecycleMethods to disable the call to componentDidMount.

Related

React class component issue in order of execution

I have the following code in my React class component.
For some reason, I am observing that, inside componentDidMount, despite having the keyword await before the call to this.getKeyForNextRequest(), the execution is jumping to the next call, this.loadGrids().
Am I doing something wrong here?
async componentDidMount() {
await this.getKeyForNextRequest();
await this.loadGrids();
}
getKeyForNextRequest = async () => {
const dataRequester = new DataRequester({
dataSource: `${URL}`,
requestType: "POST",
params: {
},
successCallback: response => {
console.log(response);
}
});
dataRequester.requestData();
}
loadGrids = async () => {
await this.loadGrid1ColumnDefs();
this.loadGrid1Data();
await this.loadGrid2ColumnDefs();
this.loadGrid2Data();
}
You can try using the Promise constructor:
getKeyForNextRequest = () => {
return new Promise((resolve, reject) => {
const dataRequester = new DataRequester({
dataSource: `${URL}`,
requestType: "POST",
params: {},
successCallback: response => {
console.log(response);
resolve(response);
}
});
});
}
This ensures you're waiting for a relevant promise, one that resolves only upon successCallback completing, rather than one that resolves instantly to undefined as you have it currently.
This is called "promisifying" the callback.
If DataRequester offers a promise-based mode, use that instead of promisifying the callback.

How to mock Axios service wrapper using JEST

I'm using JEST testing framework to write test cases for my React JS application. I'm using our internal axios wrapper to make service call. I would like to mock that wrapper service using JEST. Can someone help me on this ?
import Client from 'service-library/dist/client';
import urls from './urls';
import { NODE_ENV, API_VERSION } from '../screens/constants';
const versionHeader = 'X-API-VERSION';
class ViewServiceClass extends Client {
getFiltersList(params) {
const config = {
method: urls.View.getFilters.requestType,
url: urls.View.getFilters.path(),
params,
headers: { [versionHeader]: API_VERSION },
};
return this.client.request(config);
}
const options = { environment: NODE_ENV };
const ViewService = new ViewServiceClass(options);
export default ViewService;
Above is the Service Implementation to make API call. Which I'm leveraging that axios implementation from our internal library.
getFiltersData = () => {
const params = {
filters: 'x,y,z',
};
let {
abc,
def,
ghi
} = this.state;
trackPromise(
ViewService.getFiltersList(params)
.then((result) => {
if (result.status === 200 && result.data) {
const filtersJson = result.data;
.catch(() =>
this.setState({
alertMessage: 'No Filters Data Found. Please try after some time',
severity: 'error',
showAlert: true,
})
)
);
};
I'm using the ViewService to get the response, and I would like to mock this service. Can someone help me on this ?
You would need to spy your getFiltersList method from ViewServiceClass class.
Then mocking some response data (a Promise), something like:
import ViewService from '..';
const mockedData = {
status: 'ok',
data: ['some-data']
};
const mockedFn = jest.fn(() => Promise.resolve(mockedData));
let getFiltersListSpy;
// spy the method and set the mocked data before all tests execution
beforeAll(() => {
getFiltersListSpy = jest.spyOn(ViewService, 'getFiltersList');
getFiltersListSpy.mockReturnValue(mockedFn);
});
// clear the mock the method after all tests execution
afterAll(() => {
getFiltersListSpy.mockClear();
});
// call your method, should be returning same content as `mockedData` const
test('init', async () => {
const response = await ViewService.getFiltersList();
expect(response).toEqual(mockedData);
});
P.D: You can pass params to the method, but you will need to configure as well the mockedData as you wish.

How to test nested promises setState inside a fetch call in componentDidMount

I've seen similar solutions on stack overflow but they usually involved moving the fetch call into it's own function and calling it inside componentDidMount. But how would I go about testing a fetch call inside componentDidMount that sets the state using jest/enzyme? The data retrieved from the api call can be any arbitrary value..
Here is my code for componentDidMount
async componentDidMount() {
await fetch(
"api call",
{
method: "GET",
}
)
.then((res) => res.json())
.then(
(result) => {
const jsonData = JSON.parse(result);
this.setState({
modalOpen: "",
eventNameState: jsonData.eventNameState,
event: jsonData.event,
name: jsonData.eventNameState,
});
},
(error) => {
this.setState({ error });
}
);
}
I've tried using this code to test it but always get a _nock.default is not a function error
beforeAll(() => {
nock(
"api call"
).reply(200, {
eventNameState: "TEST",
});
});
it("Component fetches data from API", async (done) => {
const root = shallow(<EventState />);
let componentState = {};
await waitUntil(() => root.state("eventNameState") !== null);
expect(componentState).toEqual("TEST");
});
});

When I run tests on this componentDidMount in react, apparently several lines are not covered?

I'm trying to use jest to test my componentDidMount method:
componentDidMount() {
agent.Gatherings.getAll().then((result) => {
this.setState({ gatherings: result }) //no code coverage
}).catch((err) => {
this.setState({ gatherings: [] }) //no code coverage
})
}
yet one of my other tests works fine:
it('test gathering List is rendered', () => {
wrapper.setState({ gatherings: [TestGathering] })
expect(wrapper.find('MyList').length).toEqual(1);
});
I want to have every line covered in my testing. How can I get the lines in my componentDidMount() to all be tested in jest?
UPDATE, I'm importing a file directly into the test file. The file I'm importing is called agent.js. The code that gets called in the function whose lines are missed are:
agent.js
export const requests = {
get: url => fetch(url).then(res => res.json()),
post: (url, body) =>
fetch(url, {
method: 'POST',
body: body,
headers: {
'Content-Type': 'application/json'
}
}).then(res => res.json()) //also this line lacks coverage
}
export const Gatherings = {
getAll: () =>
requests.get(API_ROOT + '/gatherings')
}
export default {
Gatherings
}
Issue
A line of code has to run while a test is running to be included in the Jest code coverage.
Details
The two lines without coverage are the callbacks for the Promise returned by agent.Gatherings.getAll.
Promise callbacks get added to the PromiseJobs queue and run after the current message completes and before the next message runs.
This is why those lines are not currently included in the code coverage...right now they don't run until after your synchronous test completes.
Solution
You just need to make sure those two lines run while a test is running.
Details
The ideal approach is to await the Promise directly in your test.
In this case the Promise is not easily accessible from within the test so a different approach is needed.
Workaround
If agent.Gatherings.getAll is mocked to resolve or reject immediately then the Promise callback will be queued in PromiseJobs by the time the component finishes rendering.
To let the Promise callback run use an async test function and call await Promise.resolve(); which essentially queues the rest of the test at the end of PromiseJobs and lets any pending jobs run first:
import * as React from 'react';
import { shallow } from 'enzyme';
import { Comp } from './code'; // <= import your component here
import * as agent from './agent';
describe('Component', () => {
let spy;
beforeEach(() => {
spy = jest.spyOn(agent.Gatherings, 'getAll');
})
afterEach(() => {
spy.mockRestore();
})
it('updates when agent.Gatherings.getAll() resolves', async () => { // use an async test function
const response = [ 'gathering 1', 'gathering 2', 'gathering 3' ];
spy.mockResolvedValue(response);
const wrapper = shallow(<Comp />); // render your component
await Promise.resolve(); // let the callback queued in PromiseJobs run
expect(wrapper.state()).toEqual({ gatherings: response }); // SUCCESS
});
it('handles when agent.Gatherings.getAll() rejects', async () => { // use an async test function
spy.mockRejectedValue(new Error());
const wrapper = shallow(<Comp />); // render your component
await Promise.resolve(); // let the callback queued in PromiseJobs run
expect(wrapper.state()).toEqual({ gatherings: [] }); // SUCCESS
});
});
You should now have code coverage on the Promise callbacks in componentDidMount.

How to unit test Promise catch() method behavior with async/await in Jest?

Say I have this simple React component:
class Greeting extends React.Component {
constructor() {
fetch("https://api.domain.com/getName")
.then((response) => {
return response.text();
})
.then((name) => {
this.setState({
name: name
});
})
.catch(() => {
this.setState({
name: "<unknown>"
});
});
}
render() {
return <h1>Hello, {this.state.name}</h1>;
}
}
Given the answers below and bit more of research on the subject, I've come up with this final solution to test the resolve() case:
test.only("greeting name is 'John Doe'", async () => {
const fetchPromise = Promise.resolve({
text: () => Promise.resolve("John Doe")
});
global.fetch = () => fetchPromise;
const app = await shallow(<Application />);
expect(app.state("name")).toEqual("John Doe");
});
Which is working fine. My problem is now testing the catch() case. The following didn't work as I expected it to work:
test.only("greeting name is 'John Doe'", async () => {
const fetchPromise = Promise.reject(undefined);
global.fetch = () => fetchPromise;
const app = await shallow(<Application />);
expect(app.state("name")).toEqual("<unknown>");
});
The assertion fails, name is empty:
expect(received).toEqual(expected)
Expected value to equal:
"<unknown>"
Received:
""
at tests/components/Application.spec.tsx:51:53
at process._tickCallback (internal/process/next_tick.js:103:7)
What am I missing?
The line
const app = await shallow(<Application />);
is not correct in both tests. This would imply that shallow is returning a promise, which it does not. Thus, you are not really waiting for the promise chain in your constructor to resolve as you desire. First, move the fetch request to componentDidMount, where the React docs recommend triggering network requests, like so:
import React from 'react'
class Greeting extends React.Component {
constructor() {
super()
this.state = {
name: '',
}
}
componentDidMount() {
return fetch('https://api.domain.com/getName')
.then((response) => {
return response.text()
})
.then((name) => {
this.setState({
name,
})
})
.catch(() => {
this.setState({
name: '<unknown>',
})
})
}
render() {
return <h1>Hello, {this.state.name}</h1>
}
}
export default Greeting
Now we can test it by calling componentDidMount directly. Since ComponentDidMount is returning the promise, await will wait for the promise chain to resolve.
import Greeting from '../greeting'
import React from 'react'
import { shallow } from 'enzyme'
test("greeting name is 'John Doe'", async () => {
const fetchPromise = Promise.resolve({
text: () => Promise.resolve('John Doe'),
})
global.fetch = () => fetchPromise
const app = shallow(<Greeting />)
await app.instance().componentDidMount()
expect(app.state('name')).toEqual('John Doe')
})
test("greeting name is '<unknown>'", async () => {
const fetchPromise = Promise.reject(undefined)
global.fetch = () => fetchPromise
const app = shallow(<Greeting />)
await app.instance().componentDidMount()
expect(app.state('name')).toEqual('<unknown>')
})
By the looks of this snippet
.then((response) => {
return response.text();
})
.then((name) => {
this.setState({
name: name
});
})
it seems that text would return a string, which then would appear as the name argument on the next 'then' block. Or does it return a promise itself?
Have you looked into jest's spyOn feature? That would help you to mock not only the fetch part but also assert that the setState method was called the correct amount of times and with the expected values.
Finally, I think React discourages making side effects inside constructor. The constructor should be used to set initial state and other variables perhaps. componentWillMount should be the way to go :)
Recently, I have faced the same issue and ended up resolving it by following way
(taking your code as an example)
test.only("greeting name is 'John Doe'", async () => {
const fetchPromise = Promise.resolve(undefined);
jest.spyOn(global, 'fetch').mockRejectedValueOnce(fetchPromise)
const app = await shallow(<Application />);
await fetchPromise;
expect(app.state("name")).toEqual("<unknown>");});
Another way if you don't want to call done then return the next promise state to jest. Based on result of assertion( expect ) test case will fail or pass.
e.g
describe("Greeting", () => {
test("greeting name is unknown", () => {
global.fetch = () => {
return new Promise((resolve, reject) => {
process.nextTick(() => reject());
});
};
let app = shallow(<Application />);
return global.fetch.catch(() => {
console.log(app.state());
expect(app.state('name')).toBe('<unknown>');
})
});
});

Resources