How to test internal functions using react hooks testing library? - reactjs

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.

Related

Why "React hook is called in a function" while exporting simple single function that accesses a useAtom state?

I have this simple function that needs to set a state from jotai. I would like this function to have it's own seperate file or be cleaned up somewhere, since it will be reused. I'm new to React and come from Angular. It's kind of a function in a service Angular wise.
How would you solve this properly in React?
Code:
export const setMetamaskWallet = (): void => {
const [, setWeb3] = useAtom(web3Atom);
const [, setLoading] = useAtom(loadingAtom);
const [wallet, setWallet] = useAtom(walletAtom);
setWeb3(
new Web3(window.ethereum),
)
//Todo: Create check if metamask is in browser, otherwise throw error
const setAccount = async () => {
setLoading(true);
const accounts = await window.ethereum.request(
{
method: 'eth_requestAccounts'
},
);
setWallet(
accounts[0],
);
setLoading(false);
}
if (!wallet) {
setAccount();
}
}
Hooks can only be called in React functional components or other hooks. Here it appears you have called it in a typical function, hence the error. You could package this functionality in a custom hook. It may however be most appropriate to keep this function as it is, and instead of calling hooks within it, pass the relevant data into the function as parameters.

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 partially mock a custom react hook with Jest?

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;

What is the best case for defining function without any dependencies in React functional component?

I'm always confused when define function in React functional component without any dependencies.
For example,
const HelloWorld = () => {
const getHelloWorldText = useCallback(() => {
return "HelloWorld"
}, [])
return <>{getHelloWorldText()}</>
}
It's a simplified component, just imagine HelloWorld is updated every 5 seconds. useCallback is needed for optimization preventing redefine.
However, it's possible like below.
const getHelloWorldText = () => {
return "HelloWorld"
}
const HelloWorld = () => {
return <>{getHelloWorldText()}</>
}
If there's no dependency, above example is more costless than useCallback example, but it's not a pure function.
Which do you prefer? And why?
Prematurely using useCallback to optimise can often lead to worse performance.
It is recommended to only start using it when you have a measurable performance issue, so you can test whether the overhead of useCallback is worth the memoization it brings.
Also, in your example code, because the dependency array at the end is empty, it will always return the same value, as useCallback works by only re-running the given function when a variable in that array has changed.
This is what I would do:
const HelloWorld = () => {
const getHelloWorldText = () => {
return "HelloWorld"
}
return <>{getHelloWorldText()}</>
}

Why does useCallback with an empty dependency array not return the same function?

I'm trying to write a custom React hook that returns functions which maintain reference equality with every invocation of the hook. Meaning that exactly the same function is returned from the hook every single time, such that comparing them with === returns true.
I was under the impression that the useCallback hook was the way to accomplish that:
function useCorrectCallback() {
const [count, setCount] = useState(0)
let increment = () => setCount(c => c + 1)
let memoized = useCallback(increment, [])
return {
count,
increment: memoized
}
}
So when this hook is called, I believe that the increment property of the return value should be the exact same function every time. It should be the value of the inline function the first time the hook was run, and not change on subsequent executions of the hook because I have specified no dependencies to useCallback with [].
But that's not what seems to happen! I've written a test project to demonstrate this problem. Here's the crux of that test:
function validateFunctionEquality(hookResult) {
// Store the last result of running the hook
let stored = { ...hookResult }
// Force a re-render to run the hook again
expect(hookResult.count).toEqual(0)
act(hookResult.increment)
expect(hookResult.count).toEqual(1)
// Compare the previous results to the current results
expect(hookResult.constant).toEqual(stored.constant)
expect(hookResult.increment).toEqual(stored.increment)
}
Just to validate that my tests are accurate, I also wrote a hook which just uses a globally scoped object to maintain "memory" instead of trying to ask React to do it with useCallback. This hook passes the tests:
let memoryHack = {}
function useMemoryHack() {
const [count, setCount] = useState(0)
let increment = () => setCount(c => c + 1)
memoryHack.increment = memoryHack.increment || increment
return {
count,
increment: memoryHack.increment
}
}
Am I using useCallback incorrectly? Why does useCorrectCallback return a different function in its increment property on subsequent executions?
I suspect is an issue with enzyme's shallow. Probably related to https://github.com/airbnb/enzyme/issues/2086
By using mount, your test pass as it is:
act(() => {
componentMount = mount( // used mount instead of shallow
<Component>
{hookValues => {
copyHookValues(hookResult, hookValues)
return null
}}
</Component>
)
})

Resources