Reputation: 16842
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?
Upvotes: 9
Views: 16061
Reputation: 189
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>");});
Upvotes: 0
Reputation: 6884
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>')
})
Upvotes: 10
Reputation: 73
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 :)
Upvotes: 0
Reputation: 24130
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>');
})
});
});
Upvotes: -1