I'm building a React app with TypeScript. I do my component tests with react-testing-library.
I'm buildilng a parallax component for my landing page.
The component is passed the image via props and sets it via JSS as a background image:
<div
className={parallaxClasses}
style={{
backgroundImage: "url(" + image + ")",
...this.state
}}
>
{children}
</div>
Here is the unit test that I wrote:
import React from "react";
import { cleanup, render } from "react-testing-library";
import Parallax, { OwnProps } from "./Parallax";
afterEach(cleanup);
const createTestProps = (props?: object): OwnProps => ({
children: null,
filter: "primary",
image: require("../../assets/images/bridge.jpg"),
...props
});
describe("Parallax", () => {
const props = createTestProps();
const { getByText } = render(<Parallax {...props} />);
describe("rendering", () => {
test("it renders the image", () => {
expect(getByText(props.image)).toBeDefined();
});
});
});
But it fails saying:
● Parallax › rendering › it renders the image
Unable to find an element with the text: bridge.jpg. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
<body>
<div>
<div
class="Parallax-parallax-3 Parallax-primaryColor-4"
style="background-image: url(bridge.jpg); transform: translate3d(0,0px,0);"
/>
</div>
</body>
16 | describe("rendering", () => {
17 | test("it renders the image", () => {
> 18 | expect(getByText(props.image)).toBeDefined();
| ^
19 | });
20 | });
21 | });
at getElementError (node_modules/dom-testing-library/dist/query-helpers.js:30:10)
at getAllByText (node_modules/dom-testing-library/dist/queries.js:336:45)
at firstResultOrNull (node_modules/dom-testing-library/dist/query-helpers.js:38:30)
at getByText (node_modules/dom-testing-library/dist/queries.js:346:42)
at Object.getByText (src/components/Parallax/Parallax.test.tsx:18:14)
How can I test that the image is being set as a background image correctly with Jest and react-testing-library?
getByText won't find the image or its CSS. What it does is to look for a DOM node with the text you specified.
In your case, I would add a data-testid parameter to your background (<div data-testid="background">) and find the component using getByTestId.
After that you can test like this:
expect(getByTestId('background')).toHaveStyle(`background-image: url(${props.image})`)
Make sure you install #testing-library/jest-dom in order to have toHaveStyle.
If you want to avoid adding data-testid to your component, you can use container from react-testing-library.
const {container} = render(<Parallax {...props})/>
expect(container.firstChild).toHaveStyle(`background-image: url(${props.image})`)
This solution makes sense for your component test, since you are testing the background-image of the root node. However, keep in mind this note from the docs:
If you find yourself using container to query for rendered elements then you should reconsider! The other queries are designed to be more resiliant to changes that will be made to the component you're testing. Avoid using container to query for elements!
in addition to toHaveStyle JsDOM Matcher, you can use also style property which is available to the current dom element
Element DOM API
expect(getByTestId('background').style.backgroundImage).toEqual(`url(${props.image})`)
also, you can use another jestDOM matcher
toHaveAttribute Matcher
expect(getByTestId('background')).toHaveAttribute('style',`background-image: url(${props.image})`)
Simple solution for testing Component css with react-testing-library. this is helpful for me it is working perfect.
test('Should attach background color if user
provide color from props', () => {
render(<ProfilePic name="Any Name" color="red" data-
testid="profile"/>);
//or can fetch the specific element from the component
const profile = screen.getByTestId('profile');
const profilePicElem = document.getElementsByClassName(
profile.className,
);
const style = window.getComputedStyle(profilePicElem[0]);
//Assertion
expect(style.backgroundColor).toBe('red');
});
Related
I'm trying to get unit test coverage for the code in red (see screenshot) using react-testing-library. Would anyone know what unit test would cover this? I'm still learning the react-testing-library. TIA
screenshot of code here showing red, uncovered code
If you don't open the screenshot above, the code inside this function is what needs to be covered.
const togglePopover = () => {
setToolTipOpen((prev) => !prev);
};
actual full component code block:
import React, { FunctionComponent, useState, KeyboardEvent, useRef, useEffect } from 'react';
import styles from './InfoPopover.module.scss';
import { Popover, PopoverBody } from 'x'
import { PopperPlacementType } from '#material-ui/core';
import { ReactComponent as InfoIcon } from '../../../assets/icons/tooltipIcon.svg';
export interface PopperProps {
placement?: PopperPlacementType;
tipMessage: React.ReactNode | string;
stringedTipMessage: string;
}
const InfoPopover: FunctionComponent<PopperProps> = ({
placement,
tipMessage,
stringedTipMessage
}: PopperProps) => {
const [toolTipOpen, setToolTipOpen] = useState(false);
const togglePopover = () => {
setToolTipOpen((prev) => !prev);
};
const handleBlur = () => {
setToolTipOpen(false);
};
return (
<>
<button
id="popoverTarget"
className={styles.tooltipButton}
onBlur={handleBlur}
aria-label={`Tooltip Content - ${stringedTipMessage}`}
>
<InfoIcon aria-label="status tooltip" />
</button>
<Popover
target="popoverTarget"
trigger="legacy"
toggle={togglePopover}
placement={placement}
isOpen={toolTipOpen}
arrowClassName={styles.toolTipArrow}
popperClassName={styles.toolTipPopout}
>
<PopoverBody>{tipMessage}</PopoverBody>
</Popover>
</>
);
};
export default InfoPopover;
With React Testing Library, the approach is to test what the user can see/do rather than test the internals of your application.
With your example, assuming you are trying to test a simple open/close popup user flow then the user would be seeing a button, and when they activate that button they would see a popover. A simple RTL approach would be as follows:
const popoverTipMessage = "My popover message";
render(<InfoPopover tipMessage={popoverTipMessage} />);
// Popover isn't activated, so it shouldn't be in the DOM
expect(screen.getByText(popoverTipMessage)).not.toBeInDocument();
// Find button and click it to show the Popover
fireEvent.click(screen.getByRole('button', {
name: /tooltip content/i
}));
// Popover should now be activated, so check if it's visible (in the DOM)
await waitFor(() => {
// This relies on RTL's text matching to find the component.
expect(screen.getByText(popoverTipMessage)).toBeInDocument();
});
// Find button and click it again to hide the Popover
fireEvent.click(screen.getByRole('button', {
name: /tooltip content/i
}));
// Popover should now be hidden, so check if the DOM element has gone
// Note: There are other ways of checking appearance/disappearance. Check the RTL docs.
await waitFor(() => {
// This relies on RTL's text matching to find the component but there are other better ways to find an element
expect(screen.getByText(popoverTipMessage)).not.toBeInDocument();
});
The query methods I've used above are some of the basic ones, however RTL has many different queries to find the element you need to target. It has accessibility at the forefront of its design so leans heavily on these. Take a look in the docs: https://testing-library.com/docs/react-testing-library/example-intro
I created a simple Hook that uses ResizeObserver to run setState when an Mui Box element is resized before/after a certain point. The purpose was to apply different styles based on whether the flex component was wrapped or not.
It works in the browser, but now the problem that I have is that I have no idea how to write tests for it. I tried with Jest, Enzyme, but I cannot find a way to really run a test against it. I would like to mount the component with one width and verify that it has the proper class, then trigger the resize event and verify that the class changed. I will need to mock the ref and the height. I am searched the web for hours for a solution but haven't found anything that works.
Here is the component:
function Component {
const [isWrapped, setIsWrapped] = useState(false);
const myRef = useRef() as React.MutableRefObject<HTMLInputElement>;
React.useEffect() {
const resizeObserver = new ResizeObserver((entries) => {
if (entries[0].contentRect.height > 100) {
setIsWrapped(true);
} else {
setIsWrapped(false);
}
});
resizeObserver.observe(myRef.current);
}
return (
<Box {...{ ref: myRef }} display="flex" id="my-element" className={isWrapped ? classes.wrappedClass : classes.inlineClass}>{"someText"}</Box>
)
}
At the top of my test file I have
global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
}))
what I want my test to be like:
it("Test wrap/unwrap conditional styling", () => {
// mount with height 80px
let wrapper = mount(
<ReduxProvider store={store}>
<Component />
</ReduxProvider>
);
expect(toJson(wrapper)).toMatchSnapshot();
const myElement = wrapper.find("#my-element");
expect(myElement).toHaveCLass("wrappedClass");
// trigger resize with height 110
expect(myElement).toHaveCLass("inlineClass");
}
I want to test that the tooltip title is equal to a specific text or not.
This is my antd tooltip I want to write a test for that:
<Tooltip
title={
this.props.connection ? "Connected" : "Disconnected (Try again)"
}>
<Badge status="default" data-testid="connection-sign" />
</Tooltip>
and this is my test in jest:
test("Show error title in tooltip", async () => {
baseDom = render(cardComponent);
fireEvent.mouseMove(await baseDom.findByTestId("connection-sign")); //To hover element and show tooltip
expect(
baseDom.getByTitle(
"Disconnected (Try again)"
)
).toBeInTheDocument();
});
but this test failed and unable to find an element with this title. How can I test that my tooltip contain "Disconnected (Try again)"?
There are multiple mistakes in your test.
Passing component type instead of component instance to render
// this is wrong, passing component type
baseDom = render(cardComponent);
// this is right, passing component instance created with JSX
baseDom = render(<cardComponent />);
Using mouseMove instead of mouseOver event
Searching element by title and passing text instead of searching by text
// wrong, because, the prop on the Tooltip is called 'title'
// but it is actually text as 'getByTitle' looks for HTML
// title attribute
baseDom.getByTitle("Disconnected (Try again)");
// right, because you are actually looking for text not
// HTML title attribute (but wrong see (4))
baseDom.getByText("Disconnected (Try again)");
Using sync method for Tooltip search instead of async
// wrong, because, Tooltip is added dynamically to the DOM
baseDom.getByText("Disconnected (Try again)");
// right
await baseDom.findByText("Disconnected (Try again)");
To sum up, with all mistakes fixed the test should look like this:
import React from "react";
import { render, fireEvent } from "#testing-library/react";
import App from "./App";
test("Show error title in tooltip", async () => {
const baseDom = render(<cardComponent />);
fireEvent.mouseOver(baseDom.getByTestId("connection-sign"));
expect(
await baseDom.findByText("Disconnected (Try again)")
).toBeInTheDocument();
});
In addition to the accepted answer, it's important to make sure if you set the prop getPopupContainer for an Antd tooltip, the popup might not be visible to react testing library as it happened in my case since the DOM container set may not be available in the body when testing the component especially if it's a unit test. e.g
In my case I had
<Popover
getPopupContainer={() => document.getElementById('post--component-drawer')}
content={<h1>Hello world</h1>}>
<span data-testid="symbol-input--color-input">Click me</span>
</Popover>
div post--component-drawer was not available for that unit test. So I had to mock Popover to make sure I override prop getPopupContainer to null so that the popup would be visible
So at the beginning of my test file, I mocked Popover
jest.mock('antd', () => {
const antd = jest.requireActual('antd');
/** We need to mock Popover in order to override getPopupContainer to null. getPopContainer
* sets the DOM container and if this prop is set, the popup div may not be available in the body
*/
const Popover = (props) => {
return <antd.Popover {...props} getPopupContainer={null} />;
};
return {
__esModule: true,
...antd,
Popover,
};
});
test('popver works', async () => {
render(<MyComponent/>);
fireEvent.mouseOver(screen.getByTestId('symbol-input--color-input'));
await waitFor(() => {
expect(screen.getByRole('heading', {level: 1})).toBeInTheDocument();
});
});
I was tried many ways but didn't work, therefore I tried
mouse enter instead of mouseOver or mouseMove and it's worked for me.
here is a solution to test tooltip content, like as:
import { render, cleanup, waitFor, fireEvent, screen } from '#testing-library/react';
// Timeline component should render correctly with tool-tip
test('renders TimeLine component with mouse over(tool-tip)', async () => {
const { getByTestId, getByText, getByRole } = render(
<TimeLine
items={timeLineItems()}
currentItem={currentTimeLineItem()}
onTimeLineItemChange={() => {}}
/>
);
const courseTitle = "Course collection-3";
fireEvent.mouseEnter(getByRole('button'));
await waitFor(() => getByText(courseTitle));
expect(screen.getByText(courseTitle)).toBeInTheDocument();
});
I found this to be the most up to date way. You've got to do async() on the test and then await a findByRole since it isn't instantaneous!
render(<LogoBar />);
fireEvent.mouseEnter(screen.getByLabelText('DaLabel'));
await screen.findByRole(/tooltip/);
expect(screen.getByRole(/tooltip/)).toBeInTheDocument();
This is a slight modification to the Akshay Pal's solution:
import { render, cleanup, waitFor, fireEvent, screen } from '#testing-library/react';
// Timeline component should render correctly with tool-tip
test('renders TimeLine component with mouse over(tool-tip)', async () => {
render(
<TimeLine
items={timeLineItems()}
currentItem={currentTimeLineItem()}
onTimeLineItemChange={() => {}}
/>
);
const courseTitle = "Course collection-3";
fireEvent.mouseEnter(screen.getByRole('button'));
expect(await screen.getByText(courseTitle)).toBeTruthy();
});
I'm new to React and googled a lot of sources but could not solve my problem...
When I click on button via "simulate(click)" it works and I can see the right result of my test 'calls the props.onPointDelete'. But after this I should see new props of this wrapper (pointsList) which received from root App component where I have onClick handler. And it is not updated. It's the second test 'points.length equals 1' which failed. PointsList component is functional component.
I tried to update my wrapper via afterEach() function, and it's not working. Maybe this problem is somehow connected with mount() way of my wrapper? But I can't use shallow() way, because I need to access child of child component of my wrapper for my current tests. Can you help me please?
PointsList.spec.js :
import React from 'react';
import {mount} from 'enzyme';
import PointsList from './PointsList';
describe('PointsList component', () => {
const mockOnPointDelete = jest.fn();
const props = {
points: [
{
id: 1,
name: 'Point 1',
coordinates: [55.50, 57.50]
},
{
id: 2,
name: 'Point 2',
coordinates: [55.51, 57.51]
}
],
onPointDelete: mockOnPointDelete,
onDragEnd: () => {
}
};
describe('PointsList component initial', () => {
const pointsList = mount(<PointsList {...props} />);
describe('when deleting first point', () => {
pointsList.find('li').at(1).find('.btn.btn-delete').simulate('click');
afterEach(() => {
pointsList.update();
});
it('calls the props.onPointDelete', () => {
expect(mockOnPointDelete).toHaveBeenCalledTimes(1);
});
it('points.length equals 1', () => {
expect(pointsList.prop('points')).toHaveLength(1);
});
});
})
});
PointsList.js :
import React from 'react';
import {Droppable, Draggable, DragDropContext} from 'react-beautiful-dnd';
import RoutePoint from '../RoutePoint';
const PointsList = (props) => {
const {points, onPointDelete, onDragEnd} = props;
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="pointsDroppable">
{ (provided) => (
<ul className='list-group'
ref={provided.innerRef}>
{ points.map((point, i) => {
const {id, ...itemProps} = point;
return (
<Draggable key={id} draggableId={'draggable-' + id} index={i}>
{provided => (
<li className='list-group-item'
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<RoutePoint onPointDelete={() => onPointDelete(id)} {...itemProps}/>
</li>
)}
</Draggable>
);
})}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
);
};
export default PointsList;
You're mixing an integration test within unit testing. What you have above is unit testing. Since you've only mounted (you could just use shallow instead of mount) the PointList stateless function and mocked the onPointDelete function, jest has no awareness of the parent nor its methods. In simple terms, you've isolated your test to the child, not the parent + child. You're not calling the parent's onPointDelete method, you're just calling a fake/mocked function!
If you want to do an integration test with parent + child, then mount the parent that contains the child, set the parent's state (same as you did above for props, just use parent.setState(...)), do not mock the onPointDelete function, and now test for state and/or prop changes.
Here's an example of integration and unit testing: https://codesandbox.io/s/p3ro0615nm (I'm using custom shallowWrap and mountWrap functions that you can find more info about in tests/utils/index.js -- you can run the tests by clicking on the Tests tab at the bottom of the screen next Console and Problems)
The test in containers/LOAs/__tests__/LOAs.test.js is an integration test. This test steps through multiple components and checks for overall proper functionality between parent and children nodes, as well as, checks for state updates. I left some tests blank, so if you wish, you can fill them in for practice.
The test in components/LoaList/__tests__/loaList.test.js is a unit test -- note that this test is unnecessary since it's already covered in the above integration test, but it's here for example purposes of isolating the child component from the parent. This tests checks for child component functionality, prop validation, and mock function calling.
Inside a <MyComponent> component I am using react-responsive <MediaQuery> components to distinguish between rendering mobile and desktop content.
export class MyComponent extends React.Component {
//...
render() {
<MediaQuery query="(max-width: 600)">
<div className="inside-mobile">mobile view</div>
</MediaQuery>
}
}
I want to test the HTML inside <MyComponent>'s render() using enzyme, but can't seem to dive into the child elements of <MediaQuery>:
it('should dive into <MediaQuery>', () => {
const wrapper = mount(<Provider store={mockedStore}><MyComponent/></Provider>)
const actual = wrapper.getDOMNode().querySelectorAll(".inside-mobile")
expect(actual).to.have.length(1)
}
A console.log(wrapper.debug())shows that nothing inside <MediaQuery> is being rendered, though.
I'm guessing in a test (with no actual browser) window.width is not set which leads to the <MediaQuery> component not rendering anything.
What I want to do:
I want to be able to test <MyComponent>'s content using enzyme with react-responsive (or something similar such as react-media) to deal with mobile vs desktop viewports.
Things I've tried:
circumventing this by using enzyme's shallow with dive() instead of mount, to no avail.
using react-media's <Media> instead of react-responsive's <MediaQuery>, which seems to set window.matchMedia() to true by default. However, that's not working either.
console.log(wrapper.debug()) shows:
<MyComponent content={{...}}>
<Media query="(min-width: 600px)" defaultMatches={true} />
</MyComponent>
I found a working solution, using react-media instead of react-responsive, by mocking window.matchMedia so that matches is set to true during the test:
Create specific media components for different viewports:
const Mobile = ({ children, content }) => <Media query="(max-width: 600px)" children={children}>{matches => matches ? content : "" }</Media>;
const Desktop = ...
Use specific media component:
<MyComponent>
<Mobile content={
<div className="mobile">I'm mobile</div>
}/>
<Desktop content={...}/>
</MyComponent>
Test content per viewport:
const createMockMediaMatcher = matches => () => ({
matches,
addListener: () => {},
removeListener: () => {}
});
describe('MyComponent', () => {
beforeEach(() => {
window.matchMedia = createMockMediaMatcher(true);
});
it('should display the correct text on mobile', () => {
const wrapper = shallow(<MyComponent/>);
const mobileView = wrapper.find(Mobile).shallow().dive();
const actual = mobileView.find(".mobile").text();
expect(actual).to.equal("I'm mobile");
});
});