Enzyme mocked axios PUT not being called - reactjs

I have a component which has a form with a submit button which when clicked performs an axios.put which I want to intercept and test.
So far I have the following code simplified for this example:
describe('Edit Client functionality', () => {
beforeEach(() => {
const mock = new MockAdapter(axios);
mock
.onPut('http://localhost:5000/api/entity/client/1')
.reply(200, { success: true });
});
it('Dummy example test', done => {
const component = mount(<DummyComponent />);
const spy = jest.spyOn(axios, 'put');
component.find('form').simulate('submit')
setTimeout(() => {
expect(spy).toHaveBeenCalled();
done();
}, 0);
})
I've tried many variations of the above but can't get the mocked PUT to be called. Any suggestions appreciated.

Related

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 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.

How to test a method called inside the component instance using Jest and Enzyme

how do we test (spy) inner function of an instance here this.props.onSave() using Jest.
class AddImage extends Component {
constructor(props) {
this.onContinue = this.onContinue.bind(this);
}
onContinue() {
this.props.onSave(img)
.then(()=>{
//redirect to some url
});
}
}
onContinue is called on click of a button.
test file code -
describe('AddImage', () => {
beforeAll(() => {
const enzymeWrapper = ShallowRender(<AddImage {...props} />);
onContinueSpy = jest.spyOn(enzymeWrapper.instance(),'onContinue');
// how to spy onSave
});
it('should continue and save image',()=>{
enzymeWrapper.find('button').simulate('click'); //click simulated
expect(onContinueSpy).toHaveBeenCalled(); // working as expected
});
});
Now how to spy onSave method.
As onSave is a prop you can create a spy in your test and just pass it. Not that you call .then on the result of onSave call so you have to return a resolved promise.
describe('AddImage', () => {
let onSave
let response
beforeAll(() => {
response = Promise.resolve()
onSave = jest.fn(()=> response)
const enzymeWrapper = ShallowRender(<AddImage {...props} onSave={onSave} />);
});
it('should continue and save image',async()=>{
enzymeWrapper.find('button').simulate('click'); //click simulated
expect(onSave).toHaveBeenCalledWith();
await response // if you want to test the redirect you need to wait here
// test the redirect here
});
});

How to test api call in react component and expect the view change after success of api call?

I have a simple react component with only one button, when that button is clicked it makes a api call using fetch, after the success call is calls setState to update the component.
in my my-button.jsx file
import React from "react";
export default class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {
user: null
}
this.getUser = this.getUser.bind(this);
}
async getUser() {
try {
const res = await fetch("http://localhost:3000/users");
if (res.status >= 400)
throw new Error("something went wrong");
const user = await res.json();
this.setState({ user });
} catch (err) {
console.error(err);
}
}
render() {
return (
<div>
<button onClick={this.getUser}>Click Me</button>
{this.state.user ? <p>got user</p> : null}
</div>
)
}
}
in my test file
import React from "react";
import { shallow, Mount } from "enzyme";
import MyButton from "../my-button";
beforeAll(() => {
global.fetch = jest.fn();
});
it("must test the button click", (done) => {
fetch.mockImplementation(() => {
return Promise.resolve({
status: 200,
json: () => {
return Promise.resolve({ name: "Manas", userId: 2 });
}
});
});
const wrapper = shallow(<MyButton />);
wrapper.find("button").simulate("click");
//here using setTimeout to delay the find call, How to avoid using setTimeout
setTimeout(() => {
wrapper.update();
expect(wrapper.find("p").length).toBe(1);
fetch.mockClear();
done();
}, 1000)
})
I am using setTime out to delay the expect call how to avoid using setTimeout as it is not the efficient way to test.
my test fails if i don't use setTimeout
src/App.test.js
FAIL src/components/__test__/my-button.test.js
● must test the button click
expect(received).toBe(expected) // Object.is equality
Expected: 1
Received: 0
26 | // setTimeout(() => {
27 | wrapper.update();
> 28 | expect(wrapper.find("p").length).toBe(1);
| ^
29 | fetch.mockClear();
30 | done();
31 | // }, 1000)
at Object.toBe (src/components/__test__/my-button.test.js:28:38)
Test Suites: 1 failed, 1 passed, 2 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
Using settimeout enforces this order for the test expectations.
getUser → testExpectations
With the present MyButton implementation, there isn't a straight forward way to achieve that. getUser needs to be extracted out and passed as a prop to MyButton so that there can be finer control on the promise chain i.e. chain test expectations on getUser
Sample
getUser().then(testExpectations)
In the first step of the refactor, call getUser in button onClick in place of the simulate call to the ShallowWrapper of your component.
This is what simulate does but it returns a wrapper instance. You don't want this; you want the promise returned from calling getUser so that you can chain to it.
it("must test the button click", (done) => {
fetch.mockImplementation(() => {
return Promise.resolve({
status: 200,
json: () => Promise.resolve({ name: "Manas", userId: 2 })
});
});
const wrapper = shallow(<MyButton />);
const button = wrapper.find("button");
const onClick = button.prop('onClick');
onClick().then(() => {
wrapper.update();
expect(wrapper.find("p").length).toBe(1);
fetch.mockClear();
done();
})
})
The next step of the refactor will be to forward getUser as a property to MyButton. This may not be necessary if you find that MyButton will always use that specific implementation for its click event handler.
If you have async invocations during your test run, you have to run your assertions/expectation at the end of event loop.
it('must test the button click', done => {
fetch.mockImplementation(() => {
return Promise.resolve({
status: 200,
json: () => {
return Promise.resolve({ name: 'Manas', userId: 2 });
}
});
});
const wrapper = shallow(<MyButton />);
wrapper.find('button').simulate('click'); // async invocation
// wait till async action is done
new Promise((resolve, reject) => {
setImmediate(() => {
resolve();
}, 0);
}).then(() => {
wrapper.update(); // you probably won't need this line
expect(wrapper.find('p').length).toBe(1);
fetch.mockClear();
done();
});
});
I usually have this written out as a util method in all my projects.
// test-helper.js
export const waitForAsyncActionsToFinish = () => {
return new Promise((resolve, reject) => {
setImmediate(() => {
resolve();
}, 0);
});
};
it('test something', (done) => {
// mock some async actions
const wrapper = shallow(<Component />);
// componentDidMount async actions
waitForAsyncActionsToFinish().then(() => {
wrapper.find('.element').simulate('click');
// onClick async actions - you have to wait again
waitForAsyncActionsToFinish().then(() => {
expect(wrapper.state.key).toEqual('val');
done();
});
});
});

