How to partially mock a custom react hook with Jest? - reactjs

I would like to mock some of my custom React hook return value with Jest.
Here is an example of what i'm trying to do :
export default function useSongPlayer() {
const playSongA = () => {...}
const playSongB = () => {...}
const playSongC = () => {...}
return {
playSongA,
playSongB,
playSongC,
}
}
Now in my test, I would like to mock only playSongA and keep the other functions real implementations. I tried several approachs, including someting with jest.requireActual('hooks/useSongPlayer') but it seems that playSongA is successfully mocked while the others are just undefined.
With something like this, i'm pretty sure to achieve what i want
export function playSongA() {...}
export function playSongB() {...}
export function playSongC() {...}
The problem seems to be the module I try to mock is a function, and I want to partially mock the result of this function.

This is possible if you create a mock that returns the original, but also intercepts the function calls and replaces one of the values.
You can do this without a Proxy, but I choose to demonstrate that as it's the most complete method to intercept any use of the actual hook.
hooks/__mocks__/useSongPlayer.js
// Get the actual hook
const useSongPlayer = jest.requireActual('hooks/useSongPlayer');
// Proxy the original hook so everything passes through except our mocked function call
const proxiedUseSongPlayer = new Proxy(useSongPlayer, {
apply(target, thisArg, argumentsList) {
const realResult = Reflect.apply(target, thisArg, argumentsList);
return {
// Return the result of the real function
...realResult,
// Override a single function with a mocked version
playSongA: jest.fn(),
};
}
});
export default proxiedUseSongPlayer;

Related

Is it possible to mock functions outside of the test file for multiple tests to use?

Thanks in advance
Issue
I have some functions that need to be mocked in every test file. However, only in some of those files, do I actually care to evaluate or test those mock functions.
For example, let's say, that when my app renders, it immediately fetches some data In A.test.tsx I want to mock that fetch and verify that it was called.
But, in B.test.tsx, I still need to mock that fetch, but I don't care to verify that it was called.
Goal
I would like the setup to look something like this:
setup.ts
import * as SomeAPI from 'api';
const setup = () => {
jest.spyOn(SomeAPI, 'fetchData');
return { SomeAPI };
};
export default setup;
A.test.tsx
const { SomeAPI } = setup();
beforeEach(() => {
jest.clearAllMocks();
});
test('data should be fetched when app renders', async () => {
RenderWithProviders(<App />);
expect(SomeAPI.fetchData).toHaveBeenCalled();
});
B.test.tsx
setup(); // Don't destructure because I don't care to test the mock
beforeEach(() => {
jest.clearAllMocks();
});
test('test some other stuff', async () => {
// In this case, the fetchData still needs to be mocked<br>
// because the rest of the app depends on it
RenderWithProviders(<App />);
expect(someElement).toBeInTheDocument();
});
My Current Problem
My problem is that while I'm trying to attempt this way of returning mocked functions... If that mocked function is used more than once in the same test file, the jest.clearAllMocks() seems not to have an effect. I assume because the setup is happening outside of the test?
Is this possible to setup mocks outside of the test and only destructure them when needed?

How to properly mock a function returned by a React hook, inside a module that contains multiple hooks?

I have a custom React hook file that looks like this (contains multiple hook functions), and I want to mock the handleSubmit that is returned by only one of the hook
// useHook.js
export function useFirstHook() {
// calling useStates...
const [ state, setState ] = useState('')
const handleSubmit = () => {}
return [ state, {handleSubmit}]
}
export function useSecondHook() { ... }
export function useThirdHook() { ... }
// Component.js
import {useFirstHook} from './useHooks'
export function Component () {
const [ state, {handleSubmit} ] = useFirstHook()
return <button onClick={handleSubmit}>Click Me</button>
}
I want to be able to test my button works, and that it will invoke handleSubmit when clicked. This is what I have so far.
//tests.js
let submitMk = jest.fn()
jest.mock('../../usehooks', () => ({
useFirstHook: () => {
return [ '', {
handleSubmit: submitMk
}]
}
}))
test('onClick should be triggered when button is clicked', () => {
render(<Component />)
screen.getByText('Click Me').click()
expect(submitMk.mock.calls).toHaveLength(1)
})
But it will fail saying that the mocked function is not called. Which makes me think there must have been a mistake in the mocking process.
What is the proper way to mock handleSubmit?
The example in question should work in theory. The issue I had, (that was completely left out from the question) and that I want to remind everyone who is having issues with jest.mock not mocking modules correctly is that this needs to be done at the most top level (module level). Putting the jest.mock inside a beforeAll() block, or a beforeEach() will give issues.
On a side note if you are trying to mock a custom hook, placing the jest.mock on the top level, means that the hook would be called at the top/module level, so no further issues there :)

