react-native-testing-library: Error for every fireEvent changing state in a component with a Switch

Versions

"react": "16.11.0",
"react-native": "0.62.0"
"react-native-testing-library": "1.14.0",
"react-test-renderer": "16.11.0"

Description

I’m getting a console error for every fire event that changes the component state in a component with a Switch.

console.error node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js:608
  Warning: dispatchCommand was called with a ref that isn't a native component. Use React.forwardRef to get access to the underlying native component

Screenshot 2020-05-18 at 18 30 48

Reproducible Demo

https://github.com/Pardiez/rn-testing-library-329

// src/Dummy/__ tests __/Dummy.test.js

import React from 'react';
import { render, fireEvent } from 'react-native-testing-library';
import Dummy from '../';

describe('Dummy component', () => {
  test('renders correctly', () => {
    const { queryByTestId } = render(<Dummy />);

    fireEvent.changeText(queryByTestId('text-input'), 'stage');
    fireEvent.press(queryByTestId('touchable-opacity'));
    expect(queryByTestId('text-input')).toBeTruthy(); // dummy expect

  });
});
// src/Dummy/index.js

import React, { Component } from 'react';
import { View, TextInput, Switch, TouchableOpacity } from 'react-native';

class Dummy extends Component {
  constructor(props) {
    super(props);
    this.state = {
      text:'',
    };
  }

  render() {
    return (
      <View>
        <TextInput
          onChangeText={(text) => {this.setState({text})}}
          testID="text-input" />

        <TouchableOpacity
          onPress={() => {this.setState({text: ''})}}
          testID="touchable-opacity" />
          
        <Switch />
      </View>
    );
  }
}

export default Dummy;

Without setState on the fired event or without the Switch, there is no error. This error occurs only with React Native 0.62+

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 8
  • Comments: 33 (10 by maintainers)

Most upvoted comments

FYI: mocking with the following works (https://jestjs.io/docs/en/tutorial-react-native#mock-native-modules-using-jestmock)

jest.mock('react-native/Libraries/Components/Switch/Switch', () => {
    const mockComponent = require('react-native/jest/mockComponent')
    return mockComponent('react-native/Libraries/Components/Switch/Switch')
})

Edit:

As mentioned by @kptp below, since 0.66 you need to do the following instead:

jest.mock('react-native/Libraries/Components/Switch/Switch', () => {
  const mockComponent = require('react-native/jest/mockComponent')
  return {
    default: mockComponent('react-native/Libraries/Components/Switch/Switch')
  }
})

This issue popped up for me again after updating react-native to version 0.66.2. The fix was to change the mock to return a default export like so:

jest.mock('react-native/Libraries/Components/Switch/Switch', () => {
  const mockComponent = require('react-native/jest/mockComponent');
  return {
    default: mockComponent('react-native/Libraries/Components/Switch/Switch'),
  };
});

@Pardiez I got the same error:

  console.error node_modules/react-native/Libraries/LogBox/LogBox.js:33
    Warning: dispatchCommand was called with a ref that isn't a native component. Use React.forwardRef to get access to the underlying native component

Effectively it seems to be link with the Switch component. When I removed it, there is no more error. Maybe it could be link with native stuff of the Switch component. There is a old post (maybe link) about this: https://stackoverflow.com/questions/47058905/using-react-test-renderer-with-switch-causes-stateless-function-components-ca

To pass through this warning, I mock the Switch component. I add at my root dir this file __mocks__/react-native.js with the code below (the Platform was for another purpose):

import React, {useState} from 'react';
import * as ReactNative from 'react-native';

export const Platform = {
  ...ReactNative.Platform,
  OS: 'android',
  Version: 123,
  select: objs => objs['android'],
};

export const Switch = props => {
  const [value, setValue] = useState(props.value);
  return (
    <ReactNative.TouchableOpacity
      onPress={() => {
        props.onValueChange(!value);
        setValue(!value);
      }}
      testID={props.testID}>
      <ReactNative.Text>{value.toString()}</ReactNative.Text>
    </ReactNative.TouchableOpacity>
  );
};

let newRN = Object.defineProperty(ReactNative, 'Platform', {
  get: function() {
    return Platform;
  },
});
newRN = Object.defineProperty(ReactNative, 'Switch', {
  get: function() {
    return Switch;
  },
});

module.exports = newRN;

In jest I can fire the event with:

    const mySwitchElement = tree.getByTestId(
      'mySwitch',
    );
    fireEvent(mySwitchElement, 'onPress', []);

I tried this and it works like a charm, https://github.com/callstack/react-native-testing-library/issues/329#issuecomment-646610078

thank you @Merlier

however, instead of creating __mocks__/react-native.js file, I edit my jest-setup.js on my root folder. My jest config on my package.json points it as setupFiles

"jest": {
    "setupFiles": [
      "<rootDir>/jest-setup.js"
    ]
  }

here is the glimpse of my jest-setup.js

const React = require('react')
const ReactNative = require('react-native')

const Switch = function(props) {
  const [value, setValue] = React.useState(props.value)

  return (
    <ReactNative.TouchableOpacity
      onPress={() => {
        props.onValueChange(!value)
        setValue(!value)
      }}
      testID={props.testID}>
      <ReactNative.Text>{value.toString()}</ReactNative.Text>
    </ReactNative.TouchableOpacity>
  )
}

Object.defineProperty(ReactNative, 'Switch', {
  get: function() {
    return Switch
  }
})

@Pardiez I was able to reproduce your issue using your code. Everything is a you diagnosed it, removing <Switch> or setState calls prevents the message.

So I’ve made a little experiment and swapped setState with forceUpdate calls that simply trigger render, and the message also appears. Moreover, when I ran your code as an app (not test) the warning does not appear.

This makes me believe that warning is caused by re-render of Switch component under react-test-renderer.

I’ve also found relevant commit in react-test-renderer react-native-renderer source: https://github.com/facebook/react/pull/16085/files

@thymikee maybe you can see using the linked PR if we can and/or should somehow mitigate it in RNTL.

I’m facing the same issue with the <Switch /> component making use of the onValueChange prop.

// test.js
...
const switchBtn = await findByA11yRole('switch');
fireEvent(switchBtn, 'onValueChange');
...

Remember you’re running your test in different environment (Node) than your emulator (JSC/Hermes). There may be slight differences, not sure if that’s the case though. Let’s wait for somebody to triage and investigate this. cc @mdjastrzebski @cross19xx

Based on https://github.com/callstack/react-native-testing-library/issues/329#issuecomment-665367487 I’d say it’s an upstream issue. You can investigate the source of the problem in the RN mocks, if it’s not resolved on master branch already.

It’s not related to this library, rather the react-native version itself. It’s a warning, so I wouldn’t bother too much.

@TheSavior ideas about why this warning triggers for a native component in test environment? I’ve also tried this with 0.63, and latest nightly, the warning stays the same (minus component stack). Hope you have some additional context so we don’t have to dig too far 😃