Files to check out:
Basically, for each test, we create a new real redux store with an initial state. This store is passed to the page via a provider wrapping the page in the renderPage method:
export const renderPage = (
page: ReactElement,
initialState?: Partial<IAppState>,
) => {
storeManager.store = createInitialiasedStore(initialState);
const pageContainerComponent = (
<Provider store={storeManager.store}>
{page}
<Toaster />
</Provider>
);
const pageRendered = render(pageContainerComponent);
return {...pageRendered};In the setup of the test, use the previously defined renderPage method
it('should display previous and new todos', async () => {
// SETUP
const page = renderPage(<TodoList />, initialState);
// ...
});Check out:
The sagas are set up in the renderPage method and then behave just like they would in the devlopement environment.
export const sagaMiddleware = createSagaMiddleware();
export const renderPage = (
page: ReactElement,
initialState?: Partial<IAppState>,
) => {
storeManager.store = createInitialiasedStore(initialState);
sagaMiddleware.run(watchAll);
const pageContainerComponent = (
<Provider store={storeManager.store}>
{page}
<Toaster />
</Provider>
);
const pageRendered = render(pageContainerComponent);
return {...pageRendered};
};In your test, use an async function as well as the waitForElement helper to find your React Node in the DOM
it('should display succesful message on successful subscription', async () => {
// ...
// THEN
const SuccessMessage = await waitForElement(() =>
page.queryByText(wording.subscriptionSuccessful),
);
expect(SuccessMessage).toBeTruthy();
});Ask Pierre-Louis L.
More details in the future
To make our integration tests cover as much code as possible, we use fetch-mock to mock the api calls. That way, we can test that our api calls to our server are actually made and have the right parameters.
Files to check out:
- test for simple call
- test for call with response
- getMockApiResponse
- TODO: test calls with query parameters and tokens to check
Here is how you can mock api calls with fetch mock and some custom helpers and then test the page using it:
const mockGetMovies = () => {
fetchMock.get(
MOVIES_API_ENDPOINT,
getMockApiResponse(200, {results: mockPopularMovies}),
);
};
it('should load movies and display movies properly', async () => {
// SETUP
mockGetMovies();
// ...
// THEN
const FirstMovie = await waitForElement(() =>
page.queryByText(mockPopularMovies[0].title),
);
expect(FirstMovie).toBeTruthy();
});
});Basically, you need to mock to mock react-native-gesture-handler in a jest setup file
jest.mock('react-native-gesture-handler', () => {
const View = require('react-native/Libraries/Components/View/View');
return {
State: {},
PanGestureHandler: View,
BaseButton: View,
Directions: {},
};
});Then you need a renderWithNavigation function:
export const renderWithNavigation = (
pageRoute: string,
initialState?: IAppState,
) => {
const App = createAppContainerWithInitialRoute(pageRoute);
const pageContainerComponent = <App />;
const pageRendered = render(pageContainerComponent);
return {...pageRendered};
};Finally write your test:
describe('[Page] Home', () => {
it('should navigate to about page without any trouble', async () => {
const page = renderWithNavigation(Routes.Home);
const AboutButton = page.getByText('About');
fireEvent.press(AboutButton);
const AboutTitle = await waitForElement(() =>
page.queryByText(wording.aboutTitle),
);
expect(AboutTitle).toBeTruthy();
});
});Files and functions to check out:
Ask Matthieu A.
More details in the future
Ask Matthieu A.
More details in the future
To tests features including a timer (implemented with setTimeout or the saga effect delay for instance), you need to use jest fake timers
it('should load movies and display movies properly [using jest timers]', () => {
// SETUP
jest.useFakeTimers();
//...
jest.runOnlyPendingTimers(); // don't run all timers here because delay (the redux saga effect) use recursive timers
// THEN it shows the movies from the external API
const FirstMovie = waitForElement(() =>
// no need for await since we use fake timers
page.queryByText(mockPopularMovies[0].title),
);
expect(FirstMovie).toBeTruthy();
});You can find the whole test here
Can be needed to force props update, if so, use the refresh function given by the renderPage method
More details in the future
--
If you use a styling library, you need to wrap your rendered page with your Theme provider. You can do it for example in a helper:
export const renderPage = (
page: ReactElement,
initialState?: Partial<IAppState>,
) => {
// ...
const pageContainerComponent = (
<ThemeProvider theme={theme}>
// other providers
{page}
</ThemeProvider>
);
const pageRendered = render(pageContainerComponent);
return {...pageRendered};
};Then use it like this:
it('should display succesful message on successful subscription', async () => {
// SETUP
// ...
const page = renderPage(<Subscription {...props} />);
// ...
});Files to check out:
Don't forget to import jest-styled-components in each test file if you use styled-components.
WARNING : not working yet with theme provider !!
If possible, get the input in the DOM via its placeholder. However, getByPlaceholder might not work depending on the input you use. I don't think it works with react-native-paper for instance
Here is an extract from a test featuring an input
it('should display succesful message on successful subscription', async () => {
// ...
// GIVEN
const EmailInput = page.getByPlaceholder(wording.emailPlaceholder);
// ...
});Nothing specific to do here really, here is the test:
it('should display succesful message on successful subscription', async () => {
// SETUP
mockCallSubscribe(200);
const page = renderPage(<Subscription {...props} />);
// GIVEN
const EmailInput = page.getByPlaceholder(wording.emailPlaceholder);
const ValidateButton = page.getByText(wording.validateEmail);
// WHEN
fireEvent.changeText(EmailInput, 'hello@bam.com');
fireEvent.press(ValidateButton);
// THEN
const SuccessMessage = await waitForElement(() =>
page.queryByText(wording.subscriptionSuccessful),
);
expect(SuccessMessage).toBeTruthy();
});Basically, if you're in a page where you want to test a component that is outside its DOM, you need to put the component in the renderPage method next to your page.
In our case, it's the Toaster component that is outside our page, that's why we put it in the renderPage method method.
export const renderPage = (
page: ReactElement,
initialState?: Partial<IAppState>,
) => {
// ...
const pageContainerComponent = (
<Provider store={storeManager.store}>
{page}
<Toaster />
</Provider>
);
const pageRendered = render(pageContainerComponent);
return {...pageRendered};
};And then, if you want to assert on the appearance of the toaster, check its presence through its text as you would usually do:
it('should display succesful message on successful subscription', async () => {
// ...
const page = renderPage(<Subscription {...props} />);
//...
// THEN
const SuccessMessage = await waitForElement(() =>
page.queryByText(wording.subscriptionSuccessful),
);
expect(SuccessMessage).toBeTruthy();
});Files to check out:
A very common scenario is having a loader appear somewhere on the screen while you wait for your api call to finish. To do that, don't write a test that only checks the loader but write a test covering the whole functionnality.
Below is the extract of the page we want to test, you can notice the testID on the ActivityIndicator that will help us find it in the DOM in our test.
useEffect(() => {
dispatch(MoviesActions.getMovies());
}, [dispatch]);
return (
<Container>
<Card>
{movies ? (
movies.map((movie, index) => <Text key={index}>{movie}</Text>)
) : (
<ActivityIndicator size="large" testID="loader" />
)}
</Card>
</Container>
);And then comes the test:
it('should load movies and display movies properly', async () => {
// SETUP
mockGetMovies();
// GIVEN the page renders
const page = renderPage(<Movies />);
// THEN it loads
const Loader = page.queryByTestId('loader');
expect(Loader).toBeTruthy();
// THEN it shows the movies from the external API
const FirstMovie = await waitForElement(() =>
page.queryByText(mockPopularMovies[0].title),
);
expect(FirstMovie).toBeTruthy();
});Unfortunately, it is not possible for now to test that a disabled button does not call its onClick prop. There is an issue for it here.
For now, one of the best way to test it is to check the value of the disabled property on your Button component. Another way would be to mock the component TouchableOpacity to ask it not to call onClick if its prop disbaled is set to true. However that would kinda be like rewriting react native and not really testing the code executed when your real user tries to click on a disabled button. That's why we think it's better just to check the disabled prop.
Ask Antoine Jubin
More details in the future
By mocking the proper native library with jest
More details in the future