Mocking of a function within a function not working in jest with jest.spyOn - jestjs

I'm trying to write a test for a function that downloads an Excel file within my React app.
I understand that I need to mock certain functionality, but it doesn't seem to be working according to everything that I have read online.
A basic mock that works is this:
import FileSaver from 'file-saver'
import { xlsxExport } from './functions'
// other code...
test('saveAs', async () => {
const saveAsSpy = jest.spyOn(FileSaver, 'saveAs')
FileSaver.saveAs('test')
expect(saveAsSpy).toHaveBeenCalledWith('test')
})
The above works: FileSaver.saveAs was successfully mocked. However, I am utilising FileSaver.saveAs within another function that I wish to test and the mocking does not seem to transfer into that. functions.ts and functions.tests.ts below.
functions.ts:
import { Dictionary } from './interfaces'
import * as ExcelJS from 'exceljs'
import FileSaver from 'file-saver'
export function xlsxExport(data: Dictionary<any>[], fileName?: string, tabName?: string) {
const workbook = new ExcelJS.Workbook()
const worksheet = workbook.addWorksheet(tabName || 'export')
// Get columns from first item in data
worksheet.columns = Object.keys(data[0]).map((key: string) => ({ header: key, key: key }))
// Write each item as a row
for (const row of data) {
worksheet.addRow(row)
}
// Download the file
workbook.xlsx.writeBuffer().then(function (buffer) {
const blob = new Blob([buffer], { type: 'applicationi/xlsx' })
FileSaver.saveAs(blob, (fileName || 'excel_export') + '.xlsx')
})
}
functions.tests.ts
import FileSaver from 'file-saver'
import { xlsxExport } from './functions'
// ...other code
test('xlsxExport', async () => {
const saveAsSpy = jest.spyOn(FileSaver, 'saveAs')
xlsxExport(myArrayOfDicts, 'test_download')
expect(saveAsSpy).toHaveBeenCalledWith('something, anything')
})
Error:
TypeError: Cannot read properties of null (reading 'createElement')
at Function.saveAs (C:\dev\pcig-react\node_modules\file-saver\src\FileSaver.js:92:9)
at C:\dev\pcig-react\src\common\functions.ts:221:19
at processTicksAndRejections (node:internal/process/task_queues:95:5)
Node.js v19.3.0
FAIL src/common/functions.test.ts
● Test suite failed to run
Jest worker encountered 4 child process exceptions, exceeding retry limit
at ChildProcessWorker.initialize (node_modules/jest-runner/node_modules/jest-worker/build/workers/ChildProcessWorker.js:185:21)
It is trying to call the non-mocked FileSaver.saveAs (line 221 of my file) within xlsxExport.
How can I get it to call the mocked version?

Related

TypeScript Not Recognising Exported Firebase Cloud Functions

Problem Intro
I have over a hundred Firebase Cloud Functions, and to keep the code organised, I split them into separate files per function (e.g., userFunctions, adminFunctions, authFunctions, ...) as per the instructions in the official Firebase thread.
In my index.ts I import all the different function files as:
import * as adminFunctions from './modules/adminFunctions';
import * as userFunctions from './modules/userFunctions';
...
exports.adminFunctions = adminFunctions;
exports.userFunctions = userFunctions;
...
In my userFunctions.ts file, I would declare the individual functions, some of which would call additional reusable functions from authFunctions.ts
userFunctions.ts
import { https } from 'firebase-functions';
import { performAppCheckAuthentication } from './supportingFunctions/authFunctions';
exports.deleteExpiredOrganisationMembershipInvite = https.onCall(async (data, context) => {
// Perform AppCheck authentication
performAppCheckAuthentication(data, context)
// More code
...
})
The cross-referenced authFunctions.ts would look like this:
exports.performAppCheckAuthentication = function (
data: { [key: string]: any },
context: CallableContext
) {
return true; // There would be actual logic here in the real code
}
Exact Issue
When I have TypeScript try to compile this code, it gives me the following error in the userFunctions.ts file in the import statement:
Module '"./supportingFunctions/authFunctions"' has no exported member
'performAppCheckAuthentication'.
How can I keep my code split into different files to retain maintainability, but also get around this issue of not being able to import the functions?
You probably want to use the export statement instead of the exports global:
export function performAppCheckAuthentication(
data: { [key: string]: any },
context: CallableContext
) {
return true; // There would be actual logic here in the real code
}
export const deleteExpiredOrganisationMembershipInvite = https.onCall(async (data, context) => {
// Perform AppCheck authentication
performAppCheckAuthentication(data, context)
// More code
...
})
Docs

Function import problem in worker_threads

I have some code to be implemented in another thread. I use worker_threads for that reason. But I don't want to create a special file for that. So... What do I do is:
Create a temporary file
Convert my function to string
Wrap it into IIFE
Copy this function-string to my temporary file
Execute that file using worker
The problem is if I'm using exported functionality in my function, I can't paste this exported functionality into my function. In that case I'm getting "not defined" error.
Pseudo-code:
import someExternalFunc from "./somewhere"
import myWorker from "./myWorker"
function myFunc() {
return someExternalFunc();
}
myWorker(myFunc)
myWorker pseudo-code:
import { Worker } from "worker_threads"
function createTempFile(text) {
//...
return filepath
}
function myWorker(func) {
const iifeStr = "(" + func.toString() + ")()"
let wrapper = 'import { parentPort } from "worker_threads"\n'
wrapper += `parentPort.postMessage(${iifeStr})`
const filepath = createTempFile(wrapper);
const worker = new Worker(filepath);
worker.on("message", (res) => {
console.log(res)
})
}

jest mock requires requireActual

