Testing Library for React Native has shown that when mocking an axios post request, the timesHaveBeenCalled(1) will actually

Can someone guide me on how to properly mock an axios post request? I've been struggling with the documentation and various solutions from stack overflow. Each attempt either results in typescript errors or the undesired outcomes depicted below...

https://i.sstatic.net/HMRwn.png

Here is the component being rendered:

import React, { useState } from 'react';
import { Image, StyleSheet, Text, TextInput, View } from 'react-native';
import { Formik } from 'formik';
import * as Yup from 'yup';
import { MaterialCommunityIcons as Icon } from '@expo/vector-icons';
import { BtnMain, MainView } from '@app/components';
import { useAuthStore } from '@app/stores';
import { apiGetCurrentUser, apiLogin } from '@app/apis';

const validationSchema = Yup.object({
  username: Yup.string().required('Username required'),
  password: Yup.string().required('Password required')
});

export default function ScreenLogin(): JSX.Element {
  const [isLoggingIn, setIsLoggingIn] = useState(false);
  const [hidePassword, setHidePassword] = useState(true);
  const { setIsViewerAuthenticated, setViewerInfo } = useAuthStore(store => store);

  const loginHander = async (values: { username: string; password: string }): Promise<void> => {
    try {
      setIsLoggingIn(true);
      const responseToken = await apiLogin(values.username, values.password);
      if (!responseToken) {
        throw new Error('Access Denied');
      }
      await setIsViewerAuthenticated(responseToken);
      const responseViewerInfo = await apiGetCurrentUser();
      await setViewerInfo(responseViewerInfo);
    } catch (error: any) {
      throw error;
    } finally {
      setIsLoggingIn(false);
    }
  };

  return (
    <MainView>
      <Formik
        initialValues={{
          username: '',
          password: '',
          submitError: null
        }}
        validationSchema={validationSchema}
        onSubmit={(values, { setErrors }) =>
          loginHander(values).catch(error => setErrors({ submitError: error.message }))
        }
      >
        {({
          handleChange,
          handleBlur,
          handleSubmit,
          values,
          errors
          // isValid, dirty
        }) => (
          <View style={styles.container}>
            <Image source={require('@/assets/images/vlogo.png')} style={styles.image} />
            <View style={styles.form}>
              <View>
                <TextInput
                  style={styles.inputMain}
                  placeholder="Username"
                  onBlur={handleBlur('username')}
                  onChangeText={handleChange('username')}
                  value={values.username}
                />
                {errors.username && <Text style={styles.error}>{errors.username}</Text>}
              </View>
              <View>
                <View style={styles.inputContainer}>
                  <TextInput
                    style={styles.inputPswd}
                    placeholder="Password"
                    secureTextEntry={hidePassword}
                    onBlur={handleBlur('password')}
                    onChangeText={handleChange('password')}
                    value={values.password}
                  />
                  <Icon
                    style={styles.eyeIcon}
                    onPress={() => setHidePassword(!hidePassword)}
                    name={hidePassword ? 'eye-off' : 'eye'}
                    size={20}
                  />
                </View>
                {errors.password && <Text style={styles.error}>{errors.password}</Text>}
              </View>
              <View>
                <BtnMain
                  btnName="Login"
                  isLoading={isLoggingIn}
                  btnStyles={styles.btn}
                  btnTextStyles={styles.txtLogin}
                  onPress={handleSubmit}
                />
                {errors.submitError && <Text style={styles.submitError}>{errors.submitError}</Text>}
              </View>
            </View>
          </View>
        )}
      </Formik>
    </MainView>
  );
}

This depicts the spyon function:

export async function apiLogin(username: string, password: string): Promise<string> {
  try {
    const result = await axiosLoginRequest([mapApiEndpoints.login, { username: username, password: password }]);
    return result.data.Response;
  } catch (error: any) {
    throw new Error(error);
  }
}

Attempted test #1 using axios mocking:

import React from 'react';
import { render, fireEvent, waitFor, cleanup, screen } from '@testing-library/react-native';
import ScreenLogin from './ScreenLogin';

jest.mock('expo-asset');
jest.mock('expo-font');
const mockAxios = {
  post: jest.fn(() => Promise.resolve({ data: {} }))
};

