React Native – Testing (3)

How to fix “Received number of calls: 0” when testing a class method?

  • Main classes
class API {
  someMethod() {
    ...
  }
}

export default API;
import API from 'utils/API';

class Foo {
  constructor() {
    this.api = new API();
  }

  helloWorld() {
    this.api.someMethod();
  }
}

export default Foo;
  • Failed test
describe( 'Foo', () => {
	it ( 'should call someMethod', () => {
		const foo = new Foo();
		const api = new API();

		jest.spyOn( api, 'someMethod' );

		foo.helloWorld();

		expect(api.someMethod).toHaveBeenCalledTimes(1);
	});
});
expect(jest.fn()).toHaveBeenCalledTimes(expected)

Expected number of calls: 1
Received number of calls: 0
  • Correct test file
const mockSomeMethod = jest.fn(); // Make variables that start with the word mock

jest.mock( 'utils/API', () => { // Call jest.mock with module factory parameter
	return jest.fn().mockImplementation(() => {
		return {
			someMethod: mockSomeMethod
		};
	});
});

describe( 'Foo', () => {
	it ( 'should call greetWorld', () => {
		const foo = new Foo();
		const api = new API();

		foo.helloWorld();

		expect(api.greetWorld).toHaveBeenCalledTimes(1);
	});
});

Ref: inner-functions-with-jest

How to mock a class to return a function instead of an object?

class API {
  someMethod() {
    ...
  }
}

export default API;
  • If a class is mistakenly mocked as an “object” as opposed to a function, an object cannot be instantiated with a “new” operator.
    This mock is incorrect because it returns an object:
import API from 'utils/API';

const mockSomeMethod = jest.fn()
jest.mock('utils/API', () => {
  // Returns an object
  return {
    someMethod: mockSomeMethod
  };
})

// This will throw the error
const api = new API();
  • This mock is correct:
import API from 'utils/API';

const mockSomeMethod = jest.fn()
jest.mock('utils/API', () => {
  // Returns a function
  return jest.fn().mockImplementation(() => ({
    someMethod: mockSomeMethod
  }));
})

// This will not throw an error anymore
const api = new API();

How to mock a class which is exported as non-default?

The error you usually get for this case is “TypeError: _myClass.MyClass is not a constructor

export class API {
  someMethod() {
    ...
  }
}
import API from 'utils/API';

const mockSomeMethod = jest.fn()
jest.mock('utils/API', () => {
  return {
    get API() {
      return jest.fn().mockImplementation(() => ({
        someMethod: mockSomeMethod
      }));  
    }
  }
})

const api = new API();

How to test async/ await?

export class API {
  async fetchMethod() {
    ...
  }

  errroMethod() {
    ...
  }

////////////////////////////////
////////////////////////////////

  async someMethod() {
    try {
      await fetchMethod()
    } catch(error) {
      errorMethod()
    }
  }
}
import API from 'utils/API';

const api = new API();
const mockErrorMethod = jest.spyOn(api, 'errorMethod');

describe('API fecth works', () => {
  jest.spyOn(api, 'fetchMethod').mockReturnValue(Promise.resolve());

  it('calls fetchMethod, errorMethod is not called', async () => {
    await api.someMethod;
    expect(api.fetchMethod).toHaveBeenCalledTimes(1);
    expect(mockErrorMethod).toHaveBeenCalledTimes(0);
  });
})

describe('API fecth is down', () => {
  jest.spyOn(api, 'fetchMethod').mockReturnValue(Promise.reject(error));

  it('calls fetchMethod, errorMethod', async () => {
    await api.someMethod;
    expect(api.fetchMethod).toHaveBeenCalledTimes(1);
    expect(mockErrorMethod).toHaveBeenCalledTimes(1);
  });
})

How to test a component with a custom hook

// click-a-button.tsx
    
    import {useClickAButton} from "./hooks/index";
    
    export const ClickAButton = () => {
        const { handleClick, total } = useClickAButton();
        return <button onClick={handleClick}>{total}</button>;
    }
// hooks/use-click-a-button.tsx

import React, {useCallback, useState} from 'react';

export const useClickAButton = () => {
    const [total, setTotal] = useState<number>(0);
    const handleClick = useCallback(() => {
        setTotal(total => total + 1);
    }, []);
    return {
        handleClick,
        total,
    };
}
// click-a-button.test.tsx

import * as React from 'react';
import {act} from "react-dom/test-utils";
import {render} from "@testing-library/react";
import {useClickAButton} from './hooks/index'
import {ClickAButton} from "./index";

jest.mock('./hooks') // This is important
const hooks = { useClickAButton }

const mockUseClickAButton = jest.spyOn(hooks, 'useClickAButton');
const mockHandleClick = jest.fn();
mockUseClickAButton.mockReturnValue({
    handleClick: mockHandleClick,
    total: 5,
});

test('it runs with a mocked customHook',() =>  {
    const component  = render(<ClickAButton />);
    expect(component.container).toHaveTextContent('5');
    act(() => {
        component.container.click();
    });
    expect(mockHandleClick).toHaveBeenCalled();
})

How to mock a hook?

// navigation.ts

import { createNavigationContainerRef } from '@react-navigation/native';

export const navigationRef = createNavigationContainerRef();

...

Error when running the test file

TypeError: (0 , _native.createNavigationContainerRef) is not a function
// Test file

const mockNavigation = jest.fn();
jest.mock('@react-navigation/native', () => ({
  ...jest.requireActual('@react-navigation/native'),
  createNavigationContainerRef: () => {
    return mockNavigation;
  },
  useNavigation: () => {
    return mockNavigation;
  }
}))

How to test a button press and fire an event?

// MyComponent.tsx

...

return 
  <MyButton
    // Setup testID
    testID={constants.testId.myButton}

    // onPress action
    onPress={() => navigateToSomewhere(link)}
  />

...
// Test file

// Mock action
const mockAction = jest.spyOn(anObject, 'navigateToSomewhere').mockImplementation();

// Get button view by id
const { getByTestId } = render(<MyComponent />);
const button = getByTestId(constants.testId.myButton);
await waitFor(() => expect(button).toBeTruthy());

// Perform press action
fireEvent.press(button);

// Test result
expect(mockAction).toBeCalled();

How to test an API request using Axios?

// index.tsx

export const getUserProfile = async () => {
  try {

    const response = await axios.get(userProfileUrl)
    return response.data;

  } catch (error) {
    throw error;
  }
}
import axios from 'axios';

// Mock axios
jest.mock('axios');
const mockAxios = axios as jest.Mocked<typeof axios>;

describe('fetch user profile sucessfully', () => {
  it('should return user profile', async () => {
    // Mock return value
    mockAxios.get.mockResolvedValue(mockData);

    // Perform axios request
    await getUserProfile();

    // Result
    expect(axios.get).toHaveBeenCalledWith(`${userProfileUrl}`)
  })
}) 

How to test if an element is displayed?

const { getByTestId, toJSON } = render(<MyComponent />);

// Snapshot
expect(toJSON()).toMatchSnapshot();

// Get element by Id
const button = getByTestId(testId.button.login);
expect(button).toBeTruthy();

Be the first to comment

Leave a Reply

Your email address will not be published.


*