3

I'd like to test the selection logic of my Select component. https://codesandbox.io/s/eager-violet-kkw0x?file=/src/App.js

I found this snippet to test a simulation.

test("simulates selection", () => {
  const { getByTestId, getAllByTestId } = render(<Select />);

  fireEvent.change(getAllByTestId("select"), { target: { value: 2 } });
  let options = getAllByTestId("select-option");
  expect(options[0].selected).toBeFalsy();
  expect(options[1].selected).toBeTruthy();
  expect(options[2].selected).toBeFalsy();
});

However, it fails with TypeError: Cannot read property 'map' of undefined. What's going on? Thank you.

0

1 Answer 1

4

Short answer: you need to make sure you do a conditional check to skip whenever your values are undefined. You can do so by sticking in values && just before values.map(...

Secondly, I really don't see the value of using a two dimensional array just for having an index. You can store a list of fruits in an array and iterate through with a simple map, passing in the value both as a value and as an key. If you want to go fancy with the key, you can even assign the number increment that is generated by the .map()'s index along with the value, here is the fancy version:

        {values && values.map((index, value) => (
            <option key={`${index}-${value}`} value={value} data-testid="select">
                {value}
            </option>
        ))}

The above would generate a key like fruit-1, anotherfruit-2, etc, however you can just go with the fruit name, whatever you do, make sure you don't just go with the index number as that is not a good practice. This is all when you have a simple array, as I said, you don't need a two dimensional one.

Moving forward, there are a lot of issues in your test and some problems in the code, so it won't work unless reimagining the whole code and test, however I give my best to try and explain some of the problems and point you to the right direction:

First line with the issue is:
fireEvent.change(getAllByTestId("select"), { target: { value: 2 } });

You want to select one element, that is the <select /> so you need to use getByTestId instead of getAllByTestId, you also got the id wrong, it is select-option.

The correct format looks like this:

fireEvent.change(getByTestId("select-option"), { target: { value: 2 } });

Just a quick note, while the above works, there are other, better ways of doing this, I recommend looking into the user-event library instead of fireEvent, but most importantly, use a getByRole instead of getByTestId, read why

Next problem is that you haven't passed in your props to your select component, therefore there are no <option> elements when rendered. You couldn't do this mistake with something like TypeScript as it would warn you, but JavaScript doesn't, so.. need to pass in the props:

 const props = {
        callback: jest.fn(),
        values: [
            "grapefruit", "coconut", "lime", "mango"
        ],
        selected: 'lime'
    }

    const { getByTestId, getAllByTestId } = render(<Select {...props} />);

Moving forward, when collecting options, you used the wrong ID again (I think you mixed up the two) and I would also recommend using queryAll rather than getAll, this is because queryAll would return an empty array while getAll would throw an error.

const options = queryAllByTestId("select");

Finally your assertions are all wrong as well. Your option won't have a selected attribute what then you can boolean evaluate. Your option has two attributes data-testid and value.

I tried to give you the best answer to understand what is going on, but as the code stands, it's impossible to fix it without rethinking the whole app and test, like I mentioned above so here is my advice:

In the components:

  • Change to two dimensional array and use the value as the index.

  • You are not doing anything with the selected value, just console logging out, if you pass it back to your parent component, save it into the parent state and do whatever you want with it, Probably display somewhere - this is actually important for the test.

  • selected prop has a string, that will probably need to have the value that you pass back and save it to the state in the parent.

In the test:

React testing library is great because your tests resemble to how the users would use the app, therefore you should assert if the selected component appears where you want, instead of trying to inspect attributes, that would work the following way:

  • Make sure you render the component that will have both the select component and the component you will render the selected value to as text.

  • Make sure you pass in all props to the component you rendering with rtl

  • Simulate action (recommend the user-event library, but fireEvent will also work)

  • use the getByText to see if the value rendered and exists on the page. That would be something like: expect(getByText('fruitName')).toBeInTheDocument();

  • Finally, I recommend looking into the selector precedence (use getByRole where you can) and the difference between getBy and queryBy.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.