RTL: Mock a custom React hook only for selected unit tests - reactjs

I have a custom hook called useInitialSetup that returns a key-value dictionary object.
It's being consumed in App.tsx.
In App.test.tsx, I have a suite of unit tests written using Jest and React Testing Library.
Is it possible to mock useInitialSetup in only a selection of unit tests, but not all?
Here's what I have tried in App.test.tsx:
jest.mock('../../hooks/useInitialSetup', () => {
return jest.fn(() => ({
loading: mockLoading,
suspendedTrial: mockSuspendedTrial,
maxTrials: mockMaxTrials,
reachedMaxTrials: mockReachedMaxTrials,
}));
});
it('Unit test that uses a mocked version of useInitialSetup', () => {
render(<App/>)
...
})
it('Unit test that uses the real implementation of useInitialSetup', () => {
jest.dontMock('../../hooks/useInitialSetup')
render(<App/>)
...
})
Unit test #2 still uses the mocked version of useInitialSetup.

Here's what I ended up doing. This lets me mock the output of my useInitialSetup custom hook for this one selected unit test. Any subsequent unit tests will use the default implementation of this hook.
import * as useInitialSetup from '../../hooks/useInitialSetup';
it('Test', () => {
const spy = jest.spyOn(useInitialSetup, 'default');
spy.mockReturnValue({
loading: false,
reachedMaxTrials: true,
errorCheckingCanCreateInstance: false,
errorFetchingUser: false,
// #ts-ignore
suspendedTrial: mockSuspendedTrial,
resetChecks: () => {},
maxTrials: 1,
});
render(setupComponent());
expect(true).toBe(true) // or whatever assertion you want
spy.mockRestore();
})

Related

How to override a global jest mock in a specific test?

I am using #testing-library/react v13.4.0 with jest v29.
I am mocking the services/ledger.ts module with services/__mocks__/ledger.ts. I'll focus on one method in this file, which is defined as follows:
// services/__mocks__/ledger.ts
export const getAccounts = jest.fn(() => mockGetAccounts);
I have a setupTests.tsx that runs as setupFilesAfterEnv with the following code:
// setupTests.tsx
jest.mock('services/ledger');
This works nicely as a global mock, but for certain tests I want to override this mock. In a test file, I have the following code:
// Account.test.tsx
import { mockGbpAccount } from 'testing';
import { getAccounts } from 'services/ledger';
...
describe('components: Account', () => {
// some other tests that don't have any mocks
it('should disable GBP withdraw button if user has a balance of 0', async () => {
(getAccounts as jest.Mock).mockImplementation(() => ({
account: [
mockBtcAccount,
{ ...mockGbpAccount, balance: { formatted: '0' } },
],
}));
await renderWithProviders(<Account accountId={mockGbpAccount.id} />);
await waitFor(() => {
expect(screen.getByTestId('withdraw-cta')).toBeDisabled();
});
});
});
The resulting balance is not 0 as expected, but the default balance I set in the global mock.
Weirdly, when I run this test alone with -t, and not the entire suite, it passes. The specific test also passes when it's the first test in the suite, but not the second+ test.
I have tried different ways of mocking globally, and mocking in the specific suite, but it always uses the global mock value.
How can I override a global mock for specific tests?

Mock custom hook from 3rd party library in Jest

I am using a custom hook from 3rd party library in my React project:
import { useProductData } from '#third/prod-data-component';
const ProductRow: React.FC<MyProduct> = ({ product }) => {
// using the custom hook here
const productData = useProductData();
})
The signature of that hook function is:
export declare const useProductData: () => string | undefined;
In my jest test, I would like to mock the returned value of the hook, I tried:
it('should show correct product data', ()=>{
jest.mock('#third/prod-data-component', () => {
return { useProductData: jest.fn(()=>'foo')}
});
...
...
})
When I run test, the above mock doesn't take any effect.
How to mock the return value of custom hook that is from a 3rd party library?
==== UPDATE ====
I also tried this:
jest.mock('#third/prod-data-component', () => {
const lib = jest.requireActual('#third/prod-data-component');
return {...lib, useProductData: () => 'foo'}
});
But does't work either.
can you try this
import {useProductData} from '#third/prod-data-component'
jest.mock('#third/prod-data-component');
(useProductData as jest.Mock).mockImplementation(() => {mockKey: 'mockData'})
describe('test scenario', () => {
it('should show correct product data', () => {
// your assertions
})
})
Reading your example, you may be missing the actual module import.
However, have you tried the full module mocking?
import moduleMock from '#third/prod-data-component'
jest.mock('#third/prod-data-component')
describe('test scenario', () => {
it('should show correct product data', () => {
moduleMock.useProductData.mockReturnValue(...)
})
})
EDIT
I missed the Typescript part. You need to wrap the actual module type definitions with Jest's own type definitions.
I solved this problem in the past in the following way, using jest.mocked function:
import prod_data_component_module from '#third/prod-data-component'
jest.mock('#third/prod-data-component')
describe('Your test case scenario', () => {
const mock = jest.mocked(prod_data_component_module, { shallow: true })
it('Your test here', async () => {
mock.useProductData.mockReturnValue('...')
// test logic here
})
})
mock is a jest wrapper of the original module. It is of type jest.MockedFn<T>, so it contains actual exported types/functions and also jest mocks.

Change API response in the middle of a test

I want to test a component that shows a loading screen and then when the API sends the data, it shows the data accordingly.
I know how to do this in different test suites, but I want to do it in the same test — the API might change additional data over time and I want to test the component going from one state to the other.
For context, I'm using useSWR, but I mock the entire hook that calls it (as seen below).
The file below explains it as well:
First, I import and mock the hook
Then, I render the component (with the default mocked value) and assert that the loading is there.
I change the response of this mock hook to something different
The second assertion group should pass but it doesn't. The mock isn't even called again (I console'd it).
import { useCustomHook } from "./useCustomHook"
jest.mock(
"useCustomHook",
() => ({
useCustomHook: jest.fn(() => ({
isLoading: true,
response: undefined,
})),
}),
)
const mockuseCustomHook = useCustomHook as jest.Mock
describe("Component", () => {
it('Should render properly', () => {
render(<Component />)
expect(screen.getByText('Loading')).toBeInTheDocument()
mockuseCustomHook.mockImplementationOnce(() => ({
isLoading: false,
response: { data: 'This is the data' },
}))
expect(screen.queryByText('Loading')).not.toBeInTheDocument()
expect(screen.getByText('This is the data')).toBeInTheDocument()
})
})

Testing a component in Next.js with testing-library that relies on tRCP

I was experimenting with tRCP and diligently followed the setup for my Next.js project described in the official docs over here: https://trpc.io/docs/nextjs
However I noticed that a simple component that relies on tRPC such as this
export const Sample = () => {
const { data } = trpc.useQuery(['hello', { text: 'User' }]);
if (data === undefined) {
return <div>Loading...</div>;
}
return <div>{data.greeting}</div>;
};
cannot be properly tested since the following trivial test
describe('Sample', () => {
it('should render successfully', () => {
const { baseElement } = render(<Sample />);
expect(baseElement).toBeTruthy();
});
});
since there is no setup of provider such as the setup with the withTRCP HOC used for the application itself. As such the test fails claiming client (presumably the trcpClient, unlike the queryClient) is undefined.
I'd like to know how to setup the test correctly, in this case providing a correct client, as well as mocking the queries, since I don't have the respective server-side code running while invoking the tests.
Since you are getting undefined for the trpc client implementation, you can try spying on the query call.
import trpc from 'utils/trpc'; // This is the client implementation
describe('Sample', () => {
it('should render successfully', () => {
jest.spyOn(trpc, 'useQuery')
.mockReturnValue({ greeting: "Greeting" });
const { baseElement } = render(<Sample />);
expect(baseElement).toBeTruthy();
});
});
This is also possible with the mutations but you need to provide a mock implementation for the useMutation response for mutate property.

Unit tests on react with Jest: how to mock an action from different feature?

I am currently doing unit tests on with Jest on one of my react services (using redux-saga). These services call API, selectors and actions which are outside from the other API, selectors, and actions specific to this service.
So to mock these outside API, selectors, and actions, I used jest to mock them in my test file like this:
// API mock example
jest.mock('../otherFeatureService/api'), () => {
return { myApi: jest.fn() };
}
// reducer mock example
jest.mock('../otherFeatureService/reducer'), () => {
return { isConnected: (fake) => jest.fn(fake) };
}
// API action example
jest.mock('../otherFeatureService/actions'), () => {
return { myAction: () => jest.fn() };
}
Everything works fine in my tests, except the mock actions.
However, I got the below message when I tried to compare my expected result with the one I got for my mock actions:
expect(received).toEqual(expected)
Expected value to equal:
{"##redux-saga/IO": true, "PUT": {"action": [Function mockConstructor], "channel": null}}
Received:
{"##redux-saga/IO": true, "PUT": {"action": [Function mockConstructor], "channel": null}}
Difference:
Compared values have no visual difference.
Do you have any ideas about why it doesn't work only for actions? (I am using sagaHelper from 'redux-saga-testing' for my tests)
Thanks.
printfx000.
You're experiencing this issue because jest cannot make the difference between 2 anonymous function.
In order to fix that, you'll need to provide a named function to you mock.
Because jest hoist mock declarations, you'll need to declare your named function inside your mock.
jest.mock('../selectors', () => {
function mockedMakeSelectCoverageResult() {}
return ({
makeSelectCoverageResult: () => mockedMakeSelectCoverageResult,
});
});
expect(descriptor).toEqual(select(makeSelectCoverageResult()));

Resources