Struggling to mock custom react hook - reactjs

I'm wondering if anyone can help me with where I'm going wrong here...
Trying to mock a custom hook but jest/enzyme is not recording any calls to the function.
My test:
const mockHandleEditProtection = jest.fn();
const mockUseEditProtection = jest.fn(() => [null, mockHandleEditProtection]);
jest.mock('../../common/useEditProtection', () => ({
__esModule: true,
default: () => mockUseEditProtection,
}));
describe('my test', () => {
const wrapper = mount(<AComponent proposal={mockProposalDataWithEnrichedValues}/>)
it('should call useEditProtection with proposal object', () => {
expect(mockUseEditProtection).toBeCalledWith(mockProposalDataWithEnrichedValues);
});
it('should call edit protection when edit button is clicked', () => {
wrapper.find(TableCell).at(4).find(Button).at(0).simulate('click');
expect(mockHandleEditProtection).toBeCalled();
});
})
A basic example of how the hook is used in a component...
const AComponent = ({proposal}) => {
const [EditWarning, editProtection] = useEditProtection(proposal);
return <div>
{EditWarning}
<button onClick={editProtection}>Edit</button>
</div>
};
I am very confused as to why it isn't working so any pointers are appreciated!

Related

Mock a module that returns a function and test that this function was called

I want to test a React component that, internally, uses a custom hook (Jest used). I successfully mock this hook but I can't find a way to test the calls on the functions that this hook returns.
Mocked hook
const useAutocomplete = () => {
return {
setQuery: () => {}
}
}
React component
import useAutocomplete from "#/hooks/useAutocomplete";
const MyComponent = () => {
const { setQuery } = useAutocomplete();
useEffect(() => {
setQuery({});
}, [])
...
}
Test
jest.mock("#/hooks/useAutocomplete");
it("sets the query with an empty object", () => {
render(<MyComponent />);
// I want to check the calls to setQuery here
// e.g. mockedSetQuery.mock.calls
});
CURRENT SOLUTION
I currently made the useAutocomplete hook an external dependency:
import useAutocomplete from "#/hooks/useAutocomplete";
const MyComponent = ({ autocompleteHook }) => {
const { setQuery } = autocompleteHook();
useEffect(() => {
setQuery({});
}, [])
...
}
MyConsole.defaultProps = {
autocompleteHook: useAutocomplete
}
And then I test like this:
const mockedSetQuery = jest.fn(() => {});
const useAutocomplete = () => ({
setQuery: mockedSetQuery,
});
it("Has access to mockedSetQuery", () => {
render(<MyComponent autocompleteHook={useAutocomplete} />);
// Do something
expect(mockedSetQuery.mock.calls.length).toBe(1);
})
You can mock the useAutocomplete's setQuery method to validate if it's invoked.
jest.mock("#/hooks/useAutocomplete");
it("sets the query with an empty object", () => {
const useAutocompleteMock = jest.requireMock("#/hooks/useAutocomplete");
const setQueryMock = jest.fn();
useAutocompleteMock.setQuery = setQueryMock;
render(<MyComponent />);
// The mock function is called twice
expect(setQueryMock.mock.calls.length).toBe(1);
});

How can I mock an imported React hook/module and test that it's being called properly on different test cases using Jest

I need to test the following component that consumes a custom hook of mine.
import { useMyHook } from 'hooks/useMyHook';
const MyComponent = () => {
const myHookObj = useMyHook();
const handler = () => {
myHookObj.myMethod(someValue)
}
return(
<button onClick={handler}>MyButton</button>
);
};
This is my test file:
jest.mock('hooks/useMyHook', () => {
return {
useMyHook: () => {
return {
myMethod: jest.fn(),
};
},
};
});
describe('<MyComponent />', () => {
it('calls the hook method when button is clicked', async () => {
render(<MyComponent {...props} />);
const button = screen.getByText('MyButton');
userEvent.click(button);
// Here I need to check that the `useMyHook.method`
// was called with some `value`
// How can I do this?
});
});
I need to check that the useMyHook.method was called with some value.
I also want to test it from multiple it cases and it might be called with different values on each test.
How can I do this?
This is how I was able to do it:
import { useMyHook } from 'hooks/useMyHook';
// Mock custom hook that it's used by the component
jest.mock('hooks/useMyHook', () => {
return {
useMyHook: jest.fn(),
};
});
// Mock the implementation of the `myMethod` method of the hook
// that is used by the Component
const myMethod = jest.fn();
(useMyHook as ReturnType<typeof jest.fn>).mockImplementation(() => {
return {
myMethod: myMethod,
};
});
// Reset mock state before each test
// Note: is needs to reset the mock call count
beforeEach(() => {
myMethod.mockReset();
});
Then, on the it clauses, I'm able to:
it (`does whatever`, async () => {
expect(myMethod).toHaveBeenCalledTimes(1);
expect(myMethod).toHaveBeenLastCalledWith(someValue);
});

mock useDispatch followed by .then() with jest inside functional component

