Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Native Test Button Press

I am trying to test calling a component method from a React Native Button element.

For some reason, the test fails unless I do BOTH of these things.

wrapper.find(Button).first().props().onPress();
wrapper.find(Button).first().simulate('press');

If I comment out either of the lines, the test fails indicating that expect(instance.toggleEmailPasswordModal).toHaveBeenCalled(); failed.

Here is my component:

import React, { Component } from 'react';
import { Button, SafeAreaView, Text } from 'react-native';

import EmailPasswordModal from './EmailPasswordModal/EmailPasswordModal';

class Login extends Component {
  state = {
    emailPasswordModalVisible: false,
  };

  toggleEmailPasswordModal = () => {
    console.log('TOGGLED!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
    const { emailPasswordModalVisible } = this.state;
    this.setState({ emailPasswordModalVisible: !emailPasswordModalVisible });
  };

  render() {
    const { emailPasswordModalVisible } = this.state;
    return (
      <SafeAreaView>

        <EmailPasswordModal
          visible={ emailPasswordModalVisible }
          close={ this.toggleEmailPasswordModal }
        />

        <Text>Login Screen!</Text>

        <Button
          onPress={ this.toggleEmailPasswordModal }
          title="Login with Email and Password"
          color="#841584"
          accessibilityLabel="Login with Email and Password"
        />

      </SafeAreaView>
    );
  }
}

export default Login;

Here is my test:

import React from 'react';
import ShallowRenderer from 'react-test-renderer/shallow';
import { shallow } from 'enzyme';
import { Button } from 'react-native';

import Login from './Login';

describe('Login Screen', () => {
  describe('Snapshot Tests', () => {
    it('renders the screen with default state', () => {
      const renderer = new ShallowRenderer();
      const props = {};

      renderer.render(<Login { ...props } />);
      expect(renderer.getRenderOutput()).toMatchSnapshot();
    });
  });

  describe('Functional Tests', () => {
    it('calls the toggleEmailPasswordModal method', () => {
      const wrapper = shallow(<Login />);
      const instance = wrapper.instance();
      jest.spyOn(instance, 'toggleEmailPasswordModal');
      wrapper.find(Button).first().props().onPress();
      wrapper.find(Button).first().simulate('press');
      expect(instance.toggleEmailPasswordModal).toHaveBeenCalled();
    });
  });
});

Oddly, when the test runs, the output shows "TOGGLED!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" twice because of the logging in the component.

However, if I change the expect to :

expect(instance.toggleEmailPasswordModal).toHaveBeenCalledTimes(1);

the test passes.

If I change the expect to :

expect(instance.toggleEmailPasswordModal).toHaveBeenCalledTimes(2);

the test fails saying toggleEmailPasswordModal was only called 1 time.

Why do I need BOTH of those wrapper.find(Button)... lines? I've never seen any other tests requiring both of them.

Thanks, Justin

UPDATE:

I updated my test as follows:

it('calls the toggleEmailPasswordModal method', () => {
  const wrapper = shallow(<Login />);
  const instance = wrapper.instance();
  jest.spyOn(instance, 'toggleEmailPasswordModal');
  wrapper.find(Button).first().props().onPress();
  wrapper.find(Button).first().simulate('press');
  expect(instance.toggleEmailPasswordModal).toHaveBeenCalled();

  // I ADDED THIS SECTION HERE
  expect(instance.state.emailPasswordModalVisible).toBe(true);
});

The test fails because instance.state.emailPasswordModalVisible = false. That's strange as the toggleEmailPasswordModal apparently is called. However, since I suspect it's actually being called twice, I update the test as follows:

it('calls the toggleEmailPasswordModal method', () => {
  const wrapper = shallow(<Login />);
  const instance = wrapper.instance();
  jest.spyOn(instance, 'toggleEmailPasswordModal');
  wrapper.find(Button).first().props().onPress();

  // CHANGES START HERE
  // wrapper.find(Button).first().simulate('press');
  // expect(instance.toggleEmailPasswordModal).toHaveBeenCalled();
  expect(instance.state.emailPasswordModalVisible).toBe(true);
});

Guess what? The test passes properly. So CLEARLY calling the wrapper.find... functions twice truly is calling the toggleEmailPasswordModal method twice. So, why does it fail to detect it if I don't call twice? Why does it improperly believe the method has only been called once?

like image 238
Justin Noel Avatar asked Sep 05 '25 09:09

Justin Noel


2 Answers

I have an answer finally. According to Jest spyOn function called, I need to do instance.forceUpdate() to attach the spy to the component.

it('calls the toggleEmailPasswordModal method', () => {
  const wrapper = shallow(<Login />);
  const instance = wrapper.instance();
  const spy = jest.spyOn(instance, 'toggleEmailPasswordModal');

  // This is added per https://stackoverflow.com/questions/44769404/jest-spyon-function-called/44778519#44778519
  instance.forceUpdate();
  wrapper.find(Button).first().props().onPress();

  expect(spy).toHaveBeenCalledTimes(1);
  expect(instance.state.emailPasswordModalVisible).toBe(true);
});

Now, the test passes!

like image 139
Justin Noel Avatar answered Sep 08 '25 10:09

Justin Noel


Another example for whom looking for events:

const button = screen.getByTestId('...');
act(() => {
  fireEvent.press(button);
});
expect(hookFn).toBeCalled();
like image 33
Nagibaba Avatar answered Sep 08 '25 11:09

Nagibaba