jest mock requires requireActual - jestjs

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

Related

How to spyOn an internal static function call?

I have the following event handler and I want to test that updateOrAddNew is being called.
const { Events } = require('client');
const User = require('../models/User');
module.exports = {
name: Events.MemberAdd,
async execute(member) {
User.updateOrAddNew(member);
}
}
I've written the following test:
import {it, expect, vi } from 'vitest';
import User from '../models/User';
import memberAdd from './memberAdd';
it('calls updateOrAddNew on memberAdd', async () => {
const spy = vi.spyOn(User, 'updateOrAddNew').mockReturnValue(true);
User.updateOrAddNew = spy;
const member = {...};
await memberAdd.execute(member);
expect(spy).toBeCalled();
});
I've tried numerous different syntax but the spy is never called. updateOrAddNew is a static method. How can I test whether it's being called when memberAdd.execute is run?
I'm pretty new to testing so any help would be appreciated.
What if you declare in that file globally like this:
jest.spyOn(User, 'updateOrAddNew').mockReturnValue(true);
This should update any usage of this method with mock

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/

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

Mocking TypeDI service with Jest

I'm using Node with TypeScript, TypeDI and Jest.
I'm creating services that depend on each other, let's say:
#Service()
export class MainService{
constructor(private secondService: SecondService){}
public someMethod(someString: string) // implementation depends on secondService
}
#Service()
export class SecondService{
constructor(private thirdService: ThirdService){}
}
#Service()
export class ThirdService{
constructor(){}
}
I want to test MainService, but to instantiate it I need to pass dependency and that dependency needs another dependecy.
I tried to do this like, it works, but is ugly:
const secondService = new SecondService(new ThirdService());
jest
.spyOn(secondService, "someMethod")
.mockImplementation((someString: string) => {
// do something
return something;
});
const mainService = new MainService(secondService);
// use mainService in tests
Of course creating new instance of dependency is not always an option, and defienetly not an option when it has many dependencies.
I think it should look more like:
const secondService = SomeMockFactory.create(SecondService);
but i can't find any way to create mock while cutting off dependencies. I tried using
const secondService = jest.genMockFromModule("path/to/second/service");
but after trying to spyOn secondService methods TS is throwing error that "someMethod" is not a function.
What am I missing / doing wrong? Do I need some other library than Jest?
After a while I found out how to do this using default jest behaviour.
First, you need to create mock of SecondService in path/to/second/service/__mocks__, like:
// path/to/second/service/__mocks__/SecondService.ts
const mock = jest.fn().mockImplementation(() => ({
async thingSecondServiceDoInFirstService(
param: number
): number {
return 1;
}));
export default mock;
SecondService has to be default export, like:
// path/to/second/service/SecondService.ts
#Service()
export default class SecondService {
constructor(private thirdService: ThirdService) {}
async thingSecondServiceDoInFirstService(
param: number
): number {
return this.thirdService.thingThirdServiceDoInSecond(param);
}
}
In test file you have to use jest.mock before importing SecondService, and then create SecondService instance from mock:
jest.mock("path/to/second/service/SecondService");
import SecondService from "path/to/second/service/SecondService";
import MainService from "path/to/main/service/MainService";
describe("Test main service", () => {
const SecondServiceMock = <jest.Mock<SecondService>>SecondService;
let secondService = new SecondServiceMock();
beforeEach(() => {
mainService = new MainService(secondService);
});
// test logic...
}
As requested, ThirdService is not needed anymore.

Mocking node dependency for Typescript with Mocha / Sinon

I have a class which controls an audio receiver using an external library marantz-avr:
let AVReceiver = require('marantz-avr');
class MarantzPlugin {
hardwareInstance;
constructor(pluginConfiguration) {
const receiver = new AVReceiver(pluginConfiguration.settings.ip);
this.hardwareInstance = receiver;
}
turnPowerOn() {
this.hardwareInstance.setPowerState('on').then(res => res, error => console.error(error));
}
}
export default MarantzPlugin;
I want to unit test this class. In order to do so I have to mock the marantz-avr library since this library only works when an actual receiver is found on the provided ip address.
In the test below I mock the marantz-avr, however the MarantzPlugin still uses the original AVReceiver instead of the mocked one.
import { suite, test, slow, timeout } from 'mocha-typescript';
import * as mocha from 'mocha';
import * as assert from 'assert';
import * as sinon from 'sinon';
import * as should from 'should';
import MarantzPlugin from './';
let AVReceiver = require('marantz-avr');
#suite
class MarantzPluginTest {
public create() {
before(() => {
sinon.stub(AVReceiver.prototype, 'AVReceiver').callsFake(() => {
return {
setPowerState: () => {
return true;
}
}
});
});
let marantz = new MarantzPlugin({
id: 'MARANTZ',
settings: {
ip: '192.168.178.2',
}
});
marantz.setPowerState('on');
}
}
I've looked into http://sinonjs.org/how-to/link-seams-commonjs/ but when implemented it gave me Error: Cannot find module 'marantz-avr'
Does anyone see what I am missing here, or perhaps have a better way to unit test these kind of classes?

Resources