Testing react-lazyload in React testing library - reactjs

I am unit testing a component that makes use of react-lazyload to lazyload stuff. While rendering in my unit test, I noticed that the Lazyload is not rendering the content and its placeholder is being shown. I tried using waitFor with async/await to wait for lazily loaded content to render in next tick cycle but it still fails and screen.debug() still shows the placeholder in the dom.
Is there any way I can test such a component? Here is what I tried:
render(<WrapperComponent />)
await waitFor(() => {
expect(screen.getByText('Lazily loaded text!')).toBeInTheDocument()
});
// source code
import LazyLoad from 'react-lazyload';
const WrapperComponent = () => {
return (
<div>
<LazyLoad placeholder="Loading..." >
<div>Lazily loaded text!</div>
</LazyLoad>
</div>
);
}
Lazily loaded content has a div with the text being expected above.

We can mock the LazyLoad component from react-lazyload library at the top of our test file so that the LazyLoad component behave just like a wrapper around our actual component that we need to test for.
jest.mock(
'react-lazyload',
() =>
function LazyLoad({ children }) {
return <>{children}</>
}
)
test('component wrapped with Lazyload', () => {
const {screen} = render(< WrapperComponent />)
expect(screen.getByText('Lazily loaded text!')).toBeInTheDocument()
})

Test does not actually trigger real data fetch, you have to mock it.
Probably the function that requests for data should be the one you mock.

You can utilize forceVisible:
import { forceVisible } from 'react-lazyload';
it('Testing a component using react-lazyload', async () => {
const screen = render(<WrapperComponent />)
forceVisible();
await waitFor(() => {
expect(screen.getByText('Lazily loaded text!')).toBeInTheDocument()
});
});

Related

How to test onClick with Jest that is NOT a callback function in props?

I found lots of ways of using mock functions in jest to spy on callback functions that are passed down to a component but nothing on testing a simple onClick that is defined in the same component.
My Example Page:
const ExamplePage: NextPage = () => {
const router = useRouter();
const onClick = (): Promise<void> => {
axios.post(`/api/track`, {
eventName: Event.TRACK_CLICK,
});
router.push("/new-route");
return Promise.resolve();
};
return (
<Container data-testid="container">
<Title>Example Title</Title>
<CreateButton data-testid="create-button" onClick={onClick}>
Create Partner
</CreateButton>
</Container>
);
};
export default ExamplePage;
My current test where I am attempting to get the onClick from getAttribute:
import { fireEvent, render } from "../../../../test/customRenderer";
import ExamplePage from "../../../pages/example-page";
describe("Example page", () => {
it("has a button to create", () => {
const { getByTestId } = render(<ExamplePage />);
const createButton = getByTestId("create-button");
expect(createButton).toBeInTheDocument();
});
it(" the button's OnClick function should be executed when clicked", () => {
const { getByTestId } = render(<ExamplePage />);
// find the button
const createButton = getByTestId("create-button");
// check the button has onClick
expect(createButton).toHaveAttribute("onClick");
// get the onClick function
const onClick = createButton.getAttribute("onClick");
fireEvent.click(createButton);
// check if the button's onClick function has been executed
expect(onClick).toHaveBeenCalled();
});
});
The above fails since there is no onClick attribute only null. My comments in the test highlight my thought process of trying to reach down into this component for the function on the button and checking if it has been called.
Is there any way to test a onClick that is self contained in a react component?
You need to provide mocked router provider and expect that a certain route is pushed to the routers. You also need extract the RestAPI into a separate module and mock it! You can use Dependency Injection, IOC container or import the Api in the component and mock it using jest. I will leave the RestAPi mocking to you.
Mocking router details here: How to mock useRouter
const useRouter = jest.spyOn(require('next/router'), 'useRouter')
describe("", () => {
it("",() => {
const pushMock = jest.fn();
// Mocking Rest api call depends on how you are going to "inject it" in the component
const restApiMock = jest.jn().mockResolvedValue();
useRouter.mockImplementationOnce(() => ({
push: pushMock,
}))
const rendrResult = render(<ExamplePage />);
//get and click the create button
//expect the "side" effects of clicking the button
expect(restApiMock).toHaveBeenCalled();
expect(pushMock).toHaveBeenCalledWith("/new-route");
});
});

