Search code examples
typescriptunit-testingaxiosjestjs

Mocking axios with custom error in Jest and Typescript


I want to mocking axios post request with custom error object.

getToken.ts

const GetToken = async (params:ITokenParams):Promise<ITokenResult | IError>=>{
    try{
        const api = axios.create({
            headers: {
              "Access-Control-Allow-Origin": "*",
              "Content-Type": "application/json",
            },
        });

        const {data} = await api.post<ITokenResult>("https://someURL", { ...params });
        return data;
    }
    catch(e){
        let err:IError={
            error:true,
            module:path.basename(__filename),
            method:GetToken.name
        };
        if(e instanceof AxiosError && e.response?.data?.errors){
            let errorsArray:IApiError[]=e.response?.data?.errors;
            err.message=errorsArray.length>0?errorsArray[0].detail : e.message;
        }
        else if (e instanceof Error){
            err.message=e.message
        }
        return err;
    }
}

export default GetToken;

The custom error object is mockResponse. I want to return a response with status 401. In response, data object should includes errors array.

getToken.test.ts

import {axiosInstance} from '../../utils/axios-instance';
import GetToken from '@App/api/oauth/getToken';

jest.mock('../../utils/axios-instance.ts');

describe('Token from OAuth', () => {
    test('Return error for expired code', async () => {
        const mockResponse = {
           data:{}, 
           status: 401,
           statusText:"Unauthorized",
           headers: {}, 
           config: {} , 
           response:{ 
              data: { errors: [{ detail: 'a' }] } 
           }
        };

        const response = {
            error: true,
            module: 'getToken.ts',
            method: 'GetToken',
            message: 'a'
        };

        const mockedAxiosInstance = axiosInstance as jest.MockedFunction<typeof axiosInstance>;
        (mockedAxiosInstance.post as jest.Mock).mockRejectedValueOnce(mockResponse);

        const result= await GetToken({
            client_id:process.env.APPLICATION_ID,
            client_secret:process.env.APPLICATION_SECRET,
            code:"some-codes",
            grant_type:"authorization_code"
        });

        expect(result).toEqual(response);
    })
})

axios-instance.ts

import axios from "axios";
export const axiosInstance = axios.create({
    headers: {
        "Access-Control-Allow-Origin": "*",
        "Content-Type": "application/json",
    }
})

The test is fail.

  Object {
    "error": true,
-   "message": "a",
+   "message": "Request failed with status code 400",
    "method": "GetToken",
    "module": "getToken.ts",
  }

When i check the code in the catch statement this block runs

  else if (e instanceof Error){
        err.message=e.message
  }

How can i return my custom error object?


Solution

  • Yes I solve with 2 different option.

    Thanks to @jonrsharpe, axios instance should be same both .test.ts and .ts files.

    apiRoot.ts

    import axios from "axios";
    export const apiRoot = axios.create({
        headers: {
            "Access-Control-Allow-Origin": "*",
            "Content-Type": "application/json",
        }
    })
    

    getToken.ts

    ...
    import {apiRoot} from '@App/utils/apiRoot';
    
    const GetToken = async (params:ITokenParams):Promise<ITokenResult | IError>=>{
        try{
            // This line change with apiRoot. We don't use axios.create.
            const {data} = await apiRoot.post<ITokenResult>("someURL", { ...params });
            return data;
        }
        catch(e){
          ...
        }
    }
    
    export default GetToken;
    

    Option-1 : Test with axios

    getToken.test.ts

    import {apiRoot} from '@App/utils/apiRoot';
    import GetToken from '@App/square-api/oauth/getToken';
    import {AxiosError} from 'axios';
    
    //mock the instance from apiRoot
    jest.mock('@App/utils/apiRoot.ts');
    
    describe('Token from OAuth', () => {
        test('Return error for expired code', async () => {
            // Expected response from GetToken method
            const response = {
                error: true,
                module: 'getToken.ts',
                method: 'GetToken',
                message: 'a'
            };
    
            const mockedAxiosInstance = apiRoot as jest.MockedFunction<typeof apiRoot>;
             
            (mockedAxiosInstance.post as jest.Mock).mockRejectedValueOnce(new AxiosError("Unauthorized","401",{},{},
              {
                data: { errors: [{ detail: 'a' }] },
                status:401,
                statusText:'Unauthorized',
                headers:{},
                config:{}
              }));
    
            const result= await GetToken({
                client_id:process.env.APPLICATION_ID,
                client_secret:process.env.APPLICATION_SECRET,
                code:"some-code",
                grant_type:"authorization_code"
            });
            
            expect(result).toEqual(response);
        })
    })
    

    Option-2 : Mock Service Worker

    Don't mess with Axios

    install packages

    npm install msw --save-dev

    getToken.test.ts

    import { rest } from 'msw'
    import { setupServer } from 'msw/node'
    import GetToken from '@App/square-api/oauth/getToken';
    
    const mockResponse =  { errors: [{ detail: 'a' }] } ;
    
    const response = {
        error: true,
        module: 'getToken.ts',
        method: 'GetToken',
        message: 'a'
    };
    
    const server = setupServer(
    
    // Url should be same with post request in getToken.ts 
    rest.post("someURL", (req, res, ctx) => {
            return res(
                ctx.set('Content-Type', 'application/json'),
                ctx.status(401),
                ctx.json({...mockResponse})
            )
      })
    )
    
    beforeAll(() => server.listen())
    afterEach(() => server.resetHandlers())
    afterAll(() => server.close())
    
    describe('Token from OAuth', () => {
        test('Return error for expired code', async () => {
            const result= await GetToken({
                client_id:process.env.APPLICATION_ID,
                client_secret:process.env.APPLICATION_SECRET,
                code:"some-code",
                grant_type:"authorization_code"
            });
            
            expect(result).toEqual(response);
        })
    })