8

I have the following situation:

A.js

import fetch from 'node-fetch'
import httpClient from './myClient/httpClient'

 export default class{
    async init(){
       const response = await fetch('some_url')
       return httpClient.init(response.payload)
    }
}

A_spec.js

import test from 'ava'
import sinon from 'sinon'
import fetch from 'node-fetch'
import httpClient from './myClient/httpClient'
import A from './src/A'

test('a async test', async (t) => {
    const instance = new A()
    const stubbedHttpInit = sinon.stub(httpClient, 'init')
    sinon.stub(fetch).returns(Promise.resolve({payload: 'data'})) //this doesn't work

    await instance.init()
    t.true(stubbedHttpInit.init.calledWith('data'))
})

My idea it's check if the httpClient's init method has been called using the payload obtained in a fetch request.

My question is: How I can mock the fetch dependency for stub the returned value when i test the A's init method?

5 Answers5

12

Finally I resolved this problem stubbing the fetch.Promise reference like this:

sinon.stub(fetch, 'Promise').returns(Promise.resolve(responseObject))

the explanation for this it's that node-fetch have a reference to the native Promise and when you call fetch(), this method returns a fetch.Promise.

Milk
  • 2,469
  • 5
  • 31
  • 54
  • 2
    This does not work for typescript. I get the following error when I try to write the exact code mentioned above: Argument of type '"Promise"' is not assignable to parameter of type '"isRedirect"'. – Avneesh Jan 03 '20 at 18:34
  • Tocayo: what's the actual responseObject? Seem's like your reference link is no longer accurate to the line you meant as line #37 is just a blank line – gvasquez Apr 12 '20 at 22:57
  • Thank you! I looked at many SO answers, majority of them using `proxyquire`. This one is the most simple one. – jiri.hofman Oct 15 '22 at 11:19
  • @Avneesh late to the party, but it works in typescript if you typecast it as any: spyOn(fetch, 'Promise' as any) – Julian Re Jun 20 '23 at 16:46
4

You can stub fetch() as decribed in the manual

import sinon from 'sinon'
import * as fetchModule from 'node-fetch'
import { Response } from 'node-fetch'
// ...
const stub = sinon.stub(fetchModule, 'default')
stub.returns(new Promise((resolve) => resolve(new Response(undefined, { status: 401 }))))
// ...
stub.restore()

Note, that fetch() is the default export from node-fetch so you neeed to stub default.

mrtnlrsn
  • 1,105
  • 11
  • 19
3

sinon.stub(fetch) can't stub a function itself. Instead you need to stub the node-fetch dependency from inside ./src/A, perhaps using something like proxyquire:

import proxyquire from 'proxyquire`
const A = proxyquire('./src/A', {
  'node-fetch': sinon.stub().returns(Promise.resolve({payload: 'data'}))
})
Mark Wubben
  • 3,329
  • 1
  • 19
  • 16
1

@mrtnlrsn's answer does not work for files generated from TypeScript in NodeJS, because default is a generated property, each module which imports such a dependency has its own, so stubbing one does not affect others.

However, NodeJS gives access to imported modules, so stubbing works this way:

const nodeFetchPath = require.resolve("node-fetch");
const nodeFetchModule = require.cache[nodeFetchPath];
assert(nodeFetchModule);
const stubNodeFetch = sinon.stub(nodeFetchModule, "exports");

Make sure you use the same node-fetch module as the tested module, e.g. using yarn dedupe. Or build your own nodeFetchPath.

Jonathan Giroux
  • 377
  • 3
  • 5
0

In my case, I found it useful to preserve the node-fetch functionality since my mock responses were already being supplied via nock

To accomplish this, I proxyquire'd the dependency as described in the answer above but wrapped a require call in a spy:

import proxyquire from 'proxyquire'
import { spy } from 'sinon'

const fetchSpy = spy(require('node-fetch'))
const moduleA = proxyquire(
  './moduleA',
 { 'node-fetch': fetchSpy }
)

...

expect(fetchSpy.args).toBe(...)
benipsen
  • 493
  • 6
  • 12