React testing library - check the existence of empty div - reactjs

I'm testing a component where if ItemLength = 1, render returns null.
const { container, debug } = render(<MyComp ItemLength={1} />);
When I call debug() in my test, it shows a <div />. How do I check that the component is returning an empty div in my test?

Update
Use toBeEmptyDOMElement since toBeEmtpy has been deprecated.
You can use jest-dom's toBeEmpty:
const { container } = render(<MyComp ItemLength={1} />)
expect(container.firstChild).toBeEmpty()

The following should work as well without extending jest's expect:
const { container } = render(<MyComp ItemLength={1} />)
expect(container.firstChild).toBeNull();
Update: the new way in 2020
import { screen } from '#testing-library/react';
...
render(<MyComp ItemLength={1} />);
const child = screen.queryByTestId('data-testid-attribute-value');
expect(child).not.toBeInTheDocument();

.toHaveLength(0) should also work without jest-dom extension
const wrapper = render(<MyComp ItemLength={1}/>);
expect(wrapper.container.innerHTML).toHaveLength(0);

toBeEmpty - throw warning, you must use toBeEmptyDOMElement instead
const pageObject = cretePage(<UserResources />, appState);
expect(pageObject.noContent).toBeInTheDocument();
expect(pageObject.results).toBeEmptyDOMElement();

Since you are trying to test for empty div, one way you could try to test it is by matching node (another possible solution is number of nodes rendered)
getByText(container, (content, element) => element.tagName.toLowerCase() === 'div')

You can use js-dom's toBeEmptyDOMElement method. https://github.com/testing-library/jest-dom#tobeemptydomelement
Before you can use toBeEmptyDOMElement you will need to install jest-dom and set up jest. https://github.com/testing-library/jest-dom#usage
const { container } = render(<MyComp ItemLength={1} />)
expect(container.firstChild).toBeEmptyDOMElement()
Note: toBeEmpty method is being deprecated and its suggested to use toBeEmptyDOMElement

To extend off the previous answers; the following works and is probably a bit cleaner with no dependence on the data-testid or screen to check for roles.
import { render } from "#testing-library/react";
// Components
import { YourComponent } from "<Path>/YourComponent";
describe("YourComponent", () => {
it("component should be an empty element", () => {
const { container } = render(<YourComponent />);
expect(container).toBeEmptyDOMElement();
});
it("component should not be an empty element", () => {
const { container } = render(<YourComponent />);
expect(container).not.toBeEmptyDOMElement();
});
});

Related

Why getByRole does not accept the "ul" as a string?

I'm testing a React app with React Testing Library.
I try to get a ul element like this:
const { render, getByRole } = require("#testing-library/react");
const { default: MoviesList } = require("../components/MoviesList");
const { default: MoviesProvider } = require("../utils/MoviesProvider");
describe("<MoviesList />", () => {
it("Component should exist", () => {
render(
<MoviesProvider value={{}}>
<MoviesList />
</MoviesProvider>
);
const ul = getByRole('ul');
expect(ul).toBeInTheDocument()
// or
expect(getByRole("ul"), isInTheDocument);
});
});
But I get this error:
TypeError: Expected container to be an Element, a Document or a DocumentFragment but got string.
Am I doing something wrong?
ul is an element, not an accessibility role.
The corresponding role is list :
const ul = getByRole('list');
expect(ul).toBeInTheDocument()
You can find a list of existing roles here https://www.w3.org/TR/wai-aria/#role_definitions
The problem was that I was importing getByRole on the top from #testing-library/react. When I tried to import it from render it worked.
Can anyone explain why?
const { getByRole } = render(
<MoviesProvider value={{}}>
<MoviesList />
</MoviesProvider>
);
const list = getByRole('list');

How do I write this test for complete coverage?