My scenario is just one step ahead of this existing question in stackoverflow
I have dispatch fn but with .then() followed by.
Component:
const Testing = props => {
const [counterData, setCounterData] = useState(0)
const resData = useSelector(state => state.test)
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchTestinData())
.then(res => {
console.log(res);
setCounterData(prev => prev + 1)
})
}, [])
return <div>
Testing component
<div>
Result - {resData.title}
</div>
<div>
Counter - {counterData}
</div>
</div>
}
Test file:
// const mockDispatch = jest.fn().mockResolvedValueOnce({json : async () => []})
const mockDispatch = jest.fn().mockImplementation(() =>
Priomise.resolve({title:'tets'}))
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useDispatch: () => mockDispatch
}))
describe('<Testing />', function () {
const getComponent = (state) => <Provider store={store}>
<Testing />
</Provider>
it('testing success api', () => {
// window.fetch = jest.fn().mockResolvedValueOnce({title: 'testing title'})
render(getComponent())
screen.debug()
// expect(mockDispatch).toBeCalledTimes(1)
})
})
if am using just jest.fn() getting same error as well as with mock implemntaion.
Error screenshot
Something am missing in mock fn implementaion.
Plase help. Searched a lot but no luck.
Apparently Jest docs are a bit misleading about the possibility to use previously defined variables in a mock module factory: that is just not possible.
So the solution to your issue is just to move your mockDispatch implementation inside the module factory:
jest.mock('react-redux',
() => ({
...jest.requireActual('react-redux'),
useDispatch: () => jest.fn().mockImplementation(() =>
Promise.resolve({ title: 'test' }))
})
)

How to test with Jest whether action created by action creator (and not passed as prop) fires?

I try to test whether a given action is really fired in a component. It could be easy if the callback with action was delivered as a prop, but it's not clear for me how to do it in my case. Here is the component:
export const Modal = (props: appendProps): JSX.Element => {
const { items, activeScope } = props;
const primary = items[0] === activeScope;
const { closeInput, appendItem } = useDispatchAction();
{...}
<Button
variant="contained"
size="large"
className="modal-content__button"
color="secondary"
onClick={() => {
closeInput();
}}
>
Zamknij
</Button>
useDispatchAction is below:
import { useDispatch } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../redux';
const useDispatchAction = () => {
const dispatch = useDispatch();
return bindActionCreators(actionCreators, dispatch);
};
export default useDispatchAction;
And test itself below
describe('Test button actions', () => {
const closeInput = jest.fn();
jest.mock('../../src/hooks/useDispatchAction', () => ({closeInput}));
beforeEach(() => {
render(<Modal />);
});
afterEach(() => cleanup());
test('component fires closeInput', () => {
const closeButton = screen.getByRole('button', { name: 'Zamknij' });
expect(closeButton).toBeInTheDocument();
userEvent.click(closeButton);
expect(closeInput).toBeCalled();
});
});
but this keep telling me that function has been called 0 times and not 1 at least
When you jest.mock, you get a function that returns undefined by default. So, if useDispatchAction returns undefined, you can't go const { closeInput, appendItem } = useDispatchAction();, because you cannot destructure from undefined.
However, you can supply a second argument to jest.mock:
const closeInput = jest.fn()
jest.mock('../../src/hooks/useDispatchAction', () => ({ closeInput }))
Now you've mocked useDispatchAction to return something that looks more like your original function, except that the action returns something you can assert was called.
You can test it like this:
expect(closeInput).toHaveBeenCalled()
I think this should work, but I haven't tested this particular code.
What finally works is:
const actions = {
closeInput: jest.fn(),
appendItem: jest.fn(),
};
jest.mock('../../src/hooks/useDispatchAction', () => () => actions);
describe('Given Modal component', () => {
describe('when "Zamknij" button is clicked', () => {
it('should call closeInput function', async () => {
actions.closeInput.mockClear();
const { findByText } = render(<Modal />);
const closeButton = await findByText('Zamknij');
fireEvent.click(closeButton);
expect(actions.closeInput).toHaveBeenCalled();
});
});
});

Mocked useHistory is not called in async event handler

Summary
I'm writing test code for my react app, but somehow, it always fails.
My app code is very simple, there is only one button, and if it's clicked, a function handleSubmit is fired.
What the handler does are
Fetching data from backend(This is async function)
Move to /complete page.
What I did
I mocked the function fetching data from API in test code
I mocked the useHistory in test code
Note
I realized that if the line that is fetching data from API is commented out, the test will pass.
Code
My main app code
import { useFetchDataFromAPI } from '#/usecase/useFetchDataFromAPI';
:
const { fetchDataFromAPI } = useFetchDataFromAPI();
:
const handleSubmit = async () => {
// If the line below is not commented out, test will fail
// const { id } = await fetchDataFromAPI();
history.push(`/complete`);
};
return (
<>
<button onClick={handleSubmit}>Button</button>
</>
My test code
:
jest.mock('#/usecase/useFetchDataFromAPI', () => ({
useFetchDataFromAPI: () => {
return { fetchDataFromAPI: jest.fn((): number => {
return 1;
})}
}
}));
const mockHistoryPush = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom') as any,
useHistory: () => ({
push: mockHistoryPush,
}),
}));
:
const renderApplicationWithRouterHistory = () => {
const history = createMemoryHistory();
const wrapper = render(
<Router history={history}>
<Application />
</Router>
);
return { ...wrapper, history };
};
:
describe('Test onClick handler', async () => {
test('Submit', () => {
const { getByText, getByRole } = renderApplication();
const elementSubmit = getByText('Button');
expect(elementSubmit).toBeInTheDocument();
fireEvent.click(elementSubmit);
expect(mockHistoryPush).toHaveBeenCalled();
});
});
Your event handler is called on button click, but because it is asynchronous, its result is not evaluated until after your test runs. In this particular case, you don't need the async behavior, so just use:
const handleSubmit = () => {
history.push(`/complete`)
}
testing-library provides a method waitFor for this if your handler did need to await something:
await waitFor(() => expect(mockHistoryPush).toHaveBeenCalled())
Though another simple way is to simply await a promise in your test so that the expectation is delayed by a tick:
fireEvent.click(elementSubmit);
await Promise.resolve();
expect(mockHistoryPush).toHaveBeenCalled();

Resources