Wait for multiple elements to be removed React testing Library

I have a react application where I'm using jest and react testing library for my unit tests. I have a unit test where I want to test that SomeComponent when loaded doesn't render any skeleton.
SomeComponent renders skeletons when data isn't loaded yet. Problem is that multiple skeletons are rendered and waitForElementToBeRemoved only takes one HTMLElement.
So I was wondering if there was a way to waitForMultipleElementsToBeRemoved?
test("SomeComponent when loaded doesn't render any skeleton", async () => {
render(
<SomeComponent />,
)
const skeletons = screen.getAllByTestId("skeleton");
await waitForElementToBeRemoved(skeletons);
expect(
screen.queryByTestId("skeleton")
).not.toBeInTheDocument();
});
Note:
This test works as intended when there is only one skeleton.
You can just:
await waitForElementToBeRemoved(() => screen.getAllByTestId("initLoader"));
or in your case:
await waitForElementToBeRemoved(() => skeletons);
I think you just missing the callback. Otherwise your code is fine!
You could do this if you only want to test for 1 skeleton to be rendered
test("only 1 skeleton", async () => {
const { getAllByTestId } = render(
<div data-testId="skeleton">spooky skeleton</div>
);
const skeletons = getAllByTestId("skeleton");
await waitFor(()=>{
expect(skeletons.length).toBe(1);
})
});
Here, I use getAllByTestId, but instead of checking for it to not be in the document, I just check if the length of the returned query is 1, meaning there is only one skeleton element being rendered.
I also wrap it in a waitFor as I'm assuming that the skeletons disappear over time. Note that the default timeout is only 1000ms, but this can be configured using the options parameter.
sandbox

How to test react-toastify with jest and react-testing-library