Custom hook returning a promis

Is it possible to return a promise from a custom react hook and await for it? Like:
const getData = useMyCustomGetDataHook();
const fetchData = async () => {
await getData({...props});
// ... do stuff..
}
In reality, there is no such thing as a custom React Hook - it is simply a fancy name for a function having a name that starts with use. Thus, any function can be a custom React Hook - including a function that returns a Promise that you can await.
However the real problem is that your async function won't be usable inside a component. render() is not async and you cannot await. In fact a custom React Hook is more about the programming paradigm that the function itself.
What you need to do in your case is to launch your async operation either from useEffect() - if it does not require that DOM is created, or from useLayoutEffect() - if it requires it - and then trigger a state change in the .then() handler. This is the React way of doing this.
You can't directly await for a promise while rendering (aside from the experimental suspense stuff).
You could use e.g. useSWR() as a wrapper for a promise-returning function (which, wink wink, could be e.g. something that uses fetch()...).
async function getData(x) {
return x * 4;
}
function useData(x) {
return useSWR(['myData', x], (_, x) => getData(x));
}
function MyComponent() {
const dataSWR = useData(8);
if(dataSWR.data === undefined) {
return <>Loading...</>;
}
return <>{dataSWR.data}</>;
}

How to test internal functions using react hooks testing library?

I have a custom hook that I am trying to write tests for using the react hooks testing library package and I would like to know how I can test internal functions that are not returned in the custom hook but are used within other functions.
const customHook = () => {
const [count, setCount] = React.useState(0);
const doSomeThing = () => {
..code
}
const increment = () => {
doSomeThing(); //Would like to make assertations on this
setCount((x) => x + 1 );
}
return { count, increment }
}
export default customHook;
test
it('Should call increment', () => {
const { result } = renderHook(() => useCustomHook())
act(() => {
result.current.increment();
});
expect(doSomeThing).toHaveBeenCalled(); //end result of what I would like help on
});
How can I write a test to see if doSomething has been called/used?
You can't. It's entirely internal to that hook, and no handle is provided to get at that function. So you can't mock it, and you can't call it directly. It's impossible to test only the doSomething function as you have it.
And more to the point, you shouldn't. You don't want to test at that level. This is a private implementation detail. You should test the public interface of your hook. That means test the arguments, the return values, and how calling functions the hook returns affect the next returned values.
Tests shouldn't care how a function does its job. Tests should only care that the function does its job correctly.
That means your test can only verify what doSomething does, and not whether it's been called. And as of right now, it doesn't do anything at all, so there's nothing to test.

React and Jest: How to mock implementation of member function

I'm doing some api fetching in my component. When doing unit testing, I'd like to mock the implementation of certain member functions:
//component.js
class Foo extends Component {
prepareData() {
getSthFromApi().then(getMoreFromApi).then(val=>this.setState({val}));
}
componentDidMount() {
this.prepareData();
}
}
//test.js
//What should this be?
Foo.prepareData = jest.fn().mockImplementation(() => {
this.setState({val:1});
})
const comp = shallow(<Foo />);
How should I do it?
You shouldn't mock your member function, instead you can mock getSthFromApi function and test both componentDidmount and prepareData together.
import { getSthFromApi } from "some-module";
jest.mock("some-module");
// in your test
getSthFromApi.resolves(1);
// here you can expect val to be 1 in your state.
The problem with this code is that Foo.prepareData is static method, while prepareData is instance method.
Since prepareData is prototype method, it can be mocked on class prototype; this is one of the reasons why prototype methods are preferable:
jest
.spyOn(Foo.prototype, 'prepareData')
.mockImplementation(function () {
this.setState({val:1});
});
Notice that mockImplementation should be provided with regular function because it doesn't need lexical this.

Resources