Testing: mocking node-fetch dependency that it is used in a class method
Asked Answered
M

5

9

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?

Mintun answered 14/5, 2017 at 5:20 Comment(0)
M
16

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.

Mintun answered 15/5, 2017 at 13:28 Comment(5)
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"'.Oscilloscope
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 lineCarilla
Thank you! I looked at many SO answers, majority of them using proxyquire. This one is the most simple one.Confiteor
@Oscilloscope late to the party, but it works in typescript if you typecast it as any: spyOn(fetch, 'Promise' as any)Silverweed
@JulianRe spyOn is a Jest method. This question's code is using Sinon.Rubeola
P
5

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.

Pedicle answered 14/10, 2021 at 9:59 Comment(1)
THIS WORKS. node 20.11.0 + typescript 4.9.4 + sinon 18.0.0 + node-fetch 2.6.7Rubeola
F
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'}))
})
Fifine answered 15/5, 2017 at 8:50 Comment(1)
Thanks for your comment, but finally i've resolved this without external dependencies. Check my answer.Mintun
D
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.

Dowie answered 24/11, 2021 at 17:46 Comment(1)
Works for @Pedicle in typescript :)Pedicle
M
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(...)
Metamorphism answered 12/4, 2018 at 20:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.