Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to properly mock Select and Option Component with jest. #21080

Closed
1 task done
gwuah opened this issue Jan 22, 2020 · 13 comments
Closed
1 task done

How to properly mock Select and Option Component with jest. #21080

gwuah opened this issue Jan 22, 2020 · 13 comments

Comments

@gwuah
Copy link

gwuah commented Jan 22, 2020

  • I have searched the issues of this repository and believe that this is not a duplicate.

Reproduction link

Edit on CodeSandbox

Steps to reproduce

I want to be able to mock my select and option component. The code below is my best attempt, but it's still not working.

interface CustomSelectProps extends SelectProps<string> {
  name?: string;
  onChange: (value: string) => void;
}

interface MockFnReturnType {
  render: (props: CustomSelectProps) => JSX.Element;
  Option: (props: OptionProps) => JSX.Element;
}


jest.mock(
  "../../../../node_modules/antd/lib/select",
  (): MockFnReturnType => {
    function Option(props: OptionProps): React.ReactElement {
      return <option value={props.value}></option>;
    }

    function Select(props: CustomSelectProps): JSX.Element {
      function handleChange(): void {
        props.onChange && props.onChange("3443");
      }
      return (
        <select
          data-testid={props.placeholder}
          value={props.value}
          onChange={handleChange}
        >
          <option key="1" value="2">
            3
          </option>
        </select>
      );
    }
    return {
      Option,
      render: Select,
    };
  },
);

What is expected?

I expect my custom select component and option component to be rendered instead of the default antd component.

What is actually happening?

I expect both Option and Select to be mocked properly, but on the contrary, it isn't working.
Message : Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object

Environment Info
antd 3.26.7
React 16.12.0
System MacOS Mojave
Browser Chrome

I'm using react-testing-library to write my tests

@gwuah gwuah changed the title How to properly mock Select and Option Component How to properly mock Select and Option Component with jest. Jan 22, 2020
@zombieJ
Copy link
Member

zombieJ commented Jan 24, 2020

Please follow jest doc for more detail. It's not antd issue.

@zombieJ zombieJ closed this as completed Jan 24, 2020
@alibenmessaoud
Copy link

@gwuah Do you found a solution to mock the Select component with RTL ?

@gwuah
Copy link
Author

gwuah commented Mar 3, 2020

@alibenmessaoud no bro. I ended up using enzyme to test. It never found out how to mock both the select and option component properly

@wildan2711
Copy link

wildan2711 commented Jun 12, 2020

Here is how to mock Select and Option.

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

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

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

@raditya-pratama
Copy link

nice solution from @wildan2711 !!!
works for me

@AZReed
Copy link

AZReed commented Jul 16, 2020

Here is how to mock Select and Option.

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

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

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

It worked great @wildan2711 . Thanks. Do you know by any change how to put this under __mocks__/antd.js? I have to put it with as a plain JS object apparently from what I can make of the errors, but all the combinations that I tried don't work.

@florianMo
Copy link

florianMo commented Dec 3, 2020

Based on @wildan2711 nice solution (thanks !), this is a more complete one, supporting multiple selects, OptGroup and a few antd props :

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

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

    return (
      <select
        value={props.value}
        defaultValue={props.defaultValue}
        multiple={multiple}
        disabled={props.disabled}
        data-testid={props['data-testid']}
        className={props.className}
        onChange={(e) =>
          props.onChange(multiple ? Array.from(e.target.selectedOptions).map((option) => option.value) : e.target.value)
        }
      >
        {props.children}
      </select>
    );
  };

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

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

It is worth noting that you can easily disable this mock by adding jest.unmock('antd') at the top of any test file.

@Gibbo3771
Copy link

@florianMo This solution looks perfect but I just can't get it to work.

I am have my custom component that renders a whole bunch of stuff, and among the stuff in there is a Select with many Select.Option children.

