模拟和监控键盘事件,在反应本机中开玩笑



给定以下代码:

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()事件?

所以这个问题比我一开始能想象的要复杂得多。 既然你想在这里测试的是 dismiss(( 和 show(( 基本上是键盘,对吗?

使用以下库执行测试:@testing-library/react-native

因此,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 调用 2 个函数_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和隐藏

编辑: 对于版本 0.66>现在您需要执行以下操作:

useEffect(() => {
const kds = Keyboard.addListener("keyboardDidShow", _keyboardDidShow);
const kdh = Keyboard.addListener("keyboardDidHide", _keyboardDidHide);
// cleanup function
return () => {
kdh.remove();
kds.remove();
};
}, []);
const _keyboardDidShow = () => {
alert("Keyboard Shown");
};
const _keyboardDidHide = () => {
alert("Keyboard Hidden");
};

由于他们改变了从现在开始使其成为订阅事件的方式

对于测试,将 Keyboard.emit 更改为:

Keyboard._emitter.emit('keyboardDidShow', {});
Keyboard._emitter.emit('keyboardDidHide', {});

你应该拥有让它开玩笑所需的一切:)

我在订阅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', () => {
renderer.create(<MyComponent />)
expect(Keyboard.addListener).toHaveBeenCalled()
})
it('should call listener.remove on unmount', () => {
const component = renderer.create(
< MyComponent />,
)
component.unmount()
expect(mockListener.remove).toHaveBeenCalled()
})
})

从 React Native 65 开始,jest 失败并显示:TypeError: _reactNative.Keyboard.emit is not a function

我还没有找到解决方案...我认为根据反应本机文档,键盘没有任何变化。仅供参考

编辑:用DeviceEventEmitter.emit('keyboardWillShow')修复

几乎所有的答案都显示了不正确的测试实践,明确地调用了Keyboard对象上的某些东西。相反,您希望正常运行测试(像普通用户一样与 UI 元素交互(,然后查看这些交互是否会导致键盘显示!如果您编写一个测试明确告诉键盘打开,当然,当您断言键盘是否已打开时,您的测试将通过!

更好的解决方案是添加一个额外的侦听器,它只是一个jest.fn()函数,然后检查是否曾经调用过该函数:

// setup mock function
const keyboardDidShowMock = jest.fn();
Keyboard.addListener('keyboardDidShow', keyboardDidShowMock);
// press an input, for example
await act(() => {
fireEvent(
renderAPI.getByLabelText(`My Input`),
'press',
);
});
});
// wait for and expect that the keyboard did show listener should have been called
waitFor(() => expect(keyboardDidShowMock).toHaveBeenCalled());

但请注意,您永远不必在键盘对象上显式调用Keyboard.show()Keyboard.emit()或任何内容!这是一种不好的测试气味。

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

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

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

示例组件:

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();
});
});

最新更新