I have a screen with some form, and on submission, I send the request to back-end with axios. After successfully receiving the response, I show a toast with react-toastify. Pretty straight forward screen. However, when I try to test this behavior with an integration test using jest and react testing library, I can't seem to make the toast appear on DOM.
I have a utility renderer like that to render the component that I'm testing with toast container:
import {render} from "#testing-library/react";
import React from "react";
import {ToastContainer} from "react-toastify";
export const renderWithToastify = (component) => (
render(
<div>
{component}
<ToastContainer/>
</div>
)
);
In the test itself, I fill the form with react-testing-library, pressing the submit button, and waiting for the toast to show up. I'm using mock service worker to mock the response. I confirmed that the response is returned OK, but for some reason, the toast refuses to show up. My current test is as follows:
expect(await screen.findByRole("alert")).toBeInTheDocument();
I'm looking for an element with role alert. But this seems to be not working.
Also, I tried doing something like this:
...
beforeAll(() => {
jest.useFakeTimers();
}
...
it("test", () => {
...
act(() =>
jest.runAllTimers();
)
expect(await screen.findByRole("alert")).toBeInTheDocument();
}
I'm kind of new to JS, and the problem is probably due to asynch nature of both axios and react-toastify, but I don't know how to test this behavior. I tried a lot of things, including mocking timers and running them, mocking timers and advancing them, not mocking them and waiting etc. I even tried to mock the call to toast, but I couldn't get it working properly. Plus this seems like an implementation detail, so I don't think I should be mocking that.
I think the problem is I show the toast after the axios promise is resolved, so timers gets confused somehow.
I tried to search many places, but failed to find an answer.
Thanks in advance.
Thank you #Estus Flask, but the problem was much much more stupid :) I had to render ToastContainer before my component, like this:
import {render} from "#testing-library/react";
import React from "react";
import {ToastContainer} from "react-toastify";
export const renderWithToastify = (component) => {
return (
render(
<div>
<ToastContainer/>
{component}
</div>
)
);
};
Then, the test was very simple, I just had to await on the title of the toast:
expect(await screen.findByText("alert text")).toBeInTheDocument();
The findByRole doesn't seem to work for some reason, but I'm too tired to dig deeper :)
I didn't have to use any fake timers or flush the promises. Apperently, RTL already does those when you use await and finBy* queries, only the order of rendering was wrong.
In order to use a mock when you don't have access to the DOM (like a Redux side effect) you can do:
import { toast } from 'react-toastify'
jest.mock('react-toastify', () => ({
toast: {
success: jest.fn(),
},
}))
expect(toast.success).toHaveBeenCalled()
What I would do is mock the method from react-toastify to spy on that method to see what is gets called it, but not the actual component appearing on screen:
// setupTests.js
jest.mock('react-toastify', () => {
const actual = jest.requireActual('react-toastify');
Object.assign(actual, {toast: jest.fn()});
return actual;
});
and then in the actual test:
// test.spec.js
import {toast} from 'react-toastify';
const toastCalls = []
const spy = toast.mockImplementation((...args) => {
toastCalls.push(args)
}
)
describe('...', () => {
it('should ...', () => {
// do something that calls the toast
...
// then
expect(toastCalls).toEqual(...)
}
}
)
Another recommendation would be to put this mockImplementation into a separate helper function which you can easily call for the tests you need it for. This is a bear bones approach:
function startMonitoring() {
const monitor = {toast: [], log: [], api: [], navigation: []};
toast.mockImplementation((...args) => {
monitor.toast.push(args);
});
log.mockImplementation((...args) => {
monitor.log.push(args);
});
api.mockImplementation((...args) => {
monitor.api.push(args);
});
navigation.mockImplementation((...args) => {
monitor.navigation.push(args);
});
return () => monitor;
}
it('should...', () => {
const getSpyCalls = startMonitoring();
// do something
expect(getSpyCalls()).toEqual({
toast: [...],
log: [...],
api: [...],
navigation: [...]
});
});
Here, the solution was use getByText:
await waitFor(() => {
expect(screen.getByText(/Logged!/i)).toBeTruthy()
})

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 };

How do I mock a const method IN a component as part of an integration test?

So, I am doing an integration test with jest & tesing-library.
So, I have my "main component" called "Product" that brings in another component, lets call it "ProductListings".
In "ProductListings", i click on a div that calls a method IN the functional component. How do I mock that, to test it was called?
test('Clicking div calls method', async () => {
render(<Product />);
// THIS div is in ProductListings component.. that is contained within Product component
const divToClickAdd = await screen.findByTestId('myDivWithOnClick')
user.click(divToClickAdd); // clicking this div fires the method
// how to do this?
expect.THE-MOCKED-FUNCTION-TO-HAVE-BEEN-CALLED
});
// this is what ProductListing looks like (psuedo). It is IN Products component, that I am integration testing.
const ProductListing = () => {
// I WANT TO MOCK THIS "fireTheAPI" in my test above.
const fireTheAPI = () => {
// do some stuff
}
return (
<div onClick={() => fireTheAPI()} data-testId="myDivWithOnClick">this is a product listing item</div>
)
}
See my issue. Bring in Parent component, that contains a "inner component, ProductListings, and I get a div from that, and click.. I need to mock that function IN it, "fireTheAPI"
btw, "Product" looks something like:
const Product = () => {
return (
<div>
<WhateverComponent />
<div>
<ProductListings /> // <--- mock a function in here
</div>
</div>
)
}
I think it depends on what your function is doing.
In this instance, if you are expecting an API call, i would suggest using nock to mock the API call.
It would look something like this
test('Your Test', () => {
const scope = nock(YOUR_URL).get(YOUR_ENDPOINT).reply(200);
const product = render(<Product />);
const divToClickAdd = await product.findByTestId('myDivWithOnClick');
act(() => {
fireEvent.click(divToClickAdd);
});
await waitFor(() => {
expect(scope.isDone()).toBe(true);
});
});
If you are expecting some DOM changes, you can go on to test that too.
IMO the integration tests should focus on the outcomes of the user actions, so rather than worrying about mocking a particular function, we should look towards attempting to mock (or test) the particular outcome of said function.

Resources