I am new to React development and am studying testing with Jest and React Testing Library (RTL).
But I'm having difficulty doing the complete coverage of the component below:
import {
CustomCardActions,
CustomCardHeader,
} from '#Custom/react';
import React from 'react';
import {
PortalAccessButton,
PortalAccessContext,
PortalAccessInternalCard,
PortalAccessTitle,
} from './styles';
interface PortalAccessCard {
children: React.ReactNode
buttonText: string;
hrefLink: string;
}
export const redirectToUrl = (hrefLink: string) => {
window.open(hrefLink, '_self');
};
const PortalAccessCard = (props: PortalAccessCard) => {
const { children, buttonText, hrefLink } = props;
return (
<PortalAccessContext inverse>
<PortalAccessInternalCard>
<CustomCardHeader>
<PortalAccessTitle variant="heading-4">
{children}
</PortalAccessTitle>
</CustomCardHeader>
<CustomCardActions>
<PortalAccessButton onCustomClick={() => redirectToUrl(hrefLink)}>
{buttonText}
</PortalAccessButton>
</CustomCardActions>
</PortalAccessInternalCard>
</PortalAccessContext>
);
};
export default React.memo(PortalAccessCard);
There are two details here:
1- I exported the "redirectToUrl" method to be able to test it. I can't say if there's a better way out, but maybe the second question solves this one.
2- When I check the coverage report it says that this part () => redirectToUrl(hrefLink) has not been tested, but it is basically the pointer to the method I exported above.
My test looks like this:
import { render, RenderResult } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import PortalAccessCard from '.';
import * as PortalAccessCardComponent from '.';
describe('PortalAccessCard', () => {
let renderResult: RenderResult;
const hrefLink = '#';
beforeEach(() => {
renderResult = render(
<PortalAccessCard
buttonText="Texto do botão"
hrefLink={hrefLink}
>
Texto interno PortalAccessCard.
</PortalAccessCard>,
);
});
it('should call onCustomClick and redirectToUrl', async () => {
window.open = jest.fn();
jest.spyOn(PortalAccessCardComponent, 'redirectToUrl');
const onCustomClick = jest.fn(() => PortalAccessCardComponent.redirectToUrl(hrefLink));
const CustomButtonElement = renderResult.container.getElementsByTagName('Custom-button')[0];
CustomButtonElement.onclick = onCustomClick;
await userEvent.click(CustomButtonElement);
expect(onCustomClick).toBeCalledTimes(1);
expect(PortalAccessCardComponent.redirectToUrl).toBeCalledTimes(1);
});
});
What can I do to make the test call of the onCustomClick event call the redirectToUrl method so that Jest understands that this snippet has been tested?
Not sure which exactly line is not covered... Though, toBeCalledTimes is a sign of bad test expectation, so try to append to the very bottom line:
expect(PortalAccessCardComponent.redirectToUrl).toBeCalledWith(hrefLink);
It's better to test for the side effect you want (opening a window). redirectToUrl is an implementation detail. I think you're making this much harder than it needs to be.
Spy on window.open, click the item, check the spy. I think that's all you need.
jest.spyOn(window, 'open')
const CustomButtonElement = renderResult.container.getElementsByTagName('Custom-button')[0];
await userEvent.click(CustomButtonElement);
// or maybe: getByRole('something...').click()
expect(window.open).toHaveBeenCallWith('#', '_self')

Test a component with useState and setTimeout

Code structure is as same as given below:
FunctionComponent.js
...
const [open, handler] = useState(false);
setTimeout(() => {handler(true);}, 2000);
...
return (
...
<div className={active ? 'open' : 'close'}>
)
comp.test.js
jest.useFakeTimers();
test('test case 1', () => {
expect(wrapper.find('open').length).toBe(0);
jest.advanceTimersByTime(2000);
expect(wrapper.find('open').length).toBe(1);
jest.useRealTimers();
});
The problem is that the expression written in bold in test is saying the length of open class is still 0, so actual and expected are not meeting.
You want to test the outcome of the hook and not the hook itself since that would be like testing React. You effectively want a test where you check for if the open class exists and then doesn't exist (or vice versa), which it looks like you're trying.
In short, to solve your issue you need to use ".open" when selecting the class. I would also suggest using the .exists() check on the class instead of ".length()" and then you can use ".toBeTruthy()" as well.
You could look into improve writing your tests in a Jest/Enzyme combined format as well:
import { shallow } from 'enzyme';
import { FunctionComponent } from './FunctionComponent.jsx';
jest.useFakeTimers();
describe('<FunctionCompnent />', () => {
const mockProps = { prop1: mockProp1, prop2: mockProp2, funcProp3: jest.fn() };
const wrapper = shallow(<FunctionComponent {...mockProps} />);
afterEach(() => {
jest.advanceTimersByTime(2000);
});
afterAll(() => {
jest.useRealTimers();
});
it('should render as closed initially', () => {
expect(wrapper.find('.close').exists()).toBeTruthy();
// you could also add the check for falsy of open if you wanted
// expect(wrapper.find('.open').exists()).toBeFalsy();
});
it('should change to open after 2 seconds (or more)', () => {
expect(wrapper.find('.open').exists()).toBeTruthy();
// you could also add the check for falsy of close if you wanted
// expect(wrapper.find('.close').exists()).toBeFalsy();
});
});
EDIT: Sorry realised I wrote the test backwards after checking your code again, they should be fixed now.

