How to test the function after .then()? - reactjs

I'm trying to write the unit test for a function.
Here is the code:
const show = () => {
SheetManager.hide('Screen1')
.then(() => {
SheetManager.show('Screen2', {
payload: {
preSheet: id,
},
});
});
};
The test code is like:
jest.spyOn(SheetManager, 'hide').mockImplementation(() => {});
jest.spyOn(SheetManager, 'show').mockImplementation(() => {});
await waitFor(() => expect(SheetManager.hide).toBeCalledTimes(1));
await waitFor(() => expect(SheetManager.hide).toBeCalledWith('Screen1'));
// await waitFor(() => expect(SheetManager.show).toBeCalledTimes(1));
// await waitFor(() => expect(SheetManager.show).toBeCalledWith('Screen2'));
The problem is the two commented line of test code of show function do not work. I think it's because the .then(), so the show function is called inside hide function.
So how should I change the code to make it work?
Look forward to your suggestion!

Related

How to write a test to handle an Axios error

I have a GET Axios request in which I pass a github user and get information about him. I need to process the option when the request gives an error, which I process in catch (). How can i do this?
//get user
async componentDidMount() {
getUser(this.props.user)
.then((response) => {
this.setState(response.data);
})
.catch((error) => {
this.setState({
error: "request error"
});
});
}
//jest
jest.mock("axios");
describe("UserList Component", () => {
it("should return user login", async () => {
const user = { data: "login1" };
axios.get.mockResolvedValueOnce(user);
const res = await getUser(user);
expect(res.data).toContain("login1");
});
it("should return error", async () => {
//Here I need to write a test in which I check the error so that the error text matches
});
this is the answer testing the catch block using jest
expect(() => {
const model = new Sample(resolvedSample)
}).toThrow(TypeError);

React Testing Library: Why does the order of the tests matter?

Here's a link to codesanbox
Reordering the below tests would make find one after the other test to pass.
Another way to make it pass would be to make should be able to find 3 directly test to pass, for example, by making it find 2 instead of 3.
describe("Counter", () => {
test("should be able to find 3 directly", async () => {
render(<Counter />);
const three = await waitFor(() => screen.findByText(/3/i));
expect(three).toBeInTheDocument();
});
test("find one after the other", async () => {
render(<Counter />);
const one = await waitFor(() => screen.findByText(/1/i));
expect(one).toBeInTheDocument();
const two = await waitFor(() => screen.findByText(/2/i));
expect(two).toBeInTheDocument();
const three = await waitFor(() => screen.findByText(/3/i));
expect(three).toBeInTheDocument();
});
});
So the question is, why does the order of the tests matter? Why is the clean up not working?
It looks like waitFor has reach its timeout as waiting for the counter to 3 which means it is unrelated to ordering in this case I guess.
You would fix by increase the timeout to wait your counter as following:
test("should be able to find 3 directly", async () => {
render(<Counter />);
const three = await waitFor(() => screen.findByText(/3/i), {
timeout: 3e3, // 3s wait would find 3
});
expect(three).toBeInTheDocument();
});

How to reset a spy or mock in jest

I have a function that I have mocked in my test cases file.
MyService.getConfigsForEntity = jest.fn().mockReturnValue(
new Promise(resolve => {
let response = [];
resolve(response);
})
);
Now I want all my test cases in that file to use this mock like
describe('Should render Component Page', () => {
it('should call the API', async () => {
const {container} = render(<MyComp entityName='entity1'/>);
await wait(() => expect(MyService.getConfigsForEntity).toHaveBeenCalled());
});
});
The only issue is in only one of the test case I want to mock the return value differently.
But all other test cases before and after can use the global mock.
describe('Should call API on link click', () => {
it('should call the API on link click', async () => {
const spy = jest.spyOn(MyService, 'getConfigsForEntity ').mockReturnValue(
new Promise(resolve => {
let response = [{
"itemName": "Dummy"
}];
resolve(response);
});
const {container} = render(<MyComp entityName='entity1'/>);
await wait(() => expect(spy).toHaveBeenCalled());
spy.mockClear();
});
});
The problem is , once I mock the function differently inside one test case , all other test cases after that test case, that are using the global mock , are failing,
But it only works if I put the test case after all other test cases.
What am I doing wrong?
You can try with mockRestore():
beforeEach(() => {
spy.mockRestore();
});
have you tried?
beforeEach(() => {
jest.clearAllMocks();
})

How to mock a fetch call that is within an arrow function?

I'm trying to test the invocation of a function that deletes specific data saved in a database in React. The problem is I want to only mock the fetch call and have everything else run as usual because right now whenever tests are run the data gets deleted in the database.
Here is my code for the delete function:
deleteEvent = async () => {
try {
await fetch(
"api url",
{
method: "DELETE",
}
)
.then((res) => res.json())
.then(
(result) => {
console.log(result);
},
(error) => {
console.log(error);
}
);
} catch (error) {
console.error(error);
}
this.props.history.push("/eventList");
};
And here is my test code:
test("deleteEvent function works", (done) => {
const mockSuccessResponse = {};
const mockJsonPromise = Promise.resolve(mockSuccessResponse);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
jest.spyOn(global, "fetch").mockImplementation(() => mockFetchPromise);
const historyMock = { push: jest.fn() };
const wrapper = shallow(<EventState history={historyMock} />);
wrapper.instance().deleteEvent();
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(historyMock.push.mock.calls[0]).toEqual(["/eventList"]);
global.fetch.mockClear();
done();
});
I get number times called: 0 for the expect(global.fetch).toHaveBeenCalledTimes(1);
and a Received: undefined for the expect(historyMock.push.mock.calls[0]).toEqual(["/eventList"]);
Any help would be great
Instead of using spyOn(global, fetch), try this:
global.fetch = jest.fn().mockImplementation(() => mockFetchPromise);
const historyMock = { push: jest.fn() };
const wrapper = shallow(<EventState history={historyMock} />);
wrapper.instance().deleteEvent();
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(historyMock.push.mock.calls[0]).toEqual(["/eventList"]);
global.fetch.mockClear();
done();
});

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

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

Resources