You don't want to spy on handleClick. Instead, you want to test what effect handleClick had on the component. Your handleClick callback is an implementation detail, and as a general rule, you don't test that.
You don't want to spy on handleClick. Instead, you want to test what effect handleClick had on the component. Your handleClick callback is an implementation detail, and as a general rule, you don't test that.
You are trying to spy on handleClick as if it was a property of Foo, which is a function.
I can move handleClick to another file and import it both in the component and the test:
handler.js
export const handleClick = () => {}
Component
import { handleClick } from './handler.js'
const Foo = () => {
return <div>
<button data-testid="button" onClick={handleClick}>Continue</button>
</div>
}
Test case
import * as handlers from './handler.js';
it("should handle continue button click", async () => {
const { getByTestId } = render(<Foo />);
const button = getByTestId("button");
const spy = jest.spyOn(handlers, "handleClick");
act(() => {
fireEvent.click(button);
});
expect(spy).toHaveBeenCalled();
});
jestjs - Using react-hooks-testing-library with jest.spyOn - spy is not called - Stack Overflow
unit testing - Spy on function called from a React Functional Component - Stack Overflow
reactjs - Unable to spy on third party library function in test - Stack Overflow
Mocking Fat Arrow Component Methods
Please, if you have a better answer left it here. After a search for different approaches, I realized another way to test it.
First, I attached the current state to the value attribute of my search field.
This way, I can check if the attribute value of my search field changes accordingly
import { render, fireEvent } from '@testing-library/react';
import Search from '.';
describe('when type a valid term', () => {
it('update the state', () => {
const { getByPlaceholderText } = render(<Search />);
const inputField = getByPlaceholderText(/search/i);
fireEvent.change(inputField, { target: { value: 'moon' } });
expect(inputField).toHaveValue('moon');
});
});
It's possible to write a snapshot test as well.
Your current approach is closer to how tests were done in enzyme (test implementation details). I'd recommend checking the documentation of testing-library https://testing-library.com/docs/intro#the-problem
You should be testing as if your final user was interacting with your app. A more appropriate test might be:
describe('when no search is present', () => {
it('displays placeholder text', () => {
const { getByPlaceholderText } = render(<Search />;
const searchInput = getByPlaceholderText(/search/i)
expect(searchInput).toBeInTheDocument()
});
});
testing this way will give you the confidence you need and also code coverage
This line:
import * as myModule from './myModule.js'
...imports the module bindings for myModule.js into myModule.
Then this line:
const spy = jest.spyOn(myModule, 'bar');
...wraps the module export for bar in a spy...
...but the spy never gets called because useAHook doesn't call the module export for bar, it just calls bar directly.
If you modify useAHook to call the module export for bar then the spy will get called.
There are a couple of ways to do that.
You can move bar into its own module...
...or you can import the module bindings for myModule.js so you can call the module export for bar:
import { useCallback } from 'react';
import * as myModule from './myModule'; // <= import the module bindings
export const useAHook = (arg1, arg2) => {
const foo = useCallback(() => {
myModule.bar(arg1, arg2); // <= call the module export for bar
}, [arg1, arg2]);
return foo;
}
export const bar = (a, b) => {
//does some stuff with a and b
}
I managed to spy on the hook export method (using import * as), then inject a mock function into the implementation:
import * as useThingHook from 'useThing'
it('a test', () => {
const methodMock = jest.fn()
jest.spyOn(useThingHook, 'usething').mockImplementation(() => ({
method: methodMock
}))
act()
expect(methodMock).toHaveBeenCalled()
})