How do I properly test for a rejected promise using Jest? - reactjs

Code
import { createUser } from '../services';
...
...
handleFormSubmit = () => {
this.setState({ loading: true });
createUser()
.then(() => {
this.setState({
loading: false,
});
})
.catch(e => {
this.setState({
error: e,
});
});
};
Test
it('rejects...', () => {
const Container = createUserContainer(CreateUser);
const wrapper = shallow(<Container />);
return wrapper.instance().handleFormSubmit()
.catch(e => {
console.log("State: ", wrapper.state());
expect(e).toEqual('error');
});
});
Mock
export const createUser = function() {
return new Promise((resolve, reject) => {
reject('error');
});
};
The test does force the code to go into the catch in the method. So the state does get set to 'error'.
But in my test, it doesn't do what I expect and wait for the Promise to reject before it tests for the state change.
I'm not sure what to try here, should I be using async/await?
So it's the createUser method I want to wait for but I'm not sure my implementation allows for this.

You should do something like this:
it('rejects...', () => {
const Container = createUserContainer(CreateUser);
const wrapper = shallow(<Container />);
return expect(wrapper.instance().handleFormSubmit()).rejects.toEqual('error');
});
I think it is cleaner this way. You can see this approach in the official docs.
It's important to note that .rejects (and .resolves) returns a promise, which is returned in the example above so that jest knows to wait on it. If you don't return it, you MUST await it:
it('rejects...', async () => {
const Container = createUserContainer(CreateUser);
const wrapper = shallow(<Container />);
await expect(wrapper.instance().handleFormSubmit()).rejects.toEqual('error');
});

The test fails because it's not aware that the subject is asynchronous. It can be fixed by using a done param or making the test function async.
Note it's also necessary to set the number of expected assertions so that the test will fail even if the catch branch is not taken.
async/await style:
it('rejects...', async () => {
expect.assertions(1);
const Container = createUserContainer(CreateUser);
const wrapper = shallow(<Container />);
await wrapper.instance().handleFormSubmit()
.catch(e => {
console.log("State: ", wrapper.state());
expect(e).toEqual('error');
});
});
Older style done param:
it('rejects...', done => {
expect.assertions(1);
const Container = createUserContainer(CreateUser);
const wrapper = shallow(<Container />);
wrapper.instance().handleFormSubmit()
.catch(e => {
console.log("State: ", wrapper.state());
expect(e).toEqual('error');
done();
});
});
Asynchronous Testing Reference
expect.assertions reference

