0

I need help figuring out how to mock an axios post request. The docs have failed me. I've tried just about every combination I could find on stack overflow. All end up with either typescript errors or the undesired results shown below...

enter image description here

The rendered component

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"
                  // isDisabled={isLoggingIn || !dirty || !isValid}
                  isLoading={isLoggingIn}
                  btnStyles={styles.btn}
                  btnTextStyles={styles.txtLogin}
                  onPress={handleSubmit}
                />
                {errors.submitError && <Text style={styles.submitError}>{errors.submitError}</Text>}
              </View>
            </View>
          </View>
        )}
      </Formik>
    </MainView>
  );
}

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);
  }
}

test trial #1 (axios mock)

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('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);
  });
});

test trial #2 (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('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);
  });
});

Any help would be appreciated.

Fiddle Freak
  • 1,923
  • 5
  • 43
  • 83

1 Answers1

0

I am not sure if this is the problem, but I am pretty sure you are supposed to use waitFor on your expects, not on your fireEvents. So it might just be that you are waiting to early and then not awaiting the actual result. I do either

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

or

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

also, I don't use axios, but for fetch I could use a node module "jest-fetch-mock" and then just:

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

There is a node module jest-mock-axios with 150k weekly downloads, I would try that one: https://www.npmjs.com/package/jest-mock-axios?activeTab=readme

Mister_CK
  • 668
  • 3
  • 13