describe('Testing Login screen...', () => {
  afterAll(() => {
    cleanup();
  });
  it('renders inputs and button', async () => {
    render(<ScreenLogin />);
    const userInput = screen.getByPlaceholderText('Username');
    const passwordInput = screen.getByPlaceholderText('Password');
    const loginButton = screen.getByText('Login');
    expect(userInput).toBeTruthy();
    expect(passwordInput).toBeTruthy();
    expect(loginButton).toBeDefined();
  });
  it('enter username/password and click login', async () => {
    render(<ScreenLogin />);
    const userInput = screen.getByPlaceholderText('Username');
    const passwordInput = screen.getByPlaceholderText('Password');
    const loginButton = screen.getByText('Login');
    await waitFor(() => fireEvent.changeText(userInput as never, 'test1'));
    expect(userInput.props.value).toEqual('test1');
    await waitFor(() => fireEvent.changeText(passwordInput as never, 'pass1'));
    expect(passwordInput.props.value).toEqual('pass1');
    expect(loginButton).toBeDefined();
    mockAxios.post.mockImplementationOnce(() =>
      Promise.resolve({
        data: {
          results: 'token'
        }
      })
    );
    await waitFor(() => fireEvent(loginButton, 'click'));
    expect(mockAxios.post).toHaveBeenCalledTimes(1);
  });
});

Attempted test #2 using spyOn:

import React from 'react';
import { render, fireEvent, waitFor, cleanup, screen } from '@testing-library/react-native';
import ScreenLogin from './ScreenLogin';
import { apiLogin } from '@app/apis/index';
const api = { apiLogin };

jest.mock('expo-asset');
jest.mock('expo-font');
jest.spyOn(api, 'apiLogin').mockResolvedValue('token');

describe('Testing Login screen...', () => {
  afterAll(() => {
    cleanup();
    jest.resetAllMocks();
  });
  it('renders inputs and button', async () => {
    render(<ScreenLogin />);
    const userInput = screen.getByPlaceholderText('Username');
    const passwordInput = screen.getByPlaceholderText('Password');
    const loginButton = screen.getByText('Login');
    expect(userInput).toBeTruthy();
    expect(passwordInput).toBeTruthy();
    expect(loginButton).toBeDefined();
  });
  it('enter username/password and click login', async () => {
    // setup
    render(<ScreenLogin />);
    const userInput = screen.getByPlaceholderText('Username');
    const passwordInput = screen.getByPlaceholderText('Password');
    const loginButton = screen.getByText('Login');

    // enter credentials
    await waitFor(() => fireEvent.changeText(userInput as never, 'test1'));
    expect(userInput.props.value).toEqual('test1');
    await waitFor(() => fireEvent.changeText(passwordInput as never, 'pass1'));
    expect(passwordInput.props.value).toEqual('pass1');

    // login
    await waitFor(() => fireEvent.press(loginButton));

    // results
    expect(api.apiLogin).toHaveBeenCalledTimes(1);
  });
});

If anyone can offer assistance, it would be greatly appreciated.

Answer №1

It's possible that the issue lies in using waitFor on your expects instead of fireEvents. You might be waiting too early and missing the actual result. I typically do either

await act(async () => fireEvent(elementToFireEventOn, event));
expect(consoleLogSpy).toHaveBeenCalled()

or

