I have one object (ConvertkitServer) that contains two methods: addSubscriberTags() that calls getConvertkitTags(). Both use fetch() and the scope of this question is limited to the tests I’m creating for addSubscriberTags():
import fetch, { RequestInit, Response } from "node-fetch";
// .. omitted for brevity
async function getConvertkitTags(): Promise<ConvertkitTag[]> {
console.log('REAL getConvertkitTags()');
// .. omitted for brevity
console.log('REAL tags count', convertkit_tags.length);
return convertkit_tags;
}
async function addSubscriberTags(email: string, tags: string[]): Promise<void> {
const convertkit_tags = await getConvertkitTags();
// .. omitted for brevity
console.log('addSubscriberTags().tags.length = ', convertkit_tags.length);
const response: Response = await fetch(
new URL(`${CONVERTKIT_API_URI}/tags/${tag_ids[0]}/subscribe`).toString(),
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
api_secret: process.env.CONVERTKIT_API_SECRET as string,
email: email,
tags: tag_ids
})
}
);
return;
}
export {
addSubscriberTags,
getConvertkitTags
}
In my test, I want to:
- force the return value of
getConvertKitTags(); so I can - check for specific arguments used in the call to
fetch()in theaddConvertkitTags()method.
Neither is working.
To handle (1), I’ve set up a spy to force a specific result. It should only return 2 items, but the console log statement from the mocked method is returning all 11. This tells me my spy isn’t working:
import * as ConvertkitServer from './convertkit.server'; test.only('it adds 1 tag without throwing an error', async() => { expect.assertions(1); // setup SUT jest.spyOn(ConvertkitServer, 'getConvertkitTags').mockResolvedValue([ CONVERTKIT_TAGS_RESPONSE.tags[0], CONVERTKIT_TAGS_RESPONSE.tags[1], ]); // run SUT const response = await ConvertkitServer.addSubscriberTags('[email protected]', [CONVERTKIT_TAGS_RESPONSE.tags[0].name]); // check expect(response).toBeUndefined(); });console.log REAL getConvertkitTags() at CONVERTKIT_API_SECRET (contacts/convertkit.server.ts:195:20) console.log REAL tags count 11 at getConvertkitTags (contacts/convertkit.server.ts:2598:11) console.log addSubscriberTags().tags.length = 11 at Object.addSubscriberTags (contacts/convertkit.server.ts:2447:11)I don't understand why my spy seems to be ignored as the underlying one is what's being called from what I see in the console.
Once that's fixed, I then need to test the arguments passed to
fetch()withinaddSubscriberTags(), but after multiple attempts, I can't figure out how to properly spy on thefetch()method. I don't care iffetch()is called as I'm using MSW to intercept the calls. But I do want to check the arguments submitted, but all examples & questions/solutions I've found only address mockingfetch()which isn't what I want to do.I just want to spy on it and its arguments when called.
This was an interesting one. It has got to do with the way you are exporting your functions. When you import these functions into another file, you are actually importing from the export object, which means you are not directly working with the original functions but rather with the export objects references. As a result, with spyOn when you are targeting 'getConvertkitTags' function, you are actually spying on export.getConvertkitTags, not the original function, reference of which was enclosed with the function declaration. This causes the mock not to take effect as expected. My brain is getting all jumbled up and doing a poor job in explaining. here's an article that explains this topic beautifuly.
The only way I managed to make jest.spyOn to target the function properly was exporting them as arrow functions.
I hope we get some interesting suggestion from others.
p.s. from the comment i can see that there are other workarounds too. Still, seems a bit cumbersome to me tbh.