4

Issue:

I have a list of Items that I want to test by each one of the items name value (string). I'm using @testing-library/react and have the test suite correctly working, but I can't get my test to work.

Overview:

  • Each item has a test id of data-testid="side-menu-link". Does this have to be unique or can it be tested as is?
  • The menuItems consist of strings like Dashboard, Settings, and User Preferences

DisplayItems.test.tsx:

// Imports: Dependencies
import React from 'react';
import { render, screen } from '@testing-library/react';

// Imports: App
import App from '../../App';

// Side Menu: Dashboard
test('Renders Dashboard correctly', () => {
  // Render: App
  const { getByTestId } = render(<App />);

  // Expect
  expect(getByTestId('side-menu-link')).toHaveAttribute('Dashboard')
});


// Side Menu: User Preferences
test('Renders Dashboard correctly', () => {
  // Render: App
  const { getByTestId } = render(<App />);

  // Expect
  expect(getByTestId('side-menu-link')).toHaveAttribute('User Preferences')
});

Map Items:

// Map Menu Items
return menuItems.map((menuItem, i) => {
  return (
    <Link data-testid="side-menu-link" key={i} href="#" className="side-menu-link" to={`/${menuItem.itemName}`}>
      <div className={props.currenttab === `${menuItem.itemName}` ? 'side-menu-item-container-selected-light' : 'side-menu-item-container-light'}>
        {menuItem.itemIcon}
        <p className={props.currenttab === `${menuItem.itemName}` ? 'side-menu-title-selected-light' : 'side-menu-title-light'}>{menuItem.itemName}</p>
      </div>
    </Link>
  );
});
jefelewis
  • 1,850
  • 1
  • 27
  • 63
  • 1
    Can you expand on "I can't get my test to work"? What exactly is not working? What error message are you seeing (if any)? – srk Dec 17 '20 at 21:42

2 Answers2

3

You can have multiple testIDs. Otherwise, there wouldn't be __AllByTestId selectors. The name wasn't well-thought out, it seems, because of similarity to HTML ids which must be unique.

A throw happens if you use __ByTestId yet you have multiple elements with the matching test id:

it("getByTestId will throw with multiple testIDs", () => {
  const {getAllByTestId, getByTestId} = render(
    <View>
      <Text testID="foo">a</Text>
      <Text testID="foo">b</Text>
    </View>
  );
  expect(getAllByTestId("foo")).toHaveLength(2); // OK
  getByTestId("foo"); // => Error: Found multiple elements with testID: foo
});

To test a map, you can add test ids to the children and use the above pattern.

React Native:

import "@testing-library/jest-native/extend-expect";

// ...

it("should find text content in all children", () => {
  const {getAllByTestId} = render(
    <View>
      {[..."abcd"].map((e, i) => 
        <View key={e + i} testID="foo"><Text>{e}</Text></View>
      )}
    </View>
  );

  expect(getAllByTestId("foo")).toHaveLength(4);
  
  [..."abcd"].forEach((e, i) => {
    expect(getAllByTestId("foo")[i]).toHaveTextContent(e);
  });
});

React:

it("should find text content in all children", () => {
  const {getAllByTestId} = render(
    <ul>
      {[..."abcd"].map((e, i) => 
        <li key={e + i} data-testid="foo">{e}</li>
      )}
    </ul>
  );

  expect(getAllByTestId("foo")).toHaveLength(4);

  [..."abcd"].forEach((e, i) => {
    expect(getAllByTestId("foo")[i].textContent).toEqual(e);
  });

  // or:
  //const contents = getAllByTestId("foo").map(e => e.textContent);
  //expect(contents).toEqual([..."abcd"]);
});

It's also possible to add a testID to the parent of the mapped list elements, select the parent, then traverse over its .children array to make assertions on each child tree.

Note the differences between testID in RN and data-testid in React.


As a side note, I'm not sure

expect(getByTestId('side-menu-link')).toHaveAttribute('User Preferences')

makes much sense here. attributes are <div this_is_an_attribute="foo"> -- you're probably looking for text content.


Packages used, for reference:

React Native

{
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-native": "^0.64.0",
    "react-native-web": "^0.15.6"
  },
  "devDependencies": {
    "@babel/core": "^7.13.15",
    "@testing-library/jest-native": "^4.0.1",
    "@testing-library/react-native": "^7.2.0",
    "babel-jest": "^26.6.3",
    "jest": "^26.6.3",
    "metro-react-native-babel-preset": "^0.65.2",
    "react-test-renderer": "^17.0.2"
  }
}

React

{
  "dependencies": {
    "@babel/runtime": "7.10.5",
    "react": "16.13.1",
    "react-dom": "16.13.1",
  },
  "devDependencies": {
    "@babel/core": "7.10.5",
    "@babel/plugin-proposal-class-properties": "7.10.4",
    "@babel/preset-env": "7.10.4",
    "@babel/preset-react": "7.10.4",
    "@testing-library/dom": "7.21.0",
    "@testing-library/jest-dom": "^5.11.1",
    "@testing-library/react": "10.4.7",
    "@testing-library/react-hooks": "3.3.0",
    "@testing-library/user-event": "12.0.11",
    "babel-jest": "26.1.0",
    "jest": "26.1.0",
    "jest-environment-jsdom": "26.1.0",
    "react-test-renderer": "16.13.1",
  }
}
ggorlen
  • 44,755
  • 7
  • 76
  • 106
-1

Each item has a test id of data-testid="side-menu-link". Does this have to be unique or can it be tested as is?

The test id has to be unique. I see you are using getByTestId. According to the documentation, "getBy* queries return the first matching node for a query, and throw an error if no elements match or if more than one match is found." So your code would throw an error (assuming the list contained more than a single element).

But even if RTL did not throw an error, it makes sense that the test ids should be unique. Otherwise, how would you differentiate between two different elements in your test?

If for some reason you still want the test id to be the same, you will need to use a different query, such as getAllBy*, queryBy*, or queryAllBy*. Again, see the documentation for more information.

srk
  • 1,625
  • 1
  • 10
  • 26