Testing-Library React don't trigger click after an async - reactjs

I'm trying to write a test in Jest.
When the fireEvent.click comes after an async it it doesn't trigger the function.
This is the exact case where few lines of code are better than hundreds of words that describes the issue. In the snippet below, the second and third test are exactly the same (copy-and-pasted) except for the name of the test itself of course.
import {render, screen, waitFor, fireEvent} from '#testing-library/react';
import React from 'react';
const Component = ({asyncClick, syncClick}) => {
function onClick(){
console.log('clicked');
syncClick();
setTimeout(() => {
asyncClick();
}, 50);
}
return <div data-testid="toBeClick" onClick={onClick}>
</div>;
}
describe('a test', function(){
it('element exists', function(){
render(<Component/>);
let el = screen.getByTestId('toBeClick');
expect(el).toBeInTheDocument();
});
it('element Click', async function(){
let syncClick = jest.fn();
let asyncClick = jest.fn();
render(<Component asyncClick={asyncClick} syncClick={syncClick} />);
let el = screen.getByTestId('toBeClick');
fireEvent.click(el);
expect(syncClick).toHaveBeenCalledTimes(1);
await waitFor(() => {
expect(asyncClick).toHaveBeenCalledTimes(1);
});
});
it('element Click / 2', async function(){
let syncClick = jest.fn();
let asyncClick = jest.fn();
render(<Component asyncClick={asyncClick} syncClick={syncClick} />);
let el = screen.getByTestId('toBeClick');
fireEvent.click(el);
expect(syncClick).toHaveBeenCalledTimes(1);
await waitFor(() => {
expect(asyncClick).toHaveBeenCalledTimes(1);
});
});
})
The second test pass. The third test fails. Why?
As a side note, removing async/await and the waitFor from the second test, even the third test starts to pass.
-- UPDATE
Looks like the failure is due to an import in jest-setup.js:
require('ionic/js/ionic.js');
I'm importing Ionic v1 and it breaks the tests.

Wrap async actions that trigger something in act.
act(() => {
fireEvent.click()
});
Can try to use jest.useFakeTimers() and jest.runAllTimers().
In begin of test jest.useFakeTimers() and after click dispatched jest.runAllTimers()
If nothing works can try
await new Promise(resolve => setTimeout(resolve, 0));

Related

How to wait to assert an element never appears in the document?

I want to assert that an element never appears in my document. I know I can do this:
import '#testing-library/jest-dom/extend-expect'
it('does not contain element', async () => {
const { queryByText } = await render(<MyComponent />);
expect(queryByText('submit')).not.toBeInTheDocument();
});
But in my case I need to wait to ensure that the element isn't added after a delay. How can I achieve this?
There are two ways to do this, both involving react-testing-library's async helper function waitFor.
The first and simpler method is to wait until something else happens in your document before checking that the element doesn't exist:
import '#testing-library/jest-dom/extend-expect'
it('does not contain element', async () => {
const { getByText, queryByText } = await render(<MyComponent />);
await waitFor(() => expect(getByText('something_else')).toBeInTheDocument());
expect(queryByText('submit')).not.toBeInTheDocument();
});
You can use the same strategy with any valid Jest assertion:
import '#testing-library/jest-dom/extend-expect'
import myFunc from './myFunc'
it('does not contain element', async () => {
const { getByText, queryByText } = await render(<MyComponent />);
await waitFor(() => expect(myFunc).toBeCalled());
expect(queryByText('submit')).not.toBeInTheDocument();
});
If there isn't any good assertion you can use to wait for the right time to check an element does not exist, you can instead use waitFor to repeatedly check that an element does not exist over a period of time. If the element ever does exist before the assertion times out, the test will fail. Otherwise, the test will pass.
import '#testing-library/jest-dom/extend-expect'
it('does not contain element', async () => {
const { getByText } = await render(<MyComponent />);
await expect(async () => {
await waitFor(
() => expect(getByText('submit')).toBeInTheDocument();
);
}).rejects.toEqual(expect.anything());
});
You can adjust the amount of time waitFor will keep checking and how frequently it will check using the timeout and interval options. Do note, though, that since this test waits until waitFor times out for the test to pass, increasing the timeout option will directly increase the time this test takes to pass.
And here is the helper function I wrote to avoid having to repeat the boilerplate:
export async function expectNever(callable: () => unknown): Promise<void> {
await expect(() => waitFor(callable)).rejects.toEqual(expect.anything());
}
Which is then used like so:
it('does not contain element', async () => {
const { getByText } = await render(<MyComponent />);
await expectNever(() => {
expect(getByText('submit')).toBeInTheDocument();
});
});
We use plain JavaScript and the expectNever function from #Nathan throws an error:
Error: expect(received).rejects.toEqual()
Matcher error: received value must be a promise
I modified it to look and feel more like waitFor and this works:
const waitForNeverToHappen = async (callable) => {
await expect(waitFor(callable)).rejects.toEqual(expect.anything())
}
await waitForNeverToHappen(() => expect(screen.getByText('submit')).toBeInTheDocument())
Consider using waitForElementToBeRemoved documented here:
https://testing-library.com/docs/guide-disappearance/#waiting-for-disappearance