In my test, I am just ensuring that certain events are triggered when the person selects something from the list and then clicks OK. However, the above stub just doesn't replace the Select. I can log the HTML out and see it's still the same antd markup with the hidden options (I am also confused why this is like this, how can a screen reader work with this?).

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

      const Select = (props: any) => {
        const multiple = ["tags", "multiple"].includes(props.mode);

        return (
          <select
            value={props.value}
            defaultValue={props.defaultValue}
            multiple={multiple}
            disabled={props.disabled}
            data-testid={props["data-testid"]}
            className={props.className}
            onChange={(e) =>
              props.onChange(
                multiple
                  ? Array.from(e.target.selectedOptions).map(
                      (option) => option.value
                    )
                  : e.target.value
              )
            }
          >
            {props.children}
          </select>
        );
      };

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

      return { ...antd, Select };
    });
    const handleRedeem = sinon.mock();

    const component = render(
      <GiftManagementPanel
        onRedeemedConfirmed={handleRedeem}
        gifts={[gift]}
        businesses={[business]}
      />
    );

    // Ensure both the gift "type" and redeem button are present
    await screen.findByTestId(
      `gift-${gift.id}-${gift.data.properties.validFor}`
    );
    const redeemButton = await screen.findByLabelText("Redeem");
    userEvent.click(redeemButton);
    const modal = await screen.findByRole("document");

    const selectMenu = await findByRole(modal, "combobox"); // Finds this, but it's not the stubbed version above
    const options = Array.from(selectMenu.querySelectorAll("option")).map((o) =>
      o.getAttribute("value")
    );
    expect(options).toEqual(["HAHAHA"]);
    expect(selectMenu).toHaveValue("HAHAHA");

Is this because my component is initializing with the original select first? Where should I put this stub in a normal CRA where it is loaded first?

@florianMo
Copy link

@Gibbo3771 in my case (regular CRA app), I have this mock in src/setupTests.js, according to https://create-react-app.dev/docs/running-tests/#initializing-test-environment.

I did a quick test, it works fine if I add my mock at the top of my test file :

jest.mock("antd", () => { ... });

describe('...', () => {
  test(...);
});

But it does not work if I add it in a specific test :

jest.mock("antd", () => { ... });

describe('...', () => {
  test('something', () => {
    jest.mock("antd", () => { ... });
    expect(...);
  });
});

@Gibbo3771
Copy link

@florianMo Thanks for the very fast response. Hoisting it to the top of the file, outside of the test does indeed fix it. I am probably going to assume this is why my sinon stubs were not working as well. I am considering wrapping this selector in it's own component and stubbing that instead, that allows me to stub it at any point in the test rather than only at the top.

@akashingole1
Copy link

You can also mock List from antd like this -

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

const List = (props) => {
    return (
        <ul>
            {props.children}
        </ul>
    )
}
List.Item = ({children, ...otherProps}) => <li {...otherProps}>{children}</li>
return {...antd, List}

})`

@gmichelassi
Copy link

Based on everybody replies this Dragger (from antd Upload) should work?

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

  const MockedDragger = ({
    onChange,
    ...otherProps
  }: {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    onChange: ((info: UploadChangeParam<UploadFile<any>>) => void) | undefined;
  }) => {
    console.log('render mock');
    return (
      <Dragger
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        onChange={(info: UploadChangeParam<UploadFile<any>>) =>
          onChange({
            ...info,
            file: {
              ...info.file,
              status: 'done',
            },
          })
        }
        // eslint-disable-next-line react/jsx-props-no-spreading
        {...otherProps}
      />
    );
  };

  Upload.Dragger = MockedDragger;

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

This is placed outside of the tests but does not appear to have any effect

@florianMo
Copy link

Adding a test input to support tag mode / search behaviour :

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

  // <Select />
  // Testing mode="tags" : see OdLinkedRelation.test.tsx
  const Select = (props) => {
    const [text, setText] = React.useState('');
    const multiple = ['tags', 'multiple'].includes(props.mode);

    const handleKeyDown = (event) => {
      if (event.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)
          data-value={props.value || undefined}
          value={props.value || undefined}
          defaultValue={props.defaultValue || undefined}
          multiple={multiple || undefined}
          disabled={props.disabled || undefined}
          data-testid={props['data-testid']}
          className={props.className}
          onChange={(e) =>
            props.onChange(
              multiple ? Array.from(e.target.selectedOptions).map((option) => option.value) : e.target.value
            )
          }
        >
          {props.children}
        </select>
        {props.mode === 'tags' && (
          <input
            data-testid={props['data-testid'] + 'Input'}
            type="text"
            value={text}
            onChange={(e) => setText(e.target.value)}
            onKeyDown={handleKeyDown}
          />
        )}
      </>
    );
  };

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants