Search code examples
node.jsunit-testingmocha.jssinonrequest-promise

Stubbing a specific API request inside a function


How do I individually implement stubs to functions invoked inside a parent function?

Assuming I have these functions (req1,req2...) that are HTTP Requests from external services that are all returning differing values, is there a way where I can apply stubs for req1 or req2 individually to mock their values?

The purpose of this is because I need to do this to test a function that relies on an OTP verification and I want to bypass said verification in order to cover all branches in my testing.

import request from 'request-promise'
const request1 = async (data) => return request({uri: "service1.com/get", method: "GET"})

const apiRequests = async (data) => {
   const req1 = await request1(data); // I want to mock this value to false
   const req2 = await request2(data); // I want to mock this value to true

   if (req1 && req2) {
    const req3 = await request3(data);
    const req4 = await request4(data);

    return "Second return"
   }
   return "First return"
}

I've always been overwhelmed whenever trying to understand the deeper levels of mocking and most of the examples I see online aren't as nested the problem I'm facing so I'm a bit puzzled about how to go on about this.

I also work in a pretty strict setup so I'm not really allowed to use any other libraries/packages outside of Loopback's built-in testing libraries.


Solution

  • You can use stub.onCall(n) API.

    Defines the behavior of the stub on the nth call. Useful for testing sequential interactions.

    Besides, sinon does NOT support stub a standalone function import from a package, you need to use link seams, so that we use proxyquire package to construct seams.

    E.g.

    apiRequest.ts:

    import request from 'request-promise';
    
    const request1 = async (data) => request({ uri: 'service1.com/get', method: 'GET' });
    
    export const apiRequests = async (data) => {
      const req1 = await request1(data);
      const req2 = await request1(data);
      console.log(req1, req2);
    
      if (req1 && req2) {
        const req3 = await request1(data);
        const req4 = await request1(data);
    
        return 'Second return';
      }
      return 'First return';
    };
    

    apiRequest.test.ts

    import proxyquire from 'proxyquire';
    import sinon from 'sinon';
    
    describe('70241641', () => {
      it('should second return', async () => {
        const rpStub = sinon.stub().onCall(0).resolves(true).onCall(1).resolves(true);
        const { apiRequests } = proxyquire('./apiRequest', {
          'request-promise': rpStub,
        });
        const actual = await apiRequests('test data');
        sinon.assert.match(actual, 'Second return');
      });
    
      it('should first second', async () => {
        const rpStub = sinon.stub().onCall(0).resolves(false).onCall(1).resolves(true);
        const { apiRequests } = proxyquire('./apiRequest', {
          'request-promise': rpStub,
        });
        const actual = await apiRequests('test data');
        sinon.assert.match(actual, 'First return');
      });
    });
    

    test result:

      70241641
    true true
        ✓ should second return (2374ms)
    false true
        ✓ should first second
    
    
      2 passing (2s)
    
    ---------------|---------|----------|---------|---------|-------------------
    File           | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    ---------------|---------|----------|---------|---------|-------------------
    All files      |     100 |      100 |     100 |     100 |                   
     apiRequest.ts |     100 |      100 |     100 |     100 |                   
    ---------------|---------|----------|---------|---------|-------------------