spyOn fail even if the spy was called

In my component I have ...
onSubmit = (e) => {
e.preventDefault();
const { history, versionStore } = this.props;
versionStore.add(this.state.formData)
.then(() => history.push('/'));
}
On my test...
it('after successfully submit should redirect to / page', () => {
const spy = jest.spyOn(minProps.history, 'push')
.mockImplementation((path) => {
console.log('called with ', path); // IS CALLED!
});
const wrapper = shallow(<Add.wrappedComponent {...minProps} />);
fetchMock.postOnce('/api/version', { name: 'v1' });
wrapper.setState({ formData: { name: 'v1' } });
wrapper.find('form').simulate('submit', { preventDefault: jest.fn() });
expect(spy).toHaveBeenCalledWith('/');
spy.mockReset();
spy.mockRestore();
});
The test fail with
called with /
expect(jest.fn()).toHaveBeenCalledWith(expected)
Expected mock function to have been called with: ["/"]
But it was not called.
your redirect is inside of asynchronous code and you are testing it in a synchronous manner, meaning when the test executes the promise is not resolved yet. I would tackle this in one of 2 ways
1 - test your submit function w/o the event, then you can return the promise and test the redirection after the promise chain is successful
2 - mock versionStore.add to be synchronous and immidattly execute it's then function.

Resources