3

I'm attempting to test a Select input inside an Ant Design Form filled with initialValues and the test is failing because the Select does not receive a value. Is there a best way to test a "custom" rendered select?

Test Output:

Error: expect(element).toHaveValue(chocolate)

Expected the element to have value:
  chocolate
Received:

Example Test:

import { render, screen } from '@testing-library/react';
import { Form, Select } from 'antd';

const customRender = (ui: React.ReactElement, options = {}) => render(ui, {
  wrapper: ({ children }) => children,
  ...options,
});

describe('select tests', () => {
  it('renders select', () => {
    const options = [
      { label: 'Chocolate', value: 'chocolate' },
      { label: 'Strawberry', value: 'strawberry' },
      { label: 'Vanilla', value: 'vanilla' },
    ];
    const { value } = options[0];

    customRender(
      <Form initialValues={{ formSelectItem: value }}>
        <Form.Item label="Form Select Label" name="formSelectItem">
          <Select options={options} />
        </Form.Item>
      </Form>,
    );

    expect(screen.getByLabelText('Form Select Label')).toHaveValue(value);
  });
});

2 Answers 2

1

I mocked a normal select and was able to get everything working.

The following example utilizes Vitest for a test runner but should apply similar to Jest.

antd-mock.tsx

import React from 'react';
import { vi } from 'vitest';

vi.mock('antd', async () => {
  const antd = await vi.importActual('antd');

  const Select = props => {
    const [text, setText] = React.useState('');
    const multiple = ['multiple', 'tags'].includes(props.mode);

    const handleOnChange = e => props.onChange(
      multiple
        ? Array.from(e.target.selectedOptions)
          .map(option => option.value)
        : e.target.value,
    );

    const handleKeyDown = e => {
      if (e.key === 'Enter') {
        props.onChange([text]);
        setText('');
      }
    };

    return (
      <>
        <select
          // add value in custom attribute to handle async selector,
          // where no option exists on load (need to type to fetch option)
          className={props.className}
          data-testid={props['data-testid']}
          data-value={props.value || undefined}
          defaultValue={props.defaultValue || undefined}
          disabled={props.disabled || undefined}
          id={props.id || undefined}
          multiple={multiple || undefined}
          onChange={handleOnChange}
          value={props.value || undefined}
        >
          {props.children}
        </select>
        {props.mode === 'tags' && (
          <input
            data-testid={`${props['data-testid']}Input`}
            onChange={e => setText(e.target.value)}
            onKeyDown={handleKeyDown}
            type="text"
            value={text}
          />
        )}
      </>
    );
  };

  Select.Option = ({ children, ...otherProps }) => (
    <option {...otherProps}>{children}</option>
  );
  Select.OptGroup = ({ children, ...otherProps }) => (
    <optgroup {...otherProps}>{children}</optgroup>
  );

  return { ...antd, Select };
});

utils.tsx

import { render } from '@testing-library/react';
import { ConfigProvider } from 'antd';

const customRender = (ui: React.ReactElement, options = {}) => render(ui, {
  wrapper: ({ children }) => <ConfigProvider prefixCls="bingo">{children}</ConfigProvider>,
  ...options,
});

export * from '@testing-library/react';
export { default as userEvent } from '@testing-library/user-event';
export { customRender as render };

Select.test.tsx

import { Form } from 'antd';
import { render, screen, userEvent } from '../../../test/utils';
import Select from './Select';

const options = [
  { label: 'Chocolate', value: 'chocolate' },
  { label: 'Strawberry', value: 'strawberry' },
  { label: 'Vanilla', value: 'vanilla' },
];
const { value } = options[0];
const initialValues = { selectFormItem: value };

const renderSelect = () => render(
  <Form initialValues={initialValues}>
    <Form.Item label="Label" name="selectFormItem">
      <Select options={options} />
    </Form.Item>
  </Form>,
);

describe('select tests', () => {
  it('renders select', () => {
    render(<Select options={options} />);
    expect(screen.getByRole('combobox')).toBeInTheDocument();
  });

  it('renders select with initial values', () => {
    renderSelect();
    expect(screen.getByLabelText('Label')).toHaveValue(value);
  });

  it('handles select change', () => {
    renderSelect();
    expect(screen.getByLabelText('Label')).toHaveValue(value);
    userEvent.selectOptions(screen.getByLabelText('Label'), 'vanilla');
    expect(screen.getByLabelText('Label')).toHaveValue('vanilla');
  });
});
Sign up to request clarification or add additional context in comments.

Comments

0

testing a library component may be harsh sometimes because it hides internal complexity. for testing antd select i suggest to mock it and use normal select in your tests like this:

jest.mock('antd', () => {
        const antd = jest.requireActual('antd');

        const Select = ({ children, onChange, ...rest }) => {
            return <select role='combobox' onChange={e => onChange(e.target.value)}>
                    {children}
                </select>;
        };

        Select.Option = ({ children, ...otherProps }) => {
            return <option role='option' {...otherProps}}>{children}</option>;
        }

        return {
            ...antd,
            Select,
        }
    })

this way you can test the select component as a normal select (use screen.debug to check that the antd select is mocked)

1 Comment

This works great for mocking the select but doesn't actually pass the form's initialValues props and still fails assertion.

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.