Tips: React Native + Jest Unit Test + testing-library

Personal thought on unit test driven development:

  1. Starting early is a good idea. It’s recommended to begin writing unit tests when you start adding the first screen in your app. Instead of trying to do unit tests for every single screen, it’s better to pick one or two main screens (which will be the most complex) and work on them from time to time. Then, other developers can use your work as a bootstrap.

Debug Tip 1: mocking Firebase functions

If your component import Firebase functions like this:

import crashlytics from '@react-native-firebase/crashlytics';
import analytics from '@react-native-firebase/analytics';

Then creating mocking should be in same pattern:

jest.mock('@react-native-firebase/crashlytics', () => ({
  log: jest.fn(),
}));
jest.mock('@react-native-firebase/analytics', () => ({
  logEvent: jest.fn(),
}));

I thought this will work:

jest.mock('@react-native-firebase', () => ({
  crashlytics: jest.fn(() => ({
    log: jest.fn(),
  })),
  analytics: jest.fn(() => ({
    logEvent: jest.fn(),
  })),
}));

But I got:

Cannot find module '@react-native-firebase' from 'jest.setup.js'

Debug Tip 2 : mocking store

Most of mobile applications that built with RN use store heavily. For the unit test of complex application requires mocking the store.

Mocking store via redux-mock-store and redux-thunk:

import React from 'react';
import {cleanup, render} from '@testing-library/react-native';
import { Provider } from 'react-redux';
import { LoginScreen } from '../../src/screens';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
...
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
...
const initialState = {
  auth: {
    user: {
      id: undefined,
      guid: undefined,
      email: '',
      name: '',
      phonenumber: '',
      ...
    },
  },
};
...
describe('LoginScreen', () => {
  let store;
  let component;

  beforeEach(() => {
    store = mockStore(initialState);
    component = render(<Provider store={store}><LoginScreen /></Provider>);
    component.debug();
  });
});

Debug Tip 3 : i18next: Testing without stubbingTesting without stubbing

If your app is internationalized, and you need to have render the components with the localized strings, and your app is using i18next?

// i18nForTest.js

import { createInstance } from 'i18next';
import { initReactI18next } from 'react-i18next';
import i18n from '../src/i18n';

const i18nInstance = createInstance();

i18nInstance.use(initReactI18next).init({
  lng: 'en',
  fallbackLng: 'en',
  supportedLngs: ['ko', 'en'],
  debug: true,
  interpolation: {
    escapeValue: false,
  },
  ns: ['translations'],
  defaultNS: 'translations',
  resources: {
    en: i18n.options.resources.en,
    ko: i18n.options.resources.ko,
  },
});

export default i18nInstance;

// LoginScreen.test.js

import React from 'react';
import {cleanup, render} from '@testing-library/react-native';
import { Provider } from 'react-redux';
import { TextInput } from 'react-native-paper';
import { LoginScreen } from '../../src/screens';
import { I18nextProvider } from 'react-i18next';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import i18nInstance from '../i18nForTest';

const middlewares = [thunk];
const mockStore = configureStore(middlewares);

const initialState = {
  auth: {
    user: {
      id: undefined,
      guid: undefined,
      email: '',
      name: '',
      phonenumber: '',
      birthday: undefined,
      country: '',
      phoneVerified: '',
      callbackAddress: '',
      promiseNoti: false,
      chatNoti: false,
      activityNoti: false,
    },
  },
  common: {
    showError: false,
    showSuccess: false,
    message: '',
  },
  signatures: [],
  promises: [],
  promiseChanged: false,
  archivedPromises: [],
  isLoggedIn: false,
  ipAddress: undefined,
  lang: '',
  shownModals: [],
  showStickerTooltip: true,
};

describe('LoginScreen', () => {
  let wrapper;
  let store;
  let component;

  beforeEach(() => {
    store = mockStore(initialState);
  });

  it('renders email and password input fields', () => {
    const screen = render(
      <Provider store={store}>
        <I18nextProvider i18n={i18nInstance}>
          <LoginScreen />
        </I18nextProvider>
      </Provider>,
    );
    screen.debug();
    const emailInputPlaceholder = screen.queryByPlaceholderText(/Email address/);
    expect(emailInputPlaceholder).toBeDefined();
  });
});

More info is here – https://react.i18next.com/misc/testing


Posted

in

by

I am a software engineer who enjoys building things. Please share your interest with me – chrisyoum[at]gmail.com