instea/react-native-popup-menu

Testing with Jest fireEvent.press()

charlestbell opened this issue · 2 comments

I mentioned this in #197 but that one is closed, so it neede a new issue.

Has anyone gotten Jest fireEvent.press() working?

In the testing example, they suggest mocking all the components like this:

jest.mock('react-native-popup-menu', () => ({
Menu: 'Menu',
MenuProvider: 'MenuProvider',
MenuOptions: 'MenuOptions',
MenuOption: 'MenuOption',
MenuTrigger: 'MenuTrigger',
}));
See Example: https://github.com/instea/react-native-popup-menu/blob/master/examples/__tests__/Basic-test.js

However, mocking all the components makes them no longer function. So trying to use fireEvent.press() on them to test actual functionality doesn't work at all.

I tried not mocking the components, but then Jest cannot find them at all, and the tests fail there, so I'm back to square one.

Update:
I found a kind of workaround. Basically, I just copied the same logic from the MenuOption's onSelect, to a Text or icon component child of MenuOption, in the form of an onPress. I also moved the testID tag to the child component.

Before:

<MenuOption
                     testID={`sharedScreen.${itemData.item.id}.removeButton`}
                      customStyles={optionShareStyles}
                      onSelect={() => {
                        removeShareHandler({
                          dispatch,
                          setIsRefreshing,
                          logbookId: itemData.item.id,
                        });
                      }}
                    >
                      <FontAwesomeIcon
                        icon={faEyeSlash}
                        color={Colors.neutral[1]}
                        size={20}
                        style={styles.menuIcon}
                      />
                      <Text
                        customStyles={optionsStyles}
                        style={styles.menuText}
                      >
                        Remove
                      </Text>
                    </MenuOption>

After:

 <MenuOption
                      customStyles={optionShareStyles}
                      onSelect={() => {
                        removeShareHandler({
                          dispatch,
                          setIsRefreshing,
                          logbookId: itemData.item.id,
                        });
                      }}
                    >
                      <FontAwesomeIcon
                        testID={`sharedScreen.${itemData.item.id}.removeButton`} // Moved the testId here
                        onPress={() => { // Copied the onSelect logic to this onPress to make testing work
                          removeShareHandler({
                            dispatch,
                            setIsRefreshing,
                            logbookId: itemData.item.id,
                          });
                        }}
                        icon={faEyeSlash}
                        color={Colors.neutral[1]}
                        size={20}
                        style={styles.menuIcon}
                      />
                      <Text
                        customStyles={optionsStyles}
                        style={styles.menuText}
                      >
                        Remove
                      </Text>
                    </MenuOption>

So now there are two copies of the onPress logic and it looks like hell, but it works!

A snippet from the test:

  const removeButton = await findByTestId(
    `sharedScreen.${mockLogbookKey}.removeButton`
  );
  fireEvent.press(removeButton);

Note: I am using the jest.mock from my previous comment to make this work.

If anybody comes up with a better way, I'm all ears.

I struggled with this for a while, but I think I found an excellent way to test this out. The only issue with your method is that when you put onPress logic onto a child component, sometimes the MenuOption will not be called and in turn won't close the pop-up menu, it will perform the task because the onPress was clicked on but won't close the menu for the user. To test this on jest you can do something like this to mock the component:

const mockedOnPress = jest.fn();
const mockedMenuOption = (
  <TouchableOpacity
    testID="menuOption"
    onPress={mockedOnPress}
  ></TouchableOpacity>
);
jest.mock("react-native-popup-menu", () => ({
  Menu: "Menu",
  MenuContext: "MenuContext",
  MenuOptions: "MenuOptions",
  MenuOption: () => mockedMenuOption,
  MenuTrigger: "MenuTrigger",
  renderers: "renderers",
}));

then to test this you can try something like this

it("tests the onPress of MenuOption", async () => {
    const { getAllByTestId } = render(
      <YourComponentHere />
    );
    const button = getAllByTestId("menuOption");
    fireEvent.press(button[0]);
    expect(mockedOnPress).toHaveBeenCalled();
  });

You can see that I used getAllByTestId this is because if we render multiple MenuOptions for the user, they will all have the same testId so this will allow us to get a specific one depending on the order starting from the 0 index.