Unit testing useLoaderData() react-router v6 loader functions - reactjs

A project uses react-router v6 and in some components I call useLoaderData(). For instance:
const routes = [
{ path: "widgets",
loader: () => fetchAndGetJSON("/api/widgets"),
element: <ListWidgets/> }
];
function ListWidgets() {
const widgets = useLoaderData();
return <>
<p>Here is a list of {widgets.length} widgets:
<...>
</>;
}
When testing I do not want to execute the fetch, I want to supply the list of widgets in the test.
How can I create a unit test for ListWidgets that uses data that I supply in the test?

I haven't used React Router's data APIs myself, but as I understand it, there are three main alternatives.
Use Jest's standard mocking functionality to mock fetch, mock fetchAndGetJSON, or similar.
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve(FAKE_TEST_DATA),
})
);
beforeEach(() => fetch.mockClear());
Use createMemoryRouter with a testing version of your route that renders <ListWidgets/> with a custom loader that returns the test data that you want.
test(ListWidgets, async () => {
const routes = [{
path: "widgets",
element: <ListWidgets />,
loader: () => FAKE_TEST_DATA,
}];
const router = createMemoryRouter(routes, { initialEntries: ["widgets"] });
render(<RouterProvider router={router} />);
// ...testing...
}
Use Mock Service Worker (msw.js) to create a mock back-end. Functionally, this is pretty similar to mocking fetch yourself, but MSW is very good at streamlining and consolidating things.
const worker = setupWorker(
rest.get('/api/widgets', async (req, res, ctx) => {
const { username } = await req.json();
return res(ctx.json(FAKE_TEST_DATA))
}),
);
worker.start();
I'm a big fan of msw.js, but any of the options should work. (It's something of a trade-off: overriding loader results in more narrowly target unit tests, while msw.js lets you write less invasive tests closer to the integration testing end of the spectrum.)

Related

How to make an automated test which checks that no components in a React component tree re-rendered?

I want to automate the following test scenario:
I render some arbitrary React component tree.
I make some action (scroll some container / click a button / ...)
I assert if any components have re-rendered since taking action 2.
What would be a good way to do this? We currently use Jest, Cypress and react-test-renderer in our project - it would be great to find a way to this using those. But this is not strictly necessary.
I need this to catch improperly memoized useSelector calls high up the component tree, which result in most of the app re-rendering - we keep running into this problem over and over.
As mentioned by #morganney, welldone-software/why-did-you-render has a bunch of Cypress test examples already in the project.
Here is one example:
Cypress.Commands.add('visitAndSpyConsole', (url, cb) => {
const context = {};
cy.visit(url, {
onBeforeLoad: win => {
cy.spy(win.console, 'log');
cy.spy(win.console, 'group');
},
onLoad: win => context.win = win,
});
cy.waitFor(context.win)
.then(() => cb(context.win.console));
});
it('Child of Pure Component', () => {
cy.visitAndSpyConsole('/#childOfPureComponent', console => {
cy.contains('button', 'clicks:').click();
cy.contains('button', 'clicks:').click();
cy.contains('button', 'clicks:').should('contain', '2');
expect(console.group).to.be.calledWithMatches([
{ match: 'PureFather', times: 2 },
{ match: /props.*children\W/, times: 2 },
]);
expect(console.log).to.be.calledWithMatches([
{ match: 'syntax always produces a *NEW* immutable React element', times: 2 },
]);
});
});
If you add more details to the question, I can give you a specific example.
Generally re-render checks'/optimisation's are done at development time using Reacts Profiler API.
https://reactjs.org/docs/profiler.html
this tells you why something re-rendered, times re-rendered.
EDIT#2
I was reading a blog and i found a better way to integrate it into a test.
In my last approach you would have to have a callback to log and a babel-plugin to wrap your desired component for test.
with approach 2 you can just use -
import TestRenderer from 'react-test-renderer';
const testRenderer = TestRenderer.create(
<Link page="https://www.facebook.com/">Facebook</Link>
);
console.log(testRenderer.toJSON());
// { type: 'a',
// props: { href: 'https://www.facebook.com/' },
// children: [ 'Facebook' ] }
it prints out host(in your case react) component tree in json, You can use Jest’s snapshot testing feature to automatically save a copy of the JSON tree and check if anything has changes and compute your rerenders.
I have 2 solutions for your problem.
The first one is implemented with Puppeteer only
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const navigationPromise = page.waitForNavigation();
await page.goto('http://localhost:3000/path-to-your-page')
await page.setViewport({ width: 1276, height: 689 });
await navigationPromise;
// Begin profiling...
await page.tracing.start({ path: 'profiling-results.json' });
// do action
await page.$eval('.class_name:last-child', e => {
e.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'end' });
});
// Stop profliling
await page.tracing.stop();
await browser.close();
})()
The second one is implemented with Puppeteer and the use of React Profiler API directly in your app:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const navigationPromise = page.waitForNavigation();
await page.goto('http://localhost:3000/path-to-your-page')
await page.setViewport({ width: 1276, height: 689 });
await navigationPromise;
page
.on('console', message =>
console.log(`${message.type().substr(0, 3).toUpperCase()} ${message.text()}`))
// do action
await page.$eval('.class_name:last-child or any other CSS selector/id/etc', e => {
e.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'end' });
});
await browser.close();
})()
And for the second solution to work add this code to your App.jsx:
<Profiler
id="profile-all"
onRender={
(id, phase, actualDuration) => {
console.log({id, phase, actualDuration})
}
}>
{/*all of the markdown should go inside*/}
</Profiler>

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.

How to mock a third party React component using Jest?

TLDR; what's the proper way to mock a React component imported from a third-party library?
I'm testing a component called <App/>. It consumes a 3rd part component called <Localize/> provided by a library called localize-toolkit.
I'm having some trouble mocking <Localize/> using Jest.
Here is how I've tried mocking it.
jest.mock('localize-toolkit', () => ({
// Normally you pass in a key that represents the translated caption.
// For the sake of testing, I just want to return the key.
Localize: () => (key:string) => (<span>{key}</span>)
}));
And I've written a unit test for <App/> that looks like this.
it('Test', () => {
const component = render(<App/>);
expect(component).toMatchSnapshot();
}
)
It will pass, however this is the warning message returned.
Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render.
And when I look at the snapshot, I get a series of periods "..." where the localized caption should appear.
Am I not mocking the Localize component properly?
Here's how I ended up doing it.
Note how the third-party component Localize needs to be returned as a function.
jest.mock('localize-toolkit', () => ({
Localize: ({t}) => (<>{t}</>)
}));
and in case there are multiple components, and you only want to mock one of them, you can do this:
jest.mock("localize-toolkit", () => {
const lib = jest.requireActual("localize-toolkit");
return {
...lib,
Localize: ({t}) => (<>{t}</>),
};
});
We can mock the 3rd party library for example in my case i need to mock react-lazyload
Component.tsx
import LazyLoad from 'react-lazyload';
render() {
<LazyLoad><img/></LazyLoad>
}
In jest.config.js
module.exports = {
moduleNameMapper: {
'react-lazyload': '/jest/__mocks__/react-lazyload.js',
}
}
In jest/mocks/react-lazyload.js
import * as React from 'react';
jest.genMockFromModule('react-lazyload');
const LazyLoad = ({children}) => <>{children}</>;
module.exports = { default: LazyLoad };

Jest test case for UseEffect hooks in react JS

I am trying to write the Jest-enzyme test case for useEffect react hooks, and I am really lost, I want to write test case for 2 react hooks, one making the async call and another sorting the data and setting the data using usestate hooks, my file is here.
export const DatasetTable: React.FC<DatasetTableProps> = ({id, dataset, setDataset, datasetError, setDataSetError}) => {
const [sortedDataset, setSortedDataset] = useState<Dataset[]>();
useEffect(() => {
fetchRegistryFunction({
route:`/dataset/report?${constructQueryParams({id})}`,
setData: setDataset,
setError: setDataSetError
})();
}, [id, setDataset, setDataSetError]});
useEffect(() => {
if(dataset) {
const sortedDatasetVal = [...dataset];
sortedDatasetVal.sort(a, b) => {
const dateA: any = new Date(a.date);
const dateA: any = new Date(a.date);
return dataA - dateB;
}
setSortedDataset(sortedDatasetVal);
}
}, [dataset])
return (
<div>
<DatasetTable
origin="Datasets"
tableData={sortedDataset}
displayColumns={datasetColumns}
errorMessage={datasetError}
/>
</div>
);
}
Enzyme isn't the right library for this kind of testing.
https://react-hooks-testing-library.com/ is what you need.
In your case I would extract all the data fetching to a 'custom hook' and then test this independently from your UI presentation layer.
In doing so you have better separation of concerns and your custom hook can be used in other similar react components.
I managed to get enzyme to work with a data fetching useEffect hook. It does however require that you allow your dataFetching functions to be passed as props to the component.
Here's how I would go about testing your component, considering it now accepts fetchRegistryFunction as a prop:
const someDataSet = DataSet[] // mock your response object here.
describe('DatasetTable', () => {
let fetchRegistryFunction;
let wrapper;
beforeEach(async () => {
fetchRegistryFunction = jest.fn()
.mockImplementation(() => Promise.resolve(someDataSet));
await act(async () => {
wrapper = mount(
<DatasetTable
fetchRegistryFunction={fetchRegistryFunction}
// ... other props here
/>,
);
});
// The wrapper.update call changes everything,
// act seems to not automatically update the wrapper,
// which lets you validate your old rendered state
// before updating it.
wrapper.update();
});
afterEach(() => {
wrapper.unmount();
jest.restoreAllMocks();
});
it('should display fetched data', () => {
expect(wrapper.find(DatasetTable).props().tableData)
.toEqual(someDataSet);
});
});
Hope this helps!

