Test hooks on “keyboardDidShow” in React Native Functional Component

Update 01/05/2021

Updated testing code using @testing-library/react-native

Recently, I need to write tests for a functional component that uses useState and useEffect to toggle an internal state based on whether the virtual keyboard shows up. A simplified version of the functional component is displayed below.

Source code for the sample component

The basic functionality is that a piece of text is shown by default. However, if the virtual keyboard is pulled up, the text disappears.

Tricky Part I

Another method I have come across suggests using react-hooks-testing-library. Yet the documentation specifically mentions that this library is not suitable for testing hooks defined inside a component. So it is not the way to go.

Workaround for Tricky Part I

Testing code using enzyme and various mocking

Line 10 sets up the map keyboardCallbackMap. Line 9 puts a spy on Keyboard.addListener so that we can mock its implementation later. Line 32 mocks the implementation, and we can see that the event-to-callback mapping is recorded in keyboardCallbackMap. To better illustrate this point, we can log the content of keyboardCallbackMap after MyComponent is mounted, and this is the output we will get:

{
keyboardDidShow: [Function: _keyboardDidShow],
keyboardDidHide: [Function: _keyboardDidHide]
}

We have access to the two callbacks defined inside MyComponent. Line 37 calls _keyboardDidShow callback directly from keyboardCallbackMap, as if the "keyboardDidShow" event had been triggered. Similarly, line 39 calls _keyboardDidHide as if the "keyboardDidHide" event had been triggered.

Tricky Part II

Workaround for Tricky Part II

Other Tests

The first test from Line 18 to 23 tests the default behavior of the component. Without any manipulation, the component should display the text. This is a straightforward test with no trick involved.

The second test from Line 25 to 29 tests whether the text disappears if showText is set to false. On Line 26, we use a trick to set the default value of showText to false, thus ensuring that no text will be displayed. This apparently is a hack, because the ideal testing should be triggering the "keyboardDidShow" or "keyboardDidHide" events, and then testing whether the text disappears or shows up, respectively. Although we already have a way to “trigger” these events, the mounted MyComponent cannot be updated to reflect the change in showText. I guess this has something to do with MyComponent being stateless. Since no state is stored in the mounted component, change of the internal state also cannot be manifested. Hence, the only way I can test the component’s behavior in response to change in showText is to initialize it with different values, which is exactly what line 26 does.

Finally, in the third test, there is an additional line 41, which unmounts the component. Its purpose is to trigger the event listener remover. If any test is needed on the remover, one can write it after line 41. In my case, I don’t need to test the remover, so nothing follows that line.

Conclusion

Hi, I am from the Earth. And you?