node.jsunit-testingaxiosjestjsmocking

Issues mocking return value of Axios post call for unit test


Trying to mock an Axios call to unit test a token response from our identity software. Axios is not getting called at all and because of that my return is always undefined.

I've tried changing up Axios call to axios.post and changing the way I've mocked this more times then I can count. I don't believe like I should have to install another mocking framework just for Axios to mock this one function.

Implementation:

async getAuthToken() {        
    const oauthUrl = process.env.OAUTHURL;
    const oauthAudience = process.env.OAUTHAudience;
    const oauthUsername = process.env.OAUTHUSERNAME;
    const oauthPassword = process.env.OAUTHPASSWORD;
    
    let urlForAuth = oauthUrl
    urlForAuth = urlForAuth + '/as/token.oauth2?';
    urlForAuth = urlForAuth + 'grant_type=client_credentials';
    urlForAuth = urlForAuth + '&aud=' + oauthAudience + '/';
    urlForAuth = urlForAuth + '&scope=' + oauthAudience + '/.read';
    
    const options = {
        method: 'POST',
        url: urlForAuth,
        headers: {
            'Authorization': "Basic " + Buffer.from(oauthUsername + ":" + oauthPassword).toString("base64")
        },
        responseType: 'json'
    };
    
    try{
        let response = await axios(options); 
        return response.data.access_token;
    }
    catch(e){
        console.log(e);
        throw e;
    }       
}

Test Case:

test('token Is Returned', async () => {
    expect.hasAssertions();

    let Response = `{
            "access_token": "thisisatoken",
            "token_type": "Bearer",
            "expires_in": 3599
        }`;

    axios = jest.fn(() => Promise.resolve());

    axios.mockImplementationOnce(() =>
        Promise.resolve({
            data: Response
        })
    );            
    
    let response = await AuthService.getAuthToken();

    expect(axios).toHaveBeenCalledTimes(1);
    expect(response).toEqual("thisisatoken");  
});

The error I am getting is

Expected mock function to have been called one time, but it was called zero times.

When I debug the data element on the response contains the following:

data:"Copyright (c) 2019 Next Small Things\n"

That is no where in my code. help.


Solution

  • [upd] this answer is 5 years old. Consider using holistic network mocking - e.g. msw or nock. Unlike with module-level mocking it's way more flexible, lib- and framework-agnostic(e.g. you may have app-calls made with axios and some package requesting through low-level fetch()) and even more readable


    Original answer

    You cannot mock things this way. Actually you are mocking axios only for your test's code but not for component under test that import's axios on its own.

    You need to mock module properly and you have actually plenty of options:

    1. provide ready-to-use mock in __mocks__ folder
    2. call jest.mock('axios') to get autogenerated mock(each exported member will become jest.fn automatically)
    3. provide factory for mock jest.mock('axios', () => { .... return object like it all are exported from file ... })

    Also you need to import axios into your test to access it:

    import axios from 'axios';
    
    jest.mock('axios');
    
    test('token Is Returned', async () => {
        expect.hasAssertions();
    
        let Response = `{
                "access_token": "thisisatoken",
                "token_type": "Bearer",
                "expires_in": 3599
            }`;
    
        axios.mockReturnValue(() =>
            Promise.resolve({
                data: Response
            })
        );            
    
        let response = await AuthService.getAuthToken();
    
        expect(axios).toHaveBeenCalledTimes(1);
        expect(response).toEqual("thisisatoken");  
    });
    

    Beware of few things:

    1. jest.mock is hoisted to the top even if it's declared somewhere really deep in test's code. So it's better to place all jest.mock at the top of the file - because it anyway works this way - and this way another developer would not be confused if it's mocked or not.
    2. if using __mocks__ folder with auto mock you better inject jest.fn() in advance - most times you will like to check if part of mock has been called and with what arguments
    3. jest.mock cannot refer to any sibling variable except those one with name starting from mock.... See Service mocked with Jest causes "The module factory of jest.mock() is not allowed to reference any out-of-scope variables" error with really good explanation.
    4. it'd be hard(near to impossible) to unmock module partially. so consider your test either mock some module or does not mock at all to test it.