React Testing Library Mock function not called - reactjs

I am pretty new to testing and attempting to write what should be a simple test for our project...
test('Attempt Login', async () => {
const submitHandler = jest.fn( ()=> console.log('hello'))
const { debug, getByText, getByTestId, getByPlaceholderText } = render
(
<Router>
<LoginPage submitHandler={submitHandler} />
</Router>
)
fireEvent.change(getByPlaceholderText("Enter Username"), {
target: { value: "admin" }
});
fireEvent.change(getByPlaceholderText("Enter Password"), {
target: { value: "Password" }
});
fireEvent.click(getByTestId("login-btn"));
expect(submitHandler).toHaveBeenCalled()
})
My button inside of login
<Button data-testid="login-btn" type="submit" variant="contained" color="primary"
onClick={(event)=>submitHandler(event)}>
the testing error
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
45 | fireEvent.click(getByTestId("login-btn"));
46 |
> 47 | expect(submitHandler).toHaveBeenCalled()
| ^
48 | })
49 |
50 |
Thanks in advance for any help. I spent way too long on this already -_-
EDIT: attempting to test for the results of clicking the login button
Heres what I'm going trying:
mock an Axios call to the login route
await waitForElement getByText('home')
expect getbytext('home')
Am I on the right track?
Do I need to import the redirect page component and place it inside the router? for example the component for it to redirect to it?

You should use waitFor in this case:
...
await waitFor(() => expect(submitHandler).toHaveBeenCalled())
...
More info HERE

As you already figured out, the problem is you are passing the submitHandler mock into LoginPage but you are not using that prop.
To answer your second question
How do I mock a function not passed in as a prop?
Here is how you can mock functions imported from different files with Jest:
import { submitForm } from './ajax.js'; // the function to mock
jest.mock('./ajax.js'); // jest mocks everything in that file
it('should call submitForm correctly', async () => {
submitForm.mockResolvedValue({ loggedIn: true });
render(<LoginPage />);
userEvent.click(screen.getByRole('button', { name: 'Login' }));
expect(submitForm).toHaveBeenCalledTimes(1);
expect(await screen.findByText('You have logged in successfully')).toBeInTheDocument();
});
Useful links
Mocking modules
Understanding Jest mocks
mockResolvedValue

Related

React - Testing function passed in as props is called via button press

I am attempting to unit test a simple component that calls a function passed into it.
It is a simple footer component with two button, cancel/save.
When save is pressed, it should call the "handleSubmit" property I have passed into it but when attempting to test with jest, I cannot get the tests to pass.
Component:
function GSFooter({
handleSubmit,
}) {
return (
<Footer>
<FooterActionsWrap>
<CancelButton className="update-btn">
{" "}
<Link to={"/invoices"}>Cancel</Link>
</CancelButton>
<button
onSubmit={e => handleSubmit(e)}
className="wp-btn update-btn"
data-testid="submit-button"
>
Save Changes
</button>
</FooterActionsWrap>
</Footer>
);
}
and the test file
let handleSubmitMock = jest.fn();
test("it should run", () => {
const {container} = render(<GSFooter
handleSubmit={handleSubmitMock}
errors={{}}
/>);
fireEvent.click(getByTestId(container, 'submit-button'));
expect(handleSubmitMock).toBeCalled();
});
output:
expect(jest.fn()).toBeCalled()
Expected number of calls: >= 1
Received number of calls: 0
36 | const submitButton = getByTestId(container, 'submit-button');
37 | fireEvent.click(submitButton);
> 38 | expect(handleSubmitMock).toBeCalled();
| ^
39 | })
40 | });
41 |
UPDATED
After discussion with #cw23, he figured out that he's using onSubmit which is only triggered with fireEvent.submit instead of fireEvent.click! This is very useful info for developers facing a similar problem.
OLD ANSWER
You should call getByTestId directly. container is usually referred to DOM elements
test("it should run", () => {
const { getByTestId } = render(<GSFooter
handleSubmit={handleSubmitMock}
errors={{}}
/>);
fireEvent.click(getByTestId('submit-button'));
expect(handleSubmitMock).toBeCalled();
});

