Mock an axios request to external service in test

2.6k views Asked by At

I'm trying to mock a call to an external service from NodeJS express. I can't get axios-mock-adapter to intercept the actual axios call (http://api.openweathermap.org) and to return a mocked response. When the assert is done it fails because to values are different. The temperature from the call is the actual outside temperature and not the mocked one. Don't know if I'm totally off or if I'm to close to the solution that I cant see it. Kind of new to JavaScript and NodeJS.

Please Stackoverflow, you are my only help.

This is my code:

File to test:

WeatherTools.prototype.getWeather = new Promise(function(resolve, reject) {

    axios.get(config.weather.openWeatherLocationURL, {
        params: {
            id: config.weather.openWeatherMapLocation,
            APPID: config.weather.openWeatherMapApiKey,
            units: config.weather.openWeatherUnit
        }
    }
    ).then(function(axiosResponse) {
        resolve(axiosResponse.data);
    }).catch(function(axiosError) {
        reject(axiosError);
    });
});

Test file:

const assert = require('assert');
const weatherTool = require('./weatertools');
const axios = require('axios');

let MockAdapter = require('axios-mock-adapter');
const TestData = require('./testData.js');

let mock = new MockAdapter(axios);

describe("Testing weather tools", () => {

    beforeEach(function() {
        mock
            .onGet(config.weather.openWeatherLocationURL, {
                params: {
                    id: config.weather.openWeatherMapLocation,
                    APPID: config.weather.openWeatherMapApiKey,
                    units: config.weather.openWeatherUnit
                }
            }).reply(200, TestData.location().mockedResponseData);
    });
    it('given a correct call to openWeather a valid response should be returned xxx', function(done) {

        weatherTool.WeatherTools.getWeather.then(function(actual) {
            assert.strictEqual(actual.temp.currentTemp, TestData.location().expected.temp.currentTemp);
            done();
        })
    });
});

Config file:

config.weather.openWeatherMapApiKey = 'theSecretApiKeyOfMine';
config.weather.openWeatherMapLocation = '1234567';
config.weather.openWeatherUnit = 'metric';
config.weather.openWeatherLocationURL = 'http://api.openweathermap.org/data/2.5/weather';
1

There are 1 answers

1
Estus Flask On BEST ANSWER

The problem is in tested code. It's expected that getWeather member is a method that gets weather, while it's promise property. It doesn't get weather, it's actually weather. Since it's prototype property, it eagerly performs a request on class definition, i.e. as soon as the class is imported. This also means that data cannot be updated in future, even if there's a need to do this.

Since the request is eagerly performed, it won't be affected by Axios mock.

getWeather also uses promise construction antipattern; axios already returns a promise, there's no need to construct another one with new Promise.

It should be:

WeatherTools.prototype.getWeather = () => {
    return axios.get(config.weather.openWeatherLocationURL, {
        params: {
            id: config.weather.openWeatherMapLocation,
            APPID: config.weather.openWeatherMapApiKey,
            units: config.weather.openWeatherUnit
        }
    }
    ).then(function(axiosResponse) {
        return axiosResponse.data;
    });
});

So it could be fetched like weatherToolsInstance.getWeather().then(...). It's expected that it will be mocked with axios-mock-adapter when used like that.