Mocking external API call in custom node with node-red-node-test-helper

Hi,

I'm currently tackling the testing of my custom Node-RED node, which involves making an external asynchronous API request using Axios to fetch data. Once the data is retrieved, it's attached to the message payload.

My main challenge lies in figuring out how to mock this external API effectively while using node-red-node-test-helper for testing the output of my node.

I attempted to use Nock to mock the API request, but unfortunately, it didn't work as expected.

I have provided some code below for the scenario I want to test for:

const axios = require('axios');

module.exports = function (RED) {

    function ExecuteNode(config) {
        RED.nodes.createNode(this, config);

        node.on('input', function (msg) {
            try {
                onInput(node, config, msg, logSettings);
            } catch (error) {
                node.error(`Unhandled exception: ${error.message}`, msg);
            }
        });
    }

    async function onInput(node, config, msg) {

        msg.payload = msg.payload || {};
        msg.payload.data = msg.payload.data || {};
        msg.payload.data.processResults = msg.payload.data.processResults || [];

        let caughtError;

        try {
            const axiosConfig = {
                method: 'GET',
                url: 'https://someextralservice.com/api/v1/stuff',
            };
    
            let apiResponse;
    
            try {
                apiResponse = await axios(axiosConfig);
            } catch (error) {
                throw error;
            }
    
            if (apiResponse && apiResponse.data) {    
                someData = apiResponse.data.someData;
            }
    
            msg.payload.data.processResults.push({
                id: someData,
            });
        } catch (error) {
            caughtError = error;
        }

        if (caughtError) {
            node.status({ fill: 'red', shape: 'dot', text: 'failed to execute' });
            node.error(caughtError.message, msg);
        } else {
            node.status({});
            node.send(msg);
        }
    }

    RED.nodes.registerType('external-api', ExecuteNode);
}


 it('should output correctly', function (done) {
        // Mock response using nock
        nock('https://someextralservice.com')
            .get('/api/v1/stuff')
            .reply(200, {
                someData: 'yes',
            });
   
        helper.load(node, flowFile, function () {
            try {
                var node = helper.getNode(nodeId);
                node.on('input', function (msg) {
                    try {
                        console.log(msg)
                        expect(msg.payload).toHaveProperty('data', 
                           processResults: [{
                                id: 'yes'
                            }]
                        });
                        done();
                    }
                    catch (err) {
                        done(error)
                    }

                });
                node.receive({
                    payload: {}
                });
            } catch (err) {
                done(error)
            }
        });
    });

Have you looked at node-red source and how the http-request node is tested? It essentially sets up an express server to perform real responses to known URLs (instead of a library that overrides/intercepts NODEs http request). I'm not saying nock wont work but since the only detail you have provided is "but unfortunately, it didn't work as expected.", this is the best I can offer at this point.

Obviously, in order to do this ^, there would need to be a way of setting the base URL inside your node to something like http://localhost then it calls the API endpoints relative to that.

Hi @Steve-Mcl, thanks for the information. I will take a look at the approach used to test the http request node.

By didn't work as expected, I meant nock was unable to intercept the request and add the intended dummy data to node-red-helper which then leads to my message.payload.data.processResults being empty on node.on('input') when calling node.receive. My actual node has the endpoint within the config to know where to send the request.