Its unclear for me why requireActual (3) is required to use the mock in __mocks__/global.ts (2) instead of global.ts (1) inside app.ts
According to the docs https://jestjs.io/docs/jest-object#jestrequireactualmodulename it says
Returns the actual module instead of a mock, bypassing all checks on
whether the module should receive a mock implementation or not.
What are the mentioned checks?
// __mocks__/global.ts
export const globalConfig = {
configA: "mockedConfigA"
};
export const globalLibA = jest.fn((msg) => {
return msg + "+mockedLibA"
});
// app.ts
import { globalConfig, globalLibA } from "./global"
export const app = function (msg) {
console.log("Called app")
return globalLibA(msg);
}
export const globalConfigConfigA = globalConfig.configA;
Full source: https://github.com/Trenrod/testjest/tree/master/src

Why module.exports does not export a function properly?

I'm building a project based on CJ's from Coding Garden Inventory App. In a knex migration file I have used an external file to bring a helper functions.
tableUtils.js
function addDefaultColumns(table) {
table.timestamps(false, true)
table.datetime('deleted_at')
}
function createNameTable(knex, tableName) {
return knex.schema.createTable(tableName, table => {
table.increments().notNullable()
table.string('name').notNullable().unique()
addDefaultColumns(table)
})
}
module.exports = {
createNameTable,
addDefaultColumns
}
and in my migration file:
const tableNames = require('../../src/constants/tableNames');
const { createNameTable, addDefaultColumns } = require('../../src/constants/tableNames');
exports.up = async (knex) => {
await knex.schema.createTable(tableNames.user, table => {
table.increments().notNullable()
table.string('name').notNullable()
table.string('email', 254).notNullable().unique()
table.string('password', 127).notNullable()
table.string('avatar_url', 2000)
table.string('color', 15).defaultTo('#dddddd')
table.specificType('balance', 'money').defaultTo('0')
addDefaultColumns(table)
})
};
Once tryint to run migration with knex migrate:latest I am getting error:
migration failed with error: addDefaultColumns is not a function
addDefaultColumns is not a function
TypeError: addDefaultColumns is not a function
What am I missing here as it looks like everything should work fine.. The function is declared with function and above module.exports so there shouldn't be a problem of function being undefined..
Your code shows you requiring tableNames, but you show a file named tableUtils.js so it appears you aren't requiring the right file.

Jest - getting error when mocking FS modules and calling config module

I'm writing unit tests with Jest trying to test a module which uses FS.
The module file:
import fs from 'fs';
import logger from './logger.utils';
export const getNumberOfFiles = async (targetDir: string): Promise<number> => {
// get number of folders
logger.info(`getNumberOfFiles from ${targetDir}/${fileName}`);
const numberOfFiles = await fs.readdirSync(targetDir);
return numberOfFiles.length;
};
Test file
import fs from 'fs';
import { getNumberOfFiles } from '../../src/utils/fs.utils';
jest.mock('fs');
describe('fs.utils', () => {
describe('getNumberOfFiles', () => {
it('Should return number', async () => {
fs.readdirSync = jest.fn();
const readdirSyncMock = fs.readdirSync = jest.fn();
readdirSyncMock.mockResolvedValue([1, 2, 3]);
const result = await getNumberOfFiles('targetDir');
expect(result).toEqual(3);
expect(readdirSyncMock.mock.calls.length).toEqual(1);
});
});
});
When I run the test file, I get the following error:
Config file ..../config/runtime.json cannot be read. Error code is: undefined. Error message is: Cannot read property 'replace' of undefined
1 | const cheggLogger = require('#chegg/logger');
2 | import loggingContext from './loggingContext';
> 3 | import config from 'config';
| ^
4 | import os from 'os';
5 | import constants from '../../config/constants';
6 |
at Config.Object.<anonymous>.util.parseFile (node_modules/config/lib/config.js:789:13)
at Config.Object.<anonymous>.util.loadFileConfigs (node_modules/config/lib/config.js:666:26)
at new Config (node_modules/config/lib/config.js:116:27)
at Object.<anonymous> (node_modules/config/lib/config.js:1459:31)
at Object.<anonymous> (src/utils/logger.utils.ts:3:1)
Content of logger.utils.ts
const internalLogger = require('internalLogger');
import loggingContext from './loggingContext';
import config from 'config';
import os from 'os';
import constants from '../../config/constants';
const logger = internalLogger.createLogger({
level: config.get(constants.LOG_LEVEL)
});
export default logger;
I assume that config is using FS, and once I mock the module, it fails.
How can I resolve this? Please advise
I'm guessing the problem comes from config also using the fs api but you are now mock entire module fs which makes all methods should be mocked before using.
But I have an idea for you by using jest.doMock which you can provide a factory for each test and just mock only method we need. Here is a draft idea:
describe('fs.utils', () => {
describe('getNumberOfFiles', () => {
it('Should return number', async () => {
jest.doMock('fs', () => ({
// Keep other methods still working so `config` or others can use
// so make sure we don't break anything
...jest.requireActual('fs'),
readdirSync: jest.fn(pathUrl => {
// Mock for our test path since `config` also uses this method :(
return pathUrl === 'targetDir' ? Promise.resolve([1, 2, 3]) : jest.requireActual('fs').readdirSync(pathUrl)
})
}));
// One of the thing we should change is to switch `require` here
// to make sure the mock is happened before we actually require the code
// we can also use `import` here but requires us do a bit more thing
// so I keep thing simple by using `require`
const {getNumberOfFiles} = require('../../src/utils/fs.utils');
const result = await getNumberOfFiles('targetDir');
expect(result).toEqual(3);
// you might stop assert this as well
// expect(readdirSyncMock.mock.calls.length).toEqual(1);
});
});
});
Just also want to check, if you created a config file as described here: https://www.npmjs.com/package/config#quick-start

Resources