Your code looks correct. Why do you say that it doesn't wait for the Promise to reject? The only difference I would make would be to make use of Jest's mocking capability, so change
Mock
export const createUser = function() {
return new Promise((resolve, reject) => {
reject('error');
});
};
to
Test
jest.mock('../services');
const services = require('../services');
const createUser = jest.spyOn(services, "createUser");
createUser.mockRejectedValue("error");
...
it('rejects...', () => {
There's no need to have a separate Mock file

In your code handleFormSubmit function should return Promise on which you can wait in your test. Also you need to return truthful data from success and error callback to resolve and reject the promise respectively.
handleFormSubmit = () => {
this.setState({ loading: true });
return createUser()
.then(() => {
this.setState({
loading: false,
});
return true;
})
.catch(e => {
this.setState({
error: e,
});
throw e;
});
};
Here in your actual code you have caught the error in catch handler and trying to catch it further in out test case code. Hence catch can not be chained further, while you can chain then multiple times.
For reference go through Promise documentations:
https://www.peterbe.com/plog/chainable-catches-in-a-promise

Related

How to mock async call in React functional component using jest

I am testing a functional component that has a submit button that makes an async call to an api. The async call is located within a custom hook. As per standard testing practices, I have mocked the hook, so that my mock will be called instead of the actual async api:
someComponent.test.js
jest.mock("../../../CustomHooks/user", () => ({
useUser: () => ({
error: null,
loading: false,
forgotPassword: <SOMETHING HERE>
})
}));
I know that my forgotPassword function is called because when I change it to forgotPassword: "", I get an error in my test stating that forgotPassword is not a function.
A very simple representation of the function that is called when my submit button is clicked is this:
someComponent.js
import { useUser } from "../../../CustomHooks/user"
const SomeComponent = () => {
....state and other things etc....
const { error, loading, forgotPassword } = useUser()
const submit = async () => {
await forgotPassword(emailValue);
setState(prevState => {
return {
...prevState,
content: "code"
};
});
}
}
NOTE: My call to the async function await forgotPassword... is wrapped in a try/catch block in my code, but I have left this out for clarity.
In production, when the submit button is pressed, the async call occurs, and then the state should be switched, thus rendering some other components. My test looks to see if these components have been rendered (I am using react testing library for this).
The problem that I am having is that no matter what I place in the placeholder of the first code block, my test will always fail as the setState block is never reached. If I remove the await statement, then the setState block is hit and the component that I want to appear is there as the state has changed. However, obviously this will not work as intended outside of the test as the actual call is asynchronous. Here are some of the approaches that I have tried that do not work:
DOESN'T WORK
forgotPassword: () => {
return Promise.resolve({ data: {} });
}
DOESN'T WORK
forgotPassword: jest.fn(() => {
return Promise.resolve();
})
DOESN'T WORK
forgotPassword: jest.fn(email => {
return new Promise((resolve, reject) => {
if (email) {
resolve(email);
} else {
reject("Error");
}
});
}),
As I have said already, if I remove the await statement, then the state changes and the component appears, and hence the test passes. However, for obvious reasons, this is not what I want.
Extra Info
Here is a simplified version of my test:
it("changes state/content from email to code when submit clicked", () => {
const { getByTestId, getByText, debug } = render(<RENDER THE COMPONENT>);
const submitButton = getByTestId("fpwSubmitButton");
expect(submitButton).toBeInTheDocument();
const emailInput = getByTestId("fpwEmailInput");
fireEvent.change(emailInput, {
target: { value: "testemail#testemail.com" }
});
fireEvent.click(submitButton);
debug();
THE STATEMENTS BELOW ARE WHERE IT FAILS AS THE STATE DOESN'T CHANGE WHEN AWAIT IS PRESENT
const codeInput = getByTestId("CodeInput");
expect(codeInput).toBeInTheDocument();
});
To anyone who encounters this same problem, I found three ways that this can be solved (the preferred method is Option 3). All methods use a simple mock function that replaces the <SOMETHING HERE> of the first code block in my question. This can be replaced with () => {}:
jest.mock("../../../CustomHooks/user", () => ({
useUser: () => ({
error: null,
loading: false,
forgotPassword: () => {}
})
}));
Option 1
The first approach is to wrap your test code that relies on an async function in a setTimeout with a done callback:
it("changes state/content from email to code when submit clicked", done => {
const { getByTestId, debug } = render(<RENDER THE COMPONENT>);
const submitButton = getByTestId("fpwSubmitButton");
expect(submitButton).toBeInTheDocument();
const emailInput = getByTestId("fpwEmailInput");
fireEvent.change(emailInput, {
target: { value: "testemail#testemail.com" }
});
fireEvent.click(submitButton);
setTimeout(() => {
const codeInput = getByTestId("CodeInput");
expect(codeInput).toBeInTheDocument();
done();
});
debug();
});
Notice on the top line the done call back, as well as the test code wrapped in setTimeout at the bottom, and then invoking the callback within the setTimeout to tell jest that the test is done. If you don't call the done callback, the test will fail as it will timeout.
Option 2
The second approach is to use a function called flushPromises():
function flushPromises() {
return new Promise(resolve => setImmediate(resolve));
}
it("changes state/content from email to code when submit clicked", async () => {
const { getByTestId, debug } = render(<RENDER THE COMPONENT>);
const submitButton = getByTestId("fpwSubmitButton");
expect(submitButton).toBeInTheDocument();
const emailInput = getByTestId("fpwEmailInput");
fireEvent.change(emailInput, {
target: { value: "testemail#testemail.com" }
});
fireEvent.click(submitButton);
await flushPromises();
const codeInput = getByTestId("CodeInput");
expect(codeInput).toBeInTheDocument();
debug();
});
Notice the flushPromises() function at the top, and then the call site towards the bottom.
Option 3 (Preferred Method)
The final method is to import wait from react-testing-library, set your test as asynchronous and then await wait() whenever you have async code:
...
import { render, fireEvent, cleanup, wait } from "#testing-library/react";
...
it("changes state/content from email to code when submit clicked", async () => {
const { getByTestId, debug } = render(<RENDER THE COMPONENT>);
const submitButton = getByTestId("fpwSubmitButton");
expect(submitButton).toBeInTheDocument();
const emailInput = getByTestId("fpwEmailInput");
fireEvent.change(emailInput, {
target: { value: "testemail#testemail.com" }
});
fireEvent.click(submitButton);
await wait()
const codeInput = getByTestId("CodeInput");
expect(codeInput).toBeInTheDocument();
debug();
});
All of these solutions work because they wait for the next event loop before executing the test code. Wait() is basically a wrapper around flushPromises() with the added benefit of including act(), which will help to silence test warnings.
try something like this
forgotPassword: jest.fn( async email => {
return await new Promise( ( resolve, reject ) => {
if ( email ) {
resolve( email );
} else {
reject( "Error" );
}
} );
} );
If it doesn't work let me know.

Jest mock module resolve with variable value

Assuming I have a module which returns a promise.
I want to mock different outcomes of this promise to test the function where this module is part of. I mock the module like this:
jest.mock('../request', () => {
return () => new Promise((resolve, reject) => {
return resolve({
response: { ok: true }
});
});
});
My first test is running
test("The function resolves", () => {
const initialState = { apiData: getState("postData", {}, "ready", "POST") };
const store: any = mockStore(initialState);
return expect(
performApiRequest("postData/", {}, { data: "json" })(dispatch, () =>
store.getState()
)
).resolves.toBeUndefined();
});
The problem is now with testing an other function where the value that resolves is supposed to be different, for instance {response: { ok: false } }.
I already tried different things. First wrapping the mock in a function and give the response as an argument. --> fails for mocks can't take out of scope variables.
I tried to call jest.doMock within the test but this does not trigger the request correctly.
I tried to do
const mockResponse = jest.fn();
jest.mock("../request", () => {
return () =>
new Promise((resolve, reject) => {
return resolve({
mockResponse
});
});
});
And then call mockResponse.mockReturnValueOnce(value).
No success yet. Anybody sees a way out?
You can create a default mock function at the top level with jest.fn. Once you create the mock you can override the implementation of the function within the test case with mockImplementation or mockImplementationOnce. You can find more information about this in the Jest documentation.
import request from '../request';
jest.mock("../request", () =>
jest.fn(() =>
Promise.resolve({
response: {
ok: true
}
})
)
);
test("MyTest", () => {
request.mockImplementationOnce(() =>
Promise.resolve({
response: {
ok: false
}
})
);
});
answer with typescript would be:
import request from '../request';
jest.mock("../request", () =>
jest.fn(() =>
Promise.resolve({
response: {
ok: true
}
})
)
);
test("MyTest", () => {
(request as jest.Mock).mockImplementationOnce(() =>
Promise.resolve({
response: {
ok: true
}
})
);
});

How to test a method which is resolving a promise and then changing state in React?

I need to test that when button is clicked and after promise resolve
state.message === 'loggedIn successfully'
class Login extends Component {
constructor() {
this.onLoginClick = this.onLoginClick.bind(this);
}
fetchLogin() {
return new Promise(function (resolve) {
reollve({ success: true });
})
}
onLoginClick() {
let that = this;
fetchLogin().then(function ({ success }) {
success ?
that.setState({ message: 'loggedIn successfully' }) :
that.setState({ message: 'Fail' });
})
}
render() {
return (<div>
<button onClick={this.onLoginClick}></button>
</div>)
}
}
I guess you are aware of jest simulate in order to simulate your click ( if not then simulate)
You should be able to use jest async/await or promises with jest, here is the link to the official doc
It should be something like this :
it('works with async/await and resolves', async () => {
const wrapper = mount(<Login />);
wrapper.find('button').simulate('click');
await expect(wrapper.state().message).resolves.toEqual('loggedIn successfully');
});
Since you are performing a test on a promise, you should wait for the function triggered by the button to execute before you can make the assertion (thus the await).
The code below should work for you case:
it('should change state on successful login', async () => {
const wrapper = mount(<Login />);
await wrapper.find('button').simulate('click');
expect(wrapper.state().message).toEqual("loggedIn successfully");
});

Cannot access (async) callback function Jest

I am trying to test the esri promise with Jest & Enzyme
import { esriPromise } from 'react-arcgis';
componentWillMount(){
this.setState({name: 'JAKE'});
this.addTileLayer();
};
addTileLayer(ext, url) {
this.setState({name: 'Amber'});
let promise = esriPromise(['esri/layers/TileLayer']).then(([TileLayer]) => {
this.setState({name: 'THISONE'});
let tileLayer = new TileLayer();
this.setState({
layers: [...this.state.layers, tileLayer]
})
}).catch((err) => console.error(err));
this.setState({prmoise: promise})
};
In my test,
test('Promise states check', async () => {
const map = shallow(<Map />)
await map.instance().componentWillMount();
console.log(map.state());
});
But it always prints, promise: Promise { < pending > } } Seems like it's not going inside the esriPromise function. (I have also tried done() which was unsuccessful as well)
Any tips would be greatly appreciated!
Try returning the esriPromise in the addTileLayer function

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