I want to spyOn method, return value, and continue running through the script

I am spying on a method, in this case: axios.post. I want to resolve it and continue running the script, because in the then block, it has to run another method. The current way I am solving it, is with a jest.spyOn(axios, 'post').mockImplementation(() => Promise.resolve( ));. This, as it says, a mock implementation, and will completely forego the actual code. What I want is a way to resolve this call (by returnValue or something) and continue the actual code. I remember Jasmin had something to the effect of that.
EDIT:
I seems that the axios promise.resolve is actually progressing it. The problem more lies in that I am trying to mock the helpers.showResponseMessage and Jest keeps insisting it does not get called (even when the console.log within the mock gets called). Any ideas?
Contact (Axios call I am trying to resolve properly)
const recommendationHandler = (event) => {
event.preventDefault();
axios.post('/contact.json', extractData(config.addMessage))
.then(() => {
showResponseMessage(`Din besked er sendt.`, initialState, false, setConfig, 5000);
})
.catch(() => {
showResponseMessage(`Der opstod en fejl ved sendning. Prøv igen senere.`, {}, true, setConfig, 10000);
});
}
Testfile
import React from 'react';
import { configure, shallow, mount, render } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import axios from '../../axios-instances/axios-firebase'
import * as helpers from '../../utility/Helpers/Helpers'
import Contact from './Contact';
configure({ adapter: new Adapter() });
describe('<Contact />', () => {
it.only('should POST data to server if necessary payload is included', async () => {
jest.spyOn(axios, 'post').mockImplementation(() => Promise.resolve( ));
const wrapper = mount(<Contact />);
jest.spyOn(helpers, 'showResponseMessage').mockImplementationOnce(_ => console.log('test'));
wrapper.find('form').simulate('submit', {
preventDefault: () => { }
});
expect(axios.post).toHaveBeenCalledTimes(1);
expect(helpers.showResponseMessage).toHaveBeenCalled();
expect(wrapper.find('#responseMessage')).toHaveLength(1);
});
});
Stackoverflow provided, as it always does.
Apparently you need to put in an await Promise.resolve() to split it into microtasks. I am not quite sure how it works, and if somebody could explain, it would be great.
Stackoverflow answer: How to test async function with spyOn?
describe('<Contact />', () => {
it.only('should POST data to server if necessary payload is included', async () => {
jest.spyOn(axios, 'post').mockImplementation(() => Promise.resolve( ));
const wrapper = mount(<Contact />);
jest.spyOn(helpers, 'showResponseMessage').mockImplementationOnce(_ => console.log('test'));
wrapper.find('form').simulate('submit', {
preventDefault: () => { }
});
expect(axios.post).toHaveBeenCalledTimes(1);
await Promise.resolve(); <-- THIS IS APPARENTLY A MICROTASK
expect(helpers.showResponseMessage).toHaveBeenCalled();
expect(wrapper.find('#responseMessage')).toHaveLength(1);
});
});

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();
})

Enzyme mocked axios PUT not being called

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.

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
});
});

Resources