Testing async methods using Mocha, Chai, node.js - node.js

I have a very simple code structure like this
TestWorks.ts
const axios = require('axios');
export class TestWorks{
async getUsersList(param1:TestModel, userDetail:any){
console.log("BEGIN -- ... ");
And then this is my test class
MyTest.ts
const testworks = require("../src/interfaces/TestService/TestWorks");
it('Get Users', async () => {
var x = await testworks.getUsersList({}, {});
expect(x).to.be.an("object");
});
but I am seeing the following error, unable to figure out what the issue could be. The paths are definitely right, not an issue with the file paths of where the files are
Get Users:
TypeError: testworks.getUsersList is not a function
at C:\Users\xxxxxx\Documents\xxxxx\test\test-server.test.ts:53:28

testworks refers to the module (or whatever TypeScript exports) because you use require(). You should use import for TypeScript modules.
import { TestWorks } from '../src/interfaces/TestService/TestWorks';

Related

Why isn't my Node package being imported?

I'm learning Node.js and am using a Node-based Azure Function.
I'm trying to bring in Chalk, to log coloured messages to the console.
However, all of the below fail (in my main index.js file).
One
module.exports = async (ctx, req) => {
const chalk = require('chalk');
return console.log(chalk.blue('Hello world!'));
Despite being the approach recommended in this answer, this results in a console error that says:
Exception: require() of ES Module C:...\node_modules\chalk\source\index.js from C:...\index.js not supported.
Instead change the require of C:...\chalk\source\index.js in C:...\index.js to a dynamic import() which is available in all CommonJS modules.
Two
If I do as the error suggests, and use
const chalk = async import('chalk')
...I then get
Exception: chalk.blue is not a function
...even though console.log(chalk) does seem to show the Chalk API and its various properties.
Three
The Chalk docs themselves recommend this:
module.exports = async (ctx, req) => {
import chalk from 'chalk'
return console.log(chalk.blue('Hello world!'));
That yields an error saying I can't use import outside of a module (but surely I'm in one?)
Four
Same as three ^^ but moving the import outside module.exports:
import chalk from 'chalk'
module.exports = async (ctx, req) => {
return console.log(chalk.blue('Hello world!'));
...yields the same error.
I'm sure this is a basic error but I can't find what I'm doing wrong so I'd be so grateful if someone could help. Thank you!
When you import something using import as a function(lazy import), It'll return an object with a default property and all exported properties. That you should use to access the module.
const module = async import('chalk')
const chalk = module.default
console.log(chalk.red('Hello world!'))

I can't understand how do 'global`s work in TypeScript/NodeJS and what is their difference?

I am reading a code like below:
import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose from "mongoose";
import request from "supertest";
import { app } from "../app";
declare global {
function signin(): Promise<string[]>;
}
let mongo: any;
beforeAll(async () => {
process.env.JWT_KEY = "asdfasdf";
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const mongo = await MongoMemoryServer.create();
const mongoUri = mongo.getUri();
await mongoose.connect(mongoUri, {});
});
beforeEach(async () => {
const collections = await mongoose.connection.db.collections();
for (let collection of collections) {
await collection.deleteMany({});
}
});
afterAll(async () => {
if (mongo) {
await mongo.stop();
}
await mongoose.connection.close();
});
global.signin = async () => {
const email = "test#test.com";
const password = "password";
const response = await request(app)
.post("/api/users/signup")
.send({
email,
password,
})
.expect(201);
const cookie = response.get("Set-Cookie");
return cookie;
};
I can't understand the purpose of global.signin function and how does it work? I guess it has something to do with Jest but as long as I know the Jest codes should be inside the __test__ folder with the same file name and .test.ts extension. But the above function is defined and used inside the setup.ts file in the root of the application.
I also see some codes like following:
declare global {
namespace Express {
interface Request {
currentUser?: UserPayload;
}
}
}
In some .ts files of the project as well that I am not sure are these global variables the same as the other globals I mentioned above or these are different things? I am interested to know how this global variables work as well?
The piece of code you shared is making use of global augmentation https://www.typescriptlang.org/docs/handbook/declaration-merging.html#global-augmentation
// Hint typescript that your global object will have a custom signin function
declare global {
function signin(): Promise<string[]>;
}
// Assign value to global.signin
global.signin = async () => { /* implementation */ };
Likely one or multiple modules ("mongoose", "supertest", "../app") imported by the test file is using global.signin (or window.signin) at some point (or maybe one of their nested imports is => look for "signin(" in the project). Thus for testing purposes, global.signin needed to be mocked. However just adding global.signin = something would raise a typescript error, because signin is not a standard global variable. This is where declare global comes into play. It hints typescript that in your particular context, a signin function is expected to exist in global scope.
JavaScript/TypeScript running in node will try to resolve anything it can't find in the current local scope in global (the same way a browser would look in window). Any function or variable you can access globally (e.g. setTimeout()), can also be accessed with global. as prefix. It just makes it explicit.
What happens in your code are two things:
declare global {
function signin(): Promise<string[]>;
}
Here it tells typescript's type system that the global object also has a function called signin. This part is not required but it makes sense required for typescript to allow you to access / define that function, in JavaScript you simply define it.
https://www.typescriptlang.org/docs/handbook/declaration-merging.html has some details how declare works.
global.signin = async () => {
// code ...
};
And here it is actually added to the global object.
In JavaScript non strict mode you could even write (notice the lack of var/let/const/global.)
signin = async () => {
// code ...
};
I don't see signin getting used anywhere in that code so the reason for it is unclear to me. As long as the file that defines it gets loaded you can call the function simply by referring to it as signin(). The global. is added implicitly.
The purpose of
declare global {
namespace Express {
interface Request {
currentUser?: UserPayload;
}
}
}
is more practical, in express you may want to add properties to your requests that get added by middleware. By declaring that the Express Request has a property called currentUser you get to do
app.get((req, res) => {
const user: UserPayload = req.currentUser
...
})
without typescript complaining about an unknown property.
More on that for example https://blog.logrocket.com/extend-express-request-object-typescript/

make jest compile/transform/serve locally the module under test with puppeteer

I need to pass a function that is written in typescript which should run in the browser. The issue that I am having is that either I need to have the module I am testing transpiled and them encoded so I can pass it to the browser in puppeteer and it will run normally. This was the approach I was using, and it works. in short I was using es-build to bundle the module. and using readFile then encoding so I can, in the browser import it and run it there.
I am thinking if there is a better way to do this with jest-puppeteer? I can't use page.exposeFunction() because that is running on node environment. and passing the encoded function will give the browser ts code which is not what I want. To understand better look at the code bellow.
//file: module_under-test.e2e.test.ts
//importing does not help us because we might need the whole module encoded
import { testFn } from './module_under-test';
import fs, { readFileSync } from 'fs';
import util from 'util';
const readFile = util.promisify(fs.readFile);
//this will encode the module in a string, so it can be imported in the browser.
async function importer(path) {
return `data:text/javascript;utf-8,${encodeURIComponent((await readFile(path, { encoding: 'utf-8' })))}`;
}
describe('Basic authentication e2e tests', () => {
beforeAll(async () => {
await page.setViewport( {
width: 1920,
height: 1080,
deviceScaleFactor: 1
} );
//we do stuff like opening the page and logging in, etc
});
it('testToRunOnBrowser', async () => {
//module should be already transpiled but this was the old approach. I would use importer from the dist folder.
//with this the test pass but we don't want to have to transpile code everytime to run it.
//since we could already do it with only esbuild and puppeteer
expect(await page.evaluate(testToRunOnBrowser,await importer('../dist/module_under-test.mjs'))).toBe(true);
})
});
export async function testToRunOnBrowser(deps) {
const {testFn} = await import(deps)
const ctx = new browserGlobalFunctionCtx();
const data = ctx.DoGLobalBrowserThings();
ctx.load(data);
const dataLoaded = await testFn()
return dataLoaded === 'what I want to assert'
}
One way I did think but I was not able to do, is servng the whole src folder since the code from this project should all be tested on the browser. With that I can use babel standalone with "#babel/plugin-transform-modules-umd" and just import ts on the browser. any ideas or pointers how to do that with jest-puppeteer?

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

Jasmine spy on exported function on NodeJS

I've had trouble spying on exported function in a NodeJS (v9.6.1) app using Jasmine.
The app is written in typescript, transpiled with tsc in a dist folder to be executed as javascript.
App
I have a Foo utils file (foo.utils.ts) which exports functions:
import {readFile} from "fs";
export function getData(filePath: string){
const data = readFile(filePath)
// various checks happens here.
return data;
}
And a Bar class in a bar.ts file:
import {getData} from "../utils/foo.utils
export class Bar {
public do(){
// ... does stuff
const data = getData(filePath);
// etc.
}
}
Test
Now I'm trying to spyOn the exported getData method to check if it've been called and to return a mocked value. I don't want to read file in my unit test (and even to use the real getData method at all)
The bar.spec.ts file for testing:
describe("Bar", ()=>{
let bar: Bar;
beforeEach(function() {
bar = new Bar();
})
it(" should do stuff", ()=>{
const spy = spyOn(???, "getData").and.returnValue([]);
bar.do();
expect(spy).toHaveBeenCalled();
})
});
Problems
As it is a NodeJS app, I've been trying to use global as the object to spy on, but I get the error:
Error: : getAttachementData() method does not exist
I've also tried to add
import * as fooutils from ".../src/utils/foo.utils
And spy on fooutils but I still goes through the exported function (and crashes when it tries to read the file...)
Now I'm kinda lost. From what I've found it's not really possible to mock an exported function (even though they should be added to the global object).
I thought about refactoring the utils file to create an utils class which exports static method and spying on them.
Questions
Am I doing something wrong ?
Is it possible to spy (and replace) exported function ?
Would using static method in class (rather than export function) work ?
Any other way to that ?
You can wrap getData in an object { getData } and write your unit test as follows.
import { getData } from "../utils/foo.utils";
...
it(" should do stuff", () => {
const spy = spyOn({ getData }, "getData").and.returnValue([]);
bar.do();
expect(spy).toHaveBeenCalled();
});

Resources