Community Resources
Recommended content
-
-
- The Testing Trophy and Testing Classifications by Kent C. Dodds (2021) - classic article explaining testing philosophy behind all Testing Library implementations. +
- The Testing Trophy and Testing Classifications by Kent C. Dodds (2021) - classic article explaining testing philosophy behind all Testing Library implementations.
- Common mistakes with React Testing Library by Kent C. Dodds (2020) - classic article explaining React Testing Library best practices, highly applicable to RNTL as well.
- React Native — UI Testing (Ultimate Guide) by Anisur Rahman - comprehensive guide to RNTL testing
- React Native Testing examples repo by Steve Galili - extensive repo with RN testing examples for RNTL and Maestro diff --git a/docs/eslint-plugin-testing-library.html b/docs/eslint-plugin-testing-library.html index 0052abba..28df499a 100644 --- a/docs/eslint-plugin-testing-library.html +++ b/docs/eslint-plugin-testing-library.html @@ -4,7 +4,7 @@
If you use fake timers in any of your tests you should update your Jest dependencies to version 28. This is due to the fact that jest.useFakeTimers() config structure has changed.
Refactor legacy waitForOptions position
In version 9 we introducted query options parameters for each query type. This affected all findBy and findAllBy queries because their signatures changed e.g. from:
function findByText(text: TextMatch, waitForOptions?: WaitForOptions)
function findAllByText(text: TextMatch, waitForOptions?: WaitForOptions)
function findByText(text: TextMatch, waitForOptions?: WaitForOptions);
function findAllByText(text: TextMatch, waitForOptions?: WaitForOptions);
to
function findByText(text: TextMatch, options?: TextMatchOptions, waitForOptions?: WaitForOptions)
function findAllByText(text: TextMatch, options?: TextMatchOptions, waitForOptions?: WaitForOptions)
In order to facilitate transition, in version 9 and 10, we provided a temporary possibility to pass WaitForOptions like timeout, interval, etc inside options argument. From this release we require passing these as the proper third parameter.
WaitFor API changes
We renamed waitForElement function to waitFor for consistency with React Testing Library. Additionally, the signature has slightly changed from:
export default function waitForElement<T>(
expectation: () => T,
timeout?: number,
interval?: number
): Promise<T> {}
export default function waitForElement<T>(
expectation: () => T,
timeout?: number,
interval?: number,
): Promise<T> {}
to:
-export default function waitFor<T>(
expectation: () => T,
options: {
timeout?: number,
interval?: number,
}
): Promise<T> {}
export default function waitFor<T>(
expectation: () => T,
options: {
timeout?: number,
interval?: number,
},
): Promise<T> {}
Both changes should improve code readibility.
waitFor calls (and hence also findBy queries) are now wrapped in act by default, so that you should no longer need to use act directly in your tests.
You can usually avoid waitFor by a proper use of findBy asynchronous queries. It will result in more streamlined testing experience.
Reverted matching text across several nodes
This is a breaking change.
In v1.14 we've introduced a feature allowing to match text when it's spread across several nodes:
-const { getByText } = render(
<Text>
Hello <Text>world</Text>
</Text>
);
getByText('Hello world'); // matches
const { getByText } = render(
<Text>
Hello <Text>world</Text>
</Text>,
);
getByText('Hello world'); // matches
However this behavior was different than the web one, and wouldn't always be straightforward to reason about. For instance it could match text nodes far from each other on the screen. It also prevented us from implementing the string precision API. From v9, this type of match will not work.
A work around is to use within:
import {Text} from 'react-native'
import {render, within} from '@testing-library/react-native'
const {getByText} = render(<Text>Hello <Text>world</Text</Text>)
within(getByText('Hello', {exact: false})).getByText('world')
Test cases
Our test is on the components that either dispatch actions on the redux store or read some data from the redux store. This means we will test ./components/AddTodo.js and ./components/TodoList.js. Thus we will create ./components/AddTodo.test.js and ./components/TodoList.test.js
For ./components/AddTodo.test.js
import * as React from 'react';
import { Provider } from 'react-redux';
import { render, screen, fireEvent } from '@testing-library/react-native';
import configureStore from '../store';
import AddTodo from './AddTodo';
describe('AddTodo component test', () => {
test('adds a new TODO when the button is pressed', () => {
const store = configureStore();
const component = (
<Provider store={store}>
<AddTodo />
</Provider>
);
render(component);
// There is a TextInput.
// https://github.com/callstack/react-native-testing-library/blob/ae3d4af370487e1e8fedd8219f77225690aefc59/examples/redux/components/AddTodo.js#L24
const input = screen.getByPlaceholderText(/repository/i);
expect(input).toBeOnTheScreen();
const textToEnter = 'This is a random element';
fireEvent.changeText(input, textToEnter);
fireEvent.press(screen.getByText('Submit form'));
const todosState = store.getState().todos;
expect(todosState.length).toEqual(1);
expect(todosState).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: 1,
text: textToEnter,
date: expect.any(Date),
}),
])
);
});
});
import * as React from 'react';
import { Provider } from 'react-redux';
import { render, screen, fireEvent } from '@testing-library/react-native';
import configureStore from '../store';
import AddTodo from './AddTodo';
describe('AddTodo component test', () => {
test('adds a new TODO when the button is pressed', () => {
const store = configureStore();
const component = (
<Provider store={store}>
<AddTodo />
</Provider>
);
render(component);
// There is a TextInput.
// https://github.com/callstack/react-native-testing-library/blob/ae3d4af370487e1e8fedd8219f77225690aefc59/examples/redux/components/AddTodo.js#L24
const input = screen.getByPlaceholderText(/repository/i);
expect(input).toBeOnTheScreen();
const textToEnter = 'This is a random element';
fireEvent.changeText(input, textToEnter);
fireEvent.press(screen.getByText('Submit form'));
const todosState = store.getState().todos;
expect(todosState.length).toEqual(1);
expect(todosState).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: 1,
text: textToEnter,
date: expect.any(Date),
}),
]),
);
});
});
For ./components/TodoList.test.js
import * as React from 'react';
import { Provider } from 'react-redux';
import { render, screen, fireEvent } from '@testing-library/react-native';
import configureStore from '../store';
import TodoList from './TodoList';
describe('TodoList component test', () => {
test('it should execute with a store with 4 elements', () => {
const initialState = {
todos: [
{ id: 1, text: 'Sing something', date: new Date() },
{ id: 2, text: 'Dance something', date: new Date() },
{ id: 3, text: 'Sleep something', date: new Date() },
{ id: 4, text: 'Sleep something', date: new Date() },
],
};
const store = configureStore(initialState);
const component = (
<Provider store={store}>
<TodoList />
</Provider>
);
render(component);
const todoElems = screen.getAllByText(/something/i);
expect(todoElems.length).toEqual(4);
});
test('should execute with 2 elements and end up with 1 after delete', () => {
const initialState = {
todos: [
{ id: 1, text: 'Sing something', date: new Date() },
{ id: 2, text: 'Dance something', date: new Date() },
],
};
const store = configureStore(initialState);
const component = (
<Provider store={store}>
<TodoList />
</Provider>
);
render(component);
const todoElems = screen.getAllByText(/something/i);
expect(todoElems.length).toBe(2);
const buttons = screen.getAllByText('Delete');
expect(buttons.length).toBe(2);
fireEvent.press(buttons[0]);
expect(screen.getAllByText('Delete').length).toBe(1);
});
});