Manual mocks with Jest -- I'm not understanding something

I'm learning jest, and trying to do a manual mock for an api, I'm missing something.
I'm mocking an api call to giphy. I'm seeing a lot of different syntaxes for the manual mocks, and unfortunately, they're not making much sense to me right now. I've been trying to code along with https://hackernoon.com/api-testing-with-jest-d1ab74005c0a and https://facebook.github.io/jest/docs/en/tutorial-async.html, but I'm stuck.
I have a component called GifListContainer that displays 3 gifs, there's search functionality, I just want a manual mock with fake data to learn.
I'm using create-react-app, I see a lot of people using isomorphic-fetch and other packages, but since jest is built in can't I do it without adding anything else?
I can't figure out how to manually write the mock, I feel I'm missing something simple. It's testing fine if I don't use the mock (using different testing syntax because I'm not testing the _ mock _ file). Thank you for your time.
The error I'm getting:
● should load gifs
TypeError: GifContainer.api is not a function
at Object.<anonymous>.it (src/Part_2/GifContainer.test.js:10:23)
✕ should load gifs (6ms)
GifListContainer.js
import {gifApi} from './api'
class GifListContainer extends Component {
state = {
gifs: []
};
componentDidMount() {
this.displayGifs('coding');
}
displayGifs = (query) => {
gifApi(query)
.then(res => res.json())
.then(json => {
let firstThreeGifs = json.data.slice(0, 3);
let urls = firstThreeGifs.map(
gif => gif.images.original.url.split("?")[0]
);
this.setState({
gifs: [...urls]
});
});
};
//after this there's a search function and the render and such.
api.js
const urlPartOne = "http://api.giphy.com/v1/gifs/search?q="
const urlPartTwo = "&api_key=UE0dCN2WofIwVF0RPbpHo0Lz0k9VhqdG"
const gifApi = (query) => {
return fetch(urlPartOne + query + urlPartTwo)
}
export {gifApi}
GifContainer.test.js
import React from 'react'
let mockFunction = jest.mock('./api.js');
import * as GifContainer from './GifContainer';
it('should load gifs', () => {
return GifContainer.displayGifs('sunshine')
.then(data => {
expect(data).toBeDefined()
expect(data.entity.data.type).toEqual('gif')
})
})
_ mocks _/api.js
I'm really just not getting how to write this.
const fs = require('fs')
const api = (query) => new Promise((resolve, reject) => {
fs.readFile(`./src/Part_2/__mockData__/query.json`, 'utf8', (err, data) => {
if (err) reject(err)
resolve({ entity: JSON.parse(data) })
})
})
export default api
and then mock data in a folder _ mockData _/sushine.json
/* http://api.giphy.com/v1/gifs/search?q=sunshine&api_key=UE0dCN2WofIwVF0RPbpHo0Lz0k9VhqdG */
{
"data": [
{
"type": "gif",
}
],
"meta": {
"status": 200,
"msg": "OK",
"response_id": "5b11716a33554c732e0ddf42"
}
}
Thank you!
I don't think so the problem is on the mock itself.
Actually, first of all you need to improve the way you are doing react unit testing.
Nowadays, there are tools like Enzyme(http://airbnb.io/enzyme/) which helps you a lot to test React components.
Have you check the Testing React Apps section for Jest? Specially the DOM Testing part? Take a look here: https://facebook.github.io/jest/docs/en/tutorial-react.html#dom-testing
Back to your problem, I think it's because you are exporting the fn as default in the mock file but haven't done that on the api file. Try to put both equals and let me know!

Resources