Describe method can only pass with 1 test unless re-rendering each component again and again

I'm trying to figure out why my test - which passes when ran alone - is failing whenever the describe block contains more than 1 test. Take this example, which I've taken from my real code and simplified:
describe('Create Account Form', () => {
const {container} = render(<CreateAccountForm />);
const email = container.querySelector('input[name="email"]');
const password1 = container.querySelector('input[name="password1"]');
it('Should render all fields', () => {
allInputs.forEach((input) => {
expect(input).toBeInTheDocument();
});
});
it('Another test', () => {
expect(email).toBeInTheDocument(); // fails
});
});
The 2nd test fails, but passes only when commenting out the first test, or re-rendering the container again in the test like this:
it('Another test', () => {
const {container} = render(<CreateAccountForm />);
const email = container.querySelector('input[name="email"]');
expect(email).toBeInTheDocument(); // passes
});
Why does this have to happen? I would much rather not have to re-render the container and declare new variables inside each test block.
Thank you
RTL will unmount React trees that were mounted with render in afterEach hook. See cleanup.
Please note that this is done automatically if the testing framework you're using supports the afterEach global and it is injected to your testing environment (like mocha, Jest, and Jasmine).
Move the render code into beforeEach or individual test case. So that we can create react trees before each test case. Isolate test cases from each other, using their own test data without affecting the rest.
E.g.
index.tsx:
import React from 'react';
export function Example() {
return (
<div>
<input name="email" />
<input name="password1" />
</div>
);
}
index.test.tsx:
import { render } from '#testing-library/react';
import '#testing-library/jest-dom/extend-expect';
import React from 'react';
import { Example } from './';
describe('70753645', () => {
let email, password1, allInputs;
beforeEach(() => {
const { container } = render(<Example />);
email = container.querySelector('input[name="email"]');
password1 = container.querySelector('input[name="password1"]');
allInputs = container.querySelectorAll('input');
});
it('Should render all fields', () => {
allInputs.forEach((input) => {
expect(input).toBeInTheDocument();
});
});
it('Another test', () => {
expect(email).toBeInTheDocument();
});
});
Test result:
PASS stackoverflow/70753645/index.test.tsx (9.222 s)
70753645
✓ Should render all fields (24 ms)
✓ Another test (3 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 9.717 s
package versions:
"#testing-library/react": "^11.2.2",
"jest": "^26.6.3",

Mock function doesn't get called when inside 'if' statement - React app testing with jest and enzyme?

I am writing a test case for my react app and I'm trying to simulate a button click with a mock function. I'm passing the mock function as a prop and I'm calling the function inside an 'if' statement but the mock function doesn't get called and the test fails but if i call the function without the 'if' statement it gets called and the test passes. Why is this happening?
Form.js
const Form = ({ text, incompleteList, setIncompleteList }) => {
const submitTodoHandler = (e) => {
e.preventDefault()
if (text !== '') {
setIncompleteList([...incompleteList, { name: text, id: Math.random() * 1000 }])
}
}
return (
<form action='' autoComplete='off'>
<button type='submit' className='todo-button' onClick={submitTodoHandler}>
add
</button>
</form>
)
}
export default Form
Form.test.js
import Enzyme, { shallow, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Form from '../components/Form'
Enzyme.configure({ adapter: new Adapter() })
test('Form calls setIncompleteList prop on add button onClick event', () => {
const mockfn = jest.fn()
const wrapper = mount(<Form setIncompleteList={mockfn} />)
wrapper.find('button').simulate('click')
expect(mockfn).toHaveBeenCalled()
})
I'm using react 16.
The problem was I did not pass the 'text' props to the form component and the comparison failed to take place that's why the mock doesn't get called and the test failed.
<Form text='mock' setIncompleteList={mockfn} />
Pass value and incompleteList while mounting the component
test('Form calls setIncompleteList prop on add button onClick event', () => {
const mockfn = jest.fn()
const wrapper = mount(<Form text='mock'
incompleteList={[{name: 'sarun', id: 1001}]} setIncompleteList={mockfn} />)
wrapper.find('button').simulate('click')
expect(mockfn).toHaveBeenCalled()
})
you can also set a default value for incompletelist like below so that no need to pass incompletelist while mounting the component,
const Form = ({ text, incompleteList = [], setIncompleteList }) => {
}

Test a component with useState and setTimeout

Code structure is as same as given below:
FunctionComponent.js
...
const [open, handler] = useState(false);
setTimeout(() => {handler(true);}, 2000);
...
return (
...
<div className={active ? 'open' : 'close'}>
)
comp.test.js
jest.useFakeTimers();
test('test case 1', () => {
expect(wrapper.find('open').length).toBe(0);
jest.advanceTimersByTime(2000);
expect(wrapper.find('open').length).toBe(1);
jest.useRealTimers();
});
The problem is that the expression written in bold in test is saying the length of open class is still 0, so actual and expected are not meeting.
You want to test the outcome of the hook and not the hook itself since that would be like testing React. You effectively want a test where you check for if the open class exists and then doesn't exist (or vice versa), which it looks like you're trying.
In short, to solve your issue you need to use ".open" when selecting the class. I would also suggest using the .exists() check on the class instead of ".length()" and then you can use ".toBeTruthy()" as well.
You could look into improve writing your tests in a Jest/Enzyme combined format as well:
import { shallow } from 'enzyme';
import { FunctionComponent } from './FunctionComponent.jsx';
jest.useFakeTimers();
describe('<FunctionCompnent />', () => {
const mockProps = { prop1: mockProp1, prop2: mockProp2, funcProp3: jest.fn() };
const wrapper = shallow(<FunctionComponent {...mockProps} />);
afterEach(() => {
jest.advanceTimersByTime(2000);
});
afterAll(() => {
jest.useRealTimers();
});
it('should render as closed initially', () => {
expect(wrapper.find('.close').exists()).toBeTruthy();
// you could also add the check for falsy of open if you wanted
// expect(wrapper.find('.open').exists()).toBeFalsy();
});
it('should change to open after 2 seconds (or more)', () => {
expect(wrapper.find('.open').exists()).toBeTruthy();
// you could also add the check for falsy of close if you wanted
// expect(wrapper.find('.close').exists()).toBeFalsy();
});
});
EDIT: Sorry realised I wrote the test backwards after checking your code again, they should be fixed now.

How to simulate a click on a Font Awesome Icon using Jest?

I'm testing through Jest and am trying to test the click on a Font Awesome Icon. I've tried different ways to find the 'node', but I get the error that "Method “simulate” is meant to be run on 1 node. 0 found instead." Any insight would be helpful.
The error I get when I try different inputs to component.find() is: Method “simulate” is meant to be run on 1 node. 0 found instead.
StringEditor
const clearInput = () => {
onRemove()
}
render (
...
<FontAwesomeIcon icon={['fal', 'times-circle']} className="clear-button"
onClick={clearInput} /> : null`
)
onRemove is a callback function.
it('should call clearInput thus onRemove', () =>{
const onRemove= jest.fn()
const component = mount(<StringEditor {...defaultProps} onRemove={onRemove} />)
component.find('<dont know what to put>').simulate('click')
expect(saveValueFn).toBeCalled()
})
Try this:
it('should call clearInput thus onRemove', () =>{
const onRemove= jest.fn()
const component = mount(<StringEditor {...defaultProps} onRemove={onRemove} />)
component.find({ className: "clear-button" }).simulate('click');
expect(clearInput).toHaveBeenCalled();
})
You can use the Object Property Selector: https://airbnb.io/enzyme/docs/api/selector.html#4-object-property-selector
Or a React Component Constructor: https://airbnb.io/enzyme/docs/api/selector.html#2-a-react-component-constructor

Resources