React Native – Testing (2)

How to create a snapshot?

expect(
  render(<Component />).toJSON()
).toMatchSnapshot();

How to find an element and press it?

You can use queryByxxx or getByxxx. The difference is
– queryByxxx still returns the element as null if the element is not found
– getByxxx throws error if element is not found

How to find an element by testId?

const component = render(<Component />);
const backButton = component.queryByTestId('id-back');
expect(backButton).toBeDefined();

How to find an element by a11y?

const component = render(<Component />);
const backButton = component.queryByA11yLabel('back');
// const backButton = component.getByLabelText('back');
expect(backButton).toBeDefined();

How to find a text string inside an element?

const component = render(<Component />);
const element = component.getByTestId('id-element');
expect(element.props.children).toBe('--- a string ---');

How to press an element?

import { fireEvent, waitFor } from '@testing-library/react-native'

const component = render(<Component />)
const button = component.queryByA11yLabel('back')

await(
  waitFor(
    () => expect(button).toBeTruthy()
  )
)

fireEvent.press(button)
// fireEvent(button, 'click')

expect(
  getByTestId('id-visible-text')
).toBeTruthy();

How to create mock data?

How to create a mock component which throws error?

it('renders ErrorView when an error occurs', () => {
  const ThrowError = () => {
    throw new Error('Test error');
  }

  const { getByTestId } = render(
    <ErrorBoundary>
      <ThrowError />
    </ErrorBoundary>
  );

  expect(getByTestId('id-error-view')).toBeCalled;
});

How to mock a library?

  • Library
{
    useSomethings: {
        method1: Function;
        method2: Function;
        events: {
            store: {
                clear: Function;
            };
        };
    }
}
// Example of using this library
import { useSomethings } from '@me/library'

const MyComponent = () => {
    const { method1, method2, events } = useSomethings
    const { store } = events
    const { clear } = store
    ...
}
  • Mock file
class MockEvents {
  public store = jest.fn().mockReturnValue(
    clear: jest.fn();
  );
};

class MockUseSomethings {
  public method1 = jest.fn();
  public method2 = jest.fn();
  public events = new MockEvents();
}

class MockMyLib {
  constructor() {
    jest.mock('@me/library', () => {
      return {
        ...jest.requireActual('@me/library'),
        useSomething: jest.fn().mockReturnValue(new MockUseSomethings())
      }
    })
  }
}

export const mocks = {
  myLib: new MockMyLib()
}

Test file

const mockClear = jest.spyOn(mocks.myLib.useSomething.events.store(), 'clear');
expect(mockClear).toHaveBeenCalledTimes(1);

How to test a react-native hook?

Hook

const useSomething = (method1, method2) => {
   const navigation = useNavigation()
   const loginHandler = useCallback(
     async () => {
       ...
       navigation.navigate('login')
       
       try {
         const result = await method1()
         if (result) method2('post-login')
       } catch (e) {
         navigation.navigate('welcome')
       }
     }, [method1, method2]
   )

   return { loginHandler }
}

Test

// Mock useNavigation and useNavigation.navigate
jest.mock('@react-navigation/native', () => {
  useNavigation: jest.fn()
})

const mockUseNavigation = useNavigation as jest.Mock

const mockNavigation = {
  navigate: jest.fn()
} as any

// Mock arguments
const mockMethod1 = jest.fn()
const mockMethod2 = jest.fn()

beforeEach(() => {
  jest.clearAllMocks()
  mockUseNavigation.mockReturnValue(mockNavigation)
})

it('should navigate to Login screen', async () => {
  expect.assertions(3)

  // Call hook
  const { result } = renderHook(() => useSomething(mockMethod1, mockMethod2))
  await result.current.loginHandler()

  // Test argument call
  expect(mockMethod2).toHaveBeenCalledTimes(0)

  // Test useNavigation.navigate call
  expect(mockNavigation.navigate).toHaveBeenCalledTimes(1)
  expect(mockNavigation.navigate).toHaveBeenNthCalledWith(1, 'login')
})

it('should navigate to Login and back to Welcome screen due to error', async () => {
  expect.assertions(4)

  // Mock reject for method1
  mockMethod1.mockReturnValue(Promise.reject('error'))

  // Call hook
  const { result } = renderHook(() => useSomething(mockMethod1, mockMethod2))
  await result.current.loginHandler()

  // Test argument call
  expect(mockMethod2).toHaveBeenCalledTimes(0)

  // Test useNavigation.navigate call
  expect(mockNavigation.navigate).toHaveBeenCalledTimes(2)
  expect(mockNavigation.navigate).toHaveBeenNthCalledWith(1, 'login')
  expect(mockNavigation.navigate).toHaveBeenNthCalledWith(2, 'welcome')
})

it('should navigate to Login, then call post-login', async () => {
  expect.assertions(4)

  // Mock reject for method1
  mockMethod1.mockReturnValue(Promise.resolve('abc'))

  // Call hook
  const { result } = renderHook(() => useSomething(mockMethod1, mockMethod2))
  await result.current.loginHandler()

  // Test useNavigation.navigate call
  expect(mockNavigation.navigate).toHaveBeenCalledTimes(1)
  expect(mockNavigation.navigate).toHaveBeenNthCalledWith(1, 'login')

  // Test method2
  expect(method2).toHaveBeenCalledTimes(1)
  expect(method2).toHaveBeenNthCalledWith(1, 'post-login')
})

How to test a component having a hook/ an import component/ a custom function?

  • Main component