fireEvent(elementToFireEventOn, event)
await waitFor(() => {
  expect(consoleLogSpy).toHaveBeenCalled()
}

Additionally, for mock testing with fetch, you can use the node module "jest-fetch-mock" and simply:

fetch.mockResponse(JSON.stringify(someResult));

If you prefer axios, there is a popular node module called jest-mock-axios with 150k weekly downloads, which you can try out: https://www.npmjs.com/package/jest-mock-axios?activeTab=readme

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Manufacturing TypeScript classes that are returned by a factory

Developed a custom library that generates classes based on input data and integrates them into a main class. To enhance code maintainability and readability, the logic for generating classes has been extracted into a separate file that exports a factory f ...

Unable to use native module, expo encountering problem when importing Firebase

I am facing an issue while trying to import Firestore into my application. The error message "Invariant Violation: Native module cannot be null" keeps coming up. Looking for solutions, some suggest running pod install in ios folder. However, the ios folder ...

The toISOString() method is deducting a day from the specified value

One date format in question is as follows: Tue Oct 20 2020 00:00:00 GMT+0100 (Central European Standard Time) After using the method myValue.toISOString();, the resulting date is: 2020-10-19T23:00:00.000Z This output shows a subtraction of one day from ...

The design of Next.js takes the spotlight away from the actual content on the

Recently, I've been working on implementing the Bottom Navigation feature from material-ui into my Next.js application. Unfortunately, I encountered an issue where the navigation bar was overshadowing the content at the bottom of the page. Despite my ...

The process of generating generic interfaces in typescript

Implementing the functions below using TypeScript. interface ActionType { // what's the code? type: string }; let actionType: ActionType<{list: any}> = { type: 'type', list: [] } ...

What is the best approach for creating a Pagination component in React JS?

I recently started developing a web-app and I'm still learning about web development. I have received a backend response in JSON format with pagination information included: { "count": 16, "next": "http://localhost:800 ...

Is it possible to create an instance in TypeScript without using class decorators?

As per the definition on Wikipedia, the decorator pattern enables you to enhance an object of a class with additional functionalities, such as: let ball = new BouncyBall(new Ball()) The Ball object is adorned with extra features from the BouncyBall class ...

The code is looking for an assignment or function call, but found an expression instead: no-unused-expressions

Why am I encountering an ESLint error when using Ternary with 2 statements here? ESLint Error: no-unused-expressions res?.isSuccessful ? (this.toastService.showToast(res.message, 'success'), this.queueDataService.addMember(attendee)) : ...

Learn the steps to transfer variables between screens in React Native by referring to the document provided

Take a look at the screenshot here: Code Snippet I need to figure out how to pass the "email" variable from the 'List' screen to the 'Account' screen without actually navigating to it since it's a bottomTabNavigator. Any assistan ...

Issue encountered: XHR error (404 Not Found) occurred following the installation of typescript-collection in Angular 2 final version

After setting up an Angular 2 project quickly, I added the typescript-collections package using the command: npm install typescript-collections --save However, upon launching my project, I encountered the following error: GET http://localhost:63342/IMA% ...

Error message in React Native CLI: "There was an Invariant Violation stating that the required native component 'RNSScreen' could not be found in the UIManager."

Can someone help me troubleshoot the RNSScreen error? Despite following the react-navigation guide step by step, I am still unable to resolve the issue. ...

How can we automate the process of assigning the hash(#) in Angular?

Is it possible to automatically assign a unique hash(#) to elements inside an ngFor loop? <div *ngFor="let item of itemsArray; index as i"> <h3 #[item][i]> {{ item }} </h3> </div> I would like the outp ...

Exploring the concept of abstract method generation in TypeScript within the Visual Studio Code

Anyone familiar with a Visual Studio Code plugin that can automatically generate stub implementations for abstract methods and properties in TypeScript? I've searched through the available plugins but haven't been able to locate one. Any suggest ...

Scroll to the top on every Angular 5 route change

Currently, I am utilizing Angular 5 for my project. Within the dashboard interface, there are various sections with varying amounts of content. Some sections contain only a small amount of information, while others have large amounts of content. However, w ...

Using TypeScript conditional types with extends keyof allows for checking against specific keys, but it does not grant the ability

In TypeScript, I created custom types using template literal types to dynamically type the getters and setters of fields. The types are structured like this (Playground Link): export type Getters<T> = { [K in `get${Capitalize<keyof T & strin ...

Struggle with Loading Custom Templates in Text Editor (TinyMCE) using Angular Resolver

My goal is to incorporate dynamic templates into my tinyMCE setup before it loads, allowing users to save and use their own templates within the editor. I have attempted to achieve this by using a resolver, but encountered issues with the editor not loadin ...

Is there an issue with developing a React Native application in this manner?

I've encountered an issue while trying to develop a simple React Native application. Despite successfully creating the project, I'm facing difficulties running it within the simulator. Once the app is launched, it immediately crashes. I've ...

Creating IPv6 Mask from IPv6 Prefix Using Javascript: A Step-by-Step Guide

Write a JavaScript/TypeScript function that can convert IPv6 prefixes (ranging from 0 to 128) into the corresponding mask format (using the ffff:ffff style). Here are some examples: 33 => 'ffff:ffff:8000:0000:0000:0000:0000:0000' 128 => ...

Converting a Promise to an Observable in Angular using Typescript

I have a working method that functions as intended: getdata(): Promise<any> { let query = `SELECT * FROM table`; return new Promise((resolve, reject) => { this.db.query(query, (error, rows) => { if(error) reject(error); ...

Can declaration files be tailored for specific TypeScript versions?

My goal is to create an npm package for my type definitions to be used across various projects. I plan to name it @dfoverdx/ts-magic. Some projects are using TypeScript versions 3.x, while others are using >=4.2, which introduced new features like leadi ...