Skip to content

Latest commit

 

History

History
442 lines (340 loc) · 12.2 KB

File metadata and controls

442 lines (340 loc) · 12.2 KB

Documentation for integration test examples

List of examples


Data flow

Redux store

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);
  // ...
});

Redux saga

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

Graphql / Apollo

Ask Pierre-Louis L.

More details in the future

External api calls with fetch or wretch

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:

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

Navigation

Internal navigation

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:

Outside page navigation

Ask Matthieu A.

More details in the future

Asynchronous navigation

Ask Matthieu A.

More details in the future


Timers

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


Rerender page

Can be needed to force props update, if so, use the refresh function given by the renderPage method

More details in the future

--

User interface

Styling library

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 !!

Inputs

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);
  // ...
});

Formik form

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

Components outside the tested page

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:

Loading

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

Disabled button

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.

Scroll View

Ask Antoine Jubin

More details in the future

Native code in general

By mocking the proper native library with jest

More details in the future