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.


*