A warning is thrown because of an update to ForwardRef when testing a component animated with react-spring with #testing-library/react

I have a component animated with react-spring using the useSpring hook. Something like:
function Toggle({ handleToggle, toggled }) {
const x = useSpring({
x: toggled ? 1 : 0
});
return (
<div className={styles.switch} onToggle={handleToggle} data-testid="switch">
<animated.div
className={styles.circle}
style={{
transform: x
.interpolate({
range: [0, 0.4, 0.8, 1],
output: [0, 5, 10, 16]
})
.interpolate((x) => `translateX(${x}px)`)
}}>
</animated.div>
</div>
);
}
When testing the component a warning is thrown:
Warning: An update to ForwardRef inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/docs/test-utils.html#act
in ForwardRef (created by Toggle)
in Toggle
The code for my test is:
test("Toggle works", () => {
let toggled = false;
const handleToggle = jest.fn(() => {
toggled = true;
});
const { getByTestId, queryByTestId } = render(
<Toggle toggled={toggled} handleToggle={handleToggle}/>
);
});
How should I test components animated with react-spring using #testing-library/react?
I had this warning while testing a component that uses useSpring and <animated.div> in a react-test-renderer renderer snapshot creation:
import renderer from 'react-test-renderer';
//(...)
it('matches snapshot', () => {
const tree = renderer.create(<Component />).toJSON();
expect(tree).toMatchSnapshot();
});
//(...)
I've found inspiration in this link, and could solve it just by adding the Jest's useFakeTimers call:
import renderer from 'react-test-renderer';
//(...)
it('matches snapshot', () => {
jest.useFakeTimers(); //ADDED
const tree = renderer.create(<Component />).toJSON();
expect(tree).toMatchSnapshot();
});
//(...)
See that wrapping stuff with act, like in the referenced link, was not necessary in this case.
While researching I also found this following blog post, that explains the reasons and describes some cases in which this warning is shown, including a case when you do use jest.useFakeTimers(), the warning keeps being displayed and using act is necessary: Fix the "not wrapped in act(...)" warning

react-testing-library | Cannot Split Test into smaller chunks inside describe method

I'm learning about unit testing React components using react-testing-library
I have the component rendering correctly, however, when I aim to break the test into smaller chunks inside a describe() function. The test breaks and here's why.
Current only one or the other test() passes but not both
import React from 'react'
import 'react-testing-library/cleanup-after-each'
import { render, fireEvent } from 'react-testing-library'
import Quantity from '../components/Quantity'
describe('Quantity Component', () => {
const { container, getByTestId } = render(<Quantity />)
// first test
test('checks that quantity is never 0', () => {
expect(getByTestId('quantity')).not.toBe('0')
})
// second test
test('checks for the initial product quantity count', () => {
expect(getByTestId('quantity')).toHaveTextContent('1')
fireEvent.click(getByTestId('increment'))
expect(getByTestId('quantity')).toHaveTextContent('2')
})
})
When trying to run both tests it errors:
Unable to find an element by: [data-testid="quantity"]
[data-testid="quantity"] is just an attribute that I passed inside my desired JSX tag.
The test passes when running only the first or second test but not both concurrently.
What am I missing here?
Cross-contamination is strictly discouraged in unit testing.
The problem is that a setup occurs only once per Quantity Component suite, while it should be done for each test. This is what beforeEach is for:
describe('Quantity Component', () => {
let container, getByTestId;
beforeEach(() => {
({ container, getByTestId } = render(<Quantity />));
});
...
You need to also use an afterEach cleanup.
describe('your tests', () => {
afterEach(cleanup);
beforeEach(() => ({container, getById} = render(<Quantity />))
it('does something', () => {
expect(getByTestId('quantity')).toHaveTextContent(0);
}
}
I suggest you call the render inside your it clauses, it keeps the tests easier to manage:
describe('Quantity Component', () => {
test('checks that quantity is never 0', () => {
const { container, getByTestId } = render(<Quantity />)
expect(getByTestId('quantity')).not.toBe('0')
})
test('checks for the initial product quantity count', () => {
const { container, getByTestId } = render(<Quantity />)
expect(getByTestId('quantity')).toHaveTextContent('1')
fireEvent.click(getByTestId('increment'))
expect(getByTestId('quantity')).toHaveTextContent('2')
})
})
The added advantage is that if for some reason one of your tests needs to run with different props you can do that more easily with this setup.

Resources