Test correct SVG component renders with jest and react-testing-library
Asked Answered
L

4

13

I have an svg inside a React component that renders conditionally.

<div className='avatar'>
      {gender === true ? <MaleAvatar /> : <FemaleAvatar />}
    </div>

MaleAvatar and FemaleAvatar are components containing svgs. Initially, I want the MaleAvatar svg to render and then if the value of gender is changed to false the FemaleAvatar svg renders - but the male avatar should render first and that's what I want to test.

The component the conditional is in is a child of a child, but I am testing elements in that component by text and the test works fine eg:

const personDetailsText = screen.getByText('Personal details')

I am testing with jest and react-testing-library, but using a testing id on the parent div then grabbing the first child doesn't work because it can't recognise the testing id. So if I have:

<div data-testid='avatar' className='avatar'>
          {gender === true ? <MaleAvatar /> : <FemaleAvatar />}
        </div>

...then the test below fails at 'const avatarSVG = screen.getByTestId('avatar')':

test('gender avatar is male on initialisation', () => {
    const avatarSVG = screen.getByTestId('avatar')
    expect(avatarSVG).toBeInTheDocument()
    expect(() => screen.getByTestId('female-avatar').toThrow())
    expect(avatar.firstChild.nodeName).toBe('MaleAvatar')
  })

I'm using React hooks and I've read I also need to somehow compensate for React rendering the SVGs after the initial render - after useEffect is finished, but I can't find how to do this with react-testing-library and hooks?

This also does not work:

const avatarSVG = document.querySelector('MaleAvatar')

How can I grab the SVG components to check the correct one renders?

Ljubljana answered 12/4, 2021 at 14:19 Comment(9)
Does document.querySelector('.avatar svg') work?Theorist
No unfortunately - it no longer fails on the querySelector tho, now it fails on the expect statementLjubljana
Maybe using 'avatar' as both test id and class name is confusing... Did you try it with another class name?Theorist
If I give the parent div a test-id of test-svg-avatar then it fails on > 61 | const avatarSVG = screen.getByTestId('test-svg-avatar')Ljubljana
Another idea: use a shared className (for example 'avatar-icon') for both <MaleAvatar> and <FemaleAvatar> and check if it's present (always should be one)Theorist
I think the queryselector just returns null - svgs are loading after the useEffect hook so jest can't handle them. The common response seems to be to mock them, but I'm not sure how to use jest.mock('../../Assets/SVG/maleAvatar.js', () => () => null) with a testLjubljana
Where are you rendering the component you're testing? It's not visible from your test code.Sceptre
do the avatar components use react-svg to inject the svg? if so, you should be aware that xhr requests are involved, that you would have to mockMallorymallow
If the data-testid="avatar" element is rendered async after data loads, then that explains why your syncronous screen.getByTestId('avatar') call is failing. Did you try await screen.findByTestId('avatar')? More information seems needed to be able to answer this, i.e. a minimal reproducible example.Frodina
S
13

You can view the content of any elements with screen.debug(), in your case svg element and according to this content you can use a selector:

import { render, screen } from "@testing-library/react";
screen.debug() // it shows the "dom" 

In my case, I use FontAwesomeIcon and after to use debug I can see:

<svg
            aria-hidden="true"
            class="svg-inline--fa fa-fire fa-w-12 "
            color="#FF9C31"
            data-icon="fire"
            data-prefix="fas"
            focusable="false"
            role="img"
            viewBox="0 0 384 512"
            xmlns="http://www.w3.org/2000/svg"
          >
          
          <path
              d="M216 23.86c0-23.8-30.65-32.77-44.15-13.04C48 191.85 224 200 224 288c0 35.63-29.11 64.46-64.85 63.99-35.17-.45-63.15-29.77-63.15-64.94v-85.51c0-21.7-26.47-32.23-41.43-16.5C27.8 213.16 0 261.33 0 320c0 105.87 86.13 192 192 192s192-86.13 192-192c0-170.29-168-193-168-296.14z"
              fill="currentColor"
            />
          </svg>

So, I can use

const { container } = render(<MyComponent />);

And use some selector in my case [data-icon='fire']

const svgEl = container.querySelector("[data-icon='fire']") as HTMLImageElement;`

It is done, additionally, I can validate if it has a specific class

expect(svgEl.classList.toString()).toContain("fa-fire");
Snailpaced answered 15/4, 2022 at 16:14 Comment(0)
L
1

Have you tried adding a title prop to each svg? With that, you can use getByTitle to assert for svg. Something like

<div className='avatar'>
  {gender === true ? <MaleAvatar title="male" /> : <FemaleAvatar title="female" />}
</div>

And in your tests

expect(getByTitle('male')).toBeInTheDocument();
expect(findByTitle('female')).not.toBeInTheDocument();

The second test uses findByTitle because if it is not in the document, getBy* makes the test fails directly.

Note: to get this approach working, you need to import your SVG files as ReactComponent like this:

import { ReactComponent as FemaleAvatar } from 'assets/images/FemaleAvatar.svg';
import { ReactComponent as MaleAvatar } from 'assets/images/MaleAvatar.svg';
Leeuwenhoek answered 20/10, 2022 at 20:11 Comment(0)
G
1

I've found the better way to get svg byTitle https://testing-library.com/docs/queries/bytitle/

you need to add title tag in your svg.

so now you can reach your svg: screen.getByTitle(home icon/ui)

or you can get some other element by role with svg as its child.

ex. I have a link with svg icon as its child:

expect(screen.getByRole('link', { name: /home icon/ui })).toBeInTheDocument();
Glairy answered 26/6, 2023 at 19:8 Comment(0)
E
-8

You can put await to find the svg:

await screen.getByTestId('id')
Erepsin answered 12/10, 2021 at 7:40 Comment(2)
getBy* queries are not async. Did you mean findByTestId instead?Sceptre
This query is not user centric. You should not use test-idMellins

© 2022 - 2024 — McMap. All rights reserved.