// MyComponent.tsx
import { useNavigation } from '@react-navigation/native'     // A hook
import { ComponentSomething } from '@me/ui-library' // An import component
import { navigateToSomewhere } from './format' // A custom function

const MyComponent = () => {
    const navigation = useNavigation()
    return <View testID={'id-test-my-component'}>
        <ComponentSomething />
        <Button testID={'id-test-button'} onPress={() => navigateToSomewhere(abc)} />
    </View>
}
// format
export const navigateToSomewhere = (abc) => {
    ...
}
  • Test file
import * as helper from './format'
import MyComponent from '../MyComponent'
import { fireEvent, waitFor } from '@testing-library/react-native'
import { myRender } from './testHelper'

// Mock a hook of react-navigation
jest.mock('@react-navigation/native', () => ({
    useNavigation: jest.fn();
}));
/*
// or
export class MockReactNavigation {
    public useNavigation = jest.fn();
    constructor() {
        jest.mock(('@react-navigation/native', () => ({
            useNavigation: useNavigation;
        })));
    }
}
*/

// Mock an import UI component
jest.mock(('@me/ui-library', () => ({
    const View = require('react-native/Libraries/Components/View/View')
    return {
        ComponentSomething: View
    }
})));

// Mock a custom function
const mockCustomFunc = jest.spyOn(helper, 'navigateToSomewhere').mockImplementation()

test('Render and fire NavigateSomewhere', async () => {
    const { getByTestId, queryByTestId } = myRender(<MyComponent />)
    const button = getByTestId('id-test-button');
    await waitFor(() => expect(button).toBeTruthy())
    fireEvent.press(button)
    expect(mockCustomFunc).toBeCalled()
})
// testHelper
export myRender = (ui: ReactElement, options?: ReactElement) => render(
    ui,
    {
        wrapper: Providers,
        ...options
    }
)

const Providers = ({ children }) => {
    return (
        <Theme ...>
            {children}
        </Theme>
    )
}

How to test opening a link in a browser app?

import { Linking } from 'react-native'

export const openBrowserApp = async(url: string) => {
    const supported = await Linking.canOpenURL(url)
    if (supported) {
        Linking.openURL(url)
    }
}
// Mock methods of an object
jest.mock('react-native/Libraries/Linking/Linking', () => ({
    canOpenURL: jest.fn().mockResolvedValue(true),
    openURL: jest.fn().mockResolvedValue(null),
    addEventListener: jest.fn(),
    removeEventListener: jest.fn()
}))

it('should accept an url and open it', async () => {
    expect(openBrowserApp('https://google.com/')).toBeTruthy()
})

How to test an independent function having an argument of a method?

Main function

// Usage
const MyComponent = () => {
    const { dispatchEvent } = useSomething();
    ...
    return <Button onPress={() => navigateToScreen(dispatchEvent, 'screenName')} />
}
// I want to test this function
export const navigateToScreen = (dispatch: DispatchAction, route: string) => {
    dispatch('EVENT_NAME', {
        route: route
    })
}

Test files

//////////////////////
// Create mock data
class MockSomething {
    public dispatchEvent = jest.fn()
    ...
}

class MockClass{
    public useSomething = new MockSomething()

    constructor() {
        jest.mock('@me/my-lib-module', () => {
            return {
                ...jest.requireActual('@me/my-lib-module'),
                useSomething: jest.fn().mockReturnValue(this.useSomething)
            }
        })
    }
}

///////////////////////////////
// Use it in your test file
it('should dispatch an event', async() => {
    const mockClass = new MockClass()
    navigateToScreen(mockClass.useSomething.dispatchEvent, 'aRouteName')
    expect(mockClass.useSomething.dispatchEvent).toHaveBeenCalledWith('EVENT_NOW', {
        route: 'aRouteName'
    })
})

How to test a function returning an instance of a class?

A function returning apollo client instance

export const getApolloClient = (something) => {
    ...
    return new ApolloCLient({
        link:...
        cache,
    })
}

Test file

import { getApolloClient } from './helper'
import { ApolloClient } from '@apollo/client'

it('create an apollo client', () => {
    const something = jest.fn()
    expect(getApolloClient(something)).toBeInstanceOf(ApolloClient)
})

How to test a function calling child methods of a class instance?

Main function

import ParentClass 'src/methods/ParentClass';

export const myFunction (parentClassInstance: ParentClass) {
    parentClassInstance.methodA();
    parentClassInstance.methodB();
}
class ParentClass {
    function methodA() { ... }
    function methodB() { ... }
}

Test file

import { myFunction } from '..'

jest.mock('src/methods/ParentClass', () => {
    jest.fn().mockImplementation(() => {
        return {
            methodA: jest.fn(),
            methodB: jest.fn()
        }
    })
})

describe('MyFunction', () => {
    const parentClass = new ParentClass();
    myFunction(parentClass);

    it('should call methodA', async() => {
        expect(parentClass.methodA).toHaveBeenCalledTimes(1);
    });

    it('should call methodB', async() => {
        expect(parentClass.methodB).toHaveBeenCalledTimes(1);
    });
})

Issues

How to fix “import unexpected token” on Jest?

Add .babelrc file

{
    "env": {
        "test": {
            "presets": [
                "@babel/preset-env",
                "@babel/preset-react"
            ]
        }
    }
}

Install dependencies

yarn add -D @babel/core @babel/preset-env @babel/preset-react

Detox

How to run detox?

detox build
detox test

How to execute different test file in order?

Set test file name as “01-xxxx-yyyy.spec.js”, “02-aaaaa-zzzzz.spec.js”

Be the first to comment

Leave a Reply

Your email address will not be published.


*