模拟和监视键盘事件,并在本地进行响应

时间:2018-10-11 18:43:46

标签: unit-testing react-native mocking jestjs

给出以下代码:

import { Keyboard } from 'react-native';

// ....

componentDidMount() {
  this.keyboardShowListener = Keyboard.addListener(
    'keyboardWillShow',
    () => this.setState({ visible: true }),
  );
  this.keyboardHideListener = Keyboard.addListener(
    'keyboardWillHide',
    () => this.setState({ visible: false }),
  );
}

// ....

onCancel() {
  const { clearActiveInput } = this.props;
  clearActiveInput();
  Keyboard.dismiss();
}

是否有一种模拟导入的Keyboard组件的正确方法,既可以验证侦听器订阅是否已发生,又可以验证dismiss()事件是否已触发?

3 个答案:

答案 0 :(得分:3)

所以这个问题比我想像中要解决的复杂得多。 既然您要在这里测试的是基本上针对键盘的dismiss()和show()呢?

因此,文档中0.63和0.62的事件和侦听器为https://reactnative.dev/docs/keyboard#docsNav

useEffect(() => {
   Keyboard.addListener("keyboardDidShow", _keyboardDidShow);
   Keyboard.addListener("keyboardDidHide", _keyboardDidHide);

   // cleanup function
   return () => {
       Keyboard.removeListener("keyboardDidShow", _keyboardDidShow);
       Keyboard.removeListener("keyboardDidHide", _keyboardDidHide);
   };
}, []);

const _keyboardDidShow = () => {
    alert("Keyboard Shown");
};

const _keyboardDidHide = () => {
    alert("Keyboard Hidden");
};

要使Jest调用两个函数_keyboardDidShow_keyboardDidHide,您将需要使用Keyboard.emit('_keyboardDidShow')

示例:

it('Test Keyboards keyboardDidShow is called', () => {
    const { getByTestId } = render(<Container />);
    act(() => {
        Keyboard.emit('keyboardDidShow', {});
    });
    const box = getByTestId('TEST');
    //Do here your expect clauses to check if something changed in your container
});

不完全确定这是否能帮助任何人。但这就是我解决这个难题的方法,以弄清楚如何覆盖_keyboardDidShow和Hide

答案 1 :(得分:1)

我在订阅Keyboard事件的组件中也遇到了类似的问题

const MyComponent = () => {
    useEffect(() => {
        const listener = Keyboard.addListener('keyboardDidHide', () => {})

        return () => {
            listener.remove()
        }
    })

    return <View>...</View>
}

我可以通过以下测试来测试Keyboard.addListener,还可以测试组件卸载后正在调用listener.remove

import renderer from 'react-test-renderer'

const mockListener = {
    remove: jest.fn(),
}
const originalAddListener = Keyboard.addListener
const mockAddListener = jest.fn().mockReturnValue(mockListener)

describe('<MyComponent />', () => {
    beforeAll(() => {
        Keyboard.addListener = mockAddListener
    })
    beforeEach(() => {
        mockAddListener.mockClear()
        mockListener.remove.mockClear()
    })
    afterAll(() => {
        Keyboard.addListener = originalAddListener
    })
    it('should subscribe to KeyboardDidClose event', () => {
        render(<MyComponent />)
        expect(Keyboard.addListener).toHaveBeenCalled()
    })

    it('should call listener.remove on unmount', () => {
        const component = render(
            < MyComponent />,
        )
        component.unmount()
        expect(mockListener.remove).toHaveBeenCalled()
    })
})

答案 2 :(得分:0)

编辑:请使用@Thiago de Oliveira Cruz 提供的方法,该方法更简洁、更易于使用。

我使用 this article 中提到的方法来“触发”'keyboardDidShow''keyboardDidHide' 事件。基本上,我们模拟实现 keyboard.addListener 并使用映射来记录事件到回调的映射。这样,在安装组件后,我们就可以访问与 'keyboardDidShow''keyboardDidHide' 事件关联的回调。然后我们直接调用回调函数,就好像事件已经被触发一样。

这显然是一种解决方法,但它适用于我的用例。下面是一个简单的示例,说明如何测试使用 'keyboardDidShow''keyboardDidHide' 事件来控制是否显示一段文本的组件。有关示例的详细说明,请参阅 here

示例组件:

import * as React from 'react';
import {View, Text, Keyboard} from 'react-native';

const MyComponent = () => {
  const [showText, setShowText] = React.useState(true);

  // Use keyboard event (https://reactnative.dev/docs/keyboard)
  React.useEffect(() => {
    Keyboard.addListener('keyboardDidShow', _keyboardDidShow);
    Keyboard.addListener('keyboardDidHide', _keyboardDidHide);

    // cleanup function
    return () => {
      Keyboard.removeListener('keyboardDidShow', _keyboardDidShow);
      Keyboard.removeListener('keyboardDidHide', _keyboardDidHide);
    };
  }, []);

  const _keyboardDidShow = () => setShowText(false);
  const _keyboardDidHide = () => setShowText(true);

  if (showText) {
    return (
      <View>
        <Text>Bear, Beets, Battlestar Galactica</Text>
      </View>
    );
  }
  return null;
};

export {MyComponent};

测试代码

import * as React from 'react';
import {Keyboard} from 'react-native';
import {mount} from 'enzyme';
import {MyComponent} from 'mycomponent.js';

describe('Test MyComponent', () => {
  const mockSetShowText = jest.fn();
  const mockUseState = jest.spyOn(React, 'useState');
  const mockKeyboardListener = jest.spyOn(Keyboard, 'addListener');
  const keyboardCallbackMap = {};

  afterAll(() => {
    mockSetShowText.mockReset();
    mockKeyboardListener.mockRestore();
    mockUseState.mockRestore();
  });

  test('Text shown by default', () => {
    const myComponent = mount(<MyComponent />);
    expect(myComponent.find('Text').first()).toHaveText(
      'Bear, Beets, Battlestar Galactica',
    );
  });

  test('Text not show when showText is false', () => {
    mockUseState.mockReturnValue([false, mockSetShowText]);
    const myComponent = mount(<MyComponent />);
    expect(myComponent.find('Text')).not.toExist();
  });

  test('When Keyboard shows up, set showText to false; when disappear, set showText to true', () => {
    mockKeyboardListener.mockImplementation((event, cb) => {
      keyboardCallbackMap[event] = cb;
    });
    mockUseState.mockImplementation(showText => [showText, mockSetShowText]);
    const myComponent = mount(<MyComponent />);
    keyboardCallbackMap.keyboardDidShow(); // mock keyboardDidShow event
    expect(mockSetShowText).toHaveBeenCalledWith(false);
    keyboardCallbackMap.keyboardDidHide(); // mock keyboardDidHide event
    expect(mockSetShowText).toHaveBeenCalledWith(true);
    myComponent.unmount();
  });
});