NodeJS Jest : Testing function that imports module using require.main.require - javascript

I am trying to write unit testing using Jest for a Node JS project. It was importing all the modules using require.main.require
Below are the simulation of the issue. Code can be found here: https://stackblitz.com/edit/node-jest-demo?file=index.js
I have the following test file present in my root directory in which I am importing index.js
./sample.pass.test.js
const { checkUser } = require('./index');
console.log(checkUser); // This is purely to check If i can access checkUser from this file or not
describe('Testing...', () => {
it('Should pass', () => {
expect(0).toBe(0);
});
});
In my index.js I am importing another function using require.main.require
const { getUserById } = require.main.require('./models/UserModel');
function checkUser(id) {
const user = getUserById(id);
return user ? 'Found' : 'Not Found';
}
module.exports.checkUser = checkUser;
The above test case is passing. But If I were to place the same test file in some other directly (like _test_ ) then it fails.
E.g.: ./__test__/sample.fail.test.js
Notice here I adjusted require statement of index since it is now one level up
const { checkUser } = require('../index');
console.log(checkUser);
describe('Testing...', () => {
it('Should pass', () => {
expect(0).toBe(0);
});
});
The result shows it is unable to access UserModel.
Cannot find module './models/UserModel' from '__tests__/sample.fail.test.js'
Require stack:
index.js
__tests__/sample.fail.test.js
> 1 | const { getUserById } = require.main.require('./models/UserModel');
| ^
2 |
3 | function checkUser(id) {
4 | const user = getUserById(id);
What could be the solution in this case?
Thank in advance!

Related

How to mock a function using Frisby and Jest to return custom response?

I'm trying to mock a function using Frisby and Jest.
Here are some details about my code:
dependencies
axios: "^0.26.0",
dotenv: "^16.0.0",
express: "^4.17.2"
devDependencies
frisby: "^2.1.3",
jest: "^27.5.1"
When I mock using Jest, the correct response from API is returned, but I don't want it. I want to return a fake result like this: { a: 'b' }.
How to solve it?
I have the following code:
// (API Fetch file) backend/api/fetchBtcCurrency.js
const axios = require('axios');
const URL = 'https://api.coindesk.com/v1/bpi/currentprice/BTC.json';
const getCurrency = async () => {
const response = await axios.get(URL);
return response.data;
};
module.exports = {
getCurrency,
};
// (Model using fetch file) backend/model/cryptoModel.js
const fetchBtcCurrency = require('../api/fetchBtcCurrency');
const getBtcCurrency = async () => {
const responseFromApi = await fetchBtcCurrency.getCurrency();
return responseFromApi;
};
module.exports = {
getBtcCurrency,
};
// (My test file) /backend/__tests__/cryptoBtc.test.js
require("dotenv").config();
const frisby = require("frisby");
const URL = "http://localhost:4000/";
describe("Testing GET /api/crypto/btc", () => {
beforeEach(() => {
jest.mock('../api/fetchBtcCurrency');
});
it('Verify if returns correct response with status code 200', async () => {
const fetchBtcCurrency = require('../api/fetchBtcCurrency').getCurrency;
fetchBtcCurrency.mockImplementation(() => (JSON.stringify({ a: 'b'})));
const defaultExport = await fetchBtcCurrency();
expect(defaultExport).toBe(JSON.stringify({ a: 'b'})); // This assert works
await frisby
.get(`${URL}api/crypto/btc`)
.expect('status', 200)
.expect('json', { a: 'b'}); // Integration test with Frisby does not work correctly.
});
});
Response[
{
I hid the lines to save screen space.
}
->>>>>>> does not contain provided JSON [ {"a":"b"} ]
];
This is a classic lost reference problem.
Since you're using Frisby, by looking at your test, it seems you're starting the server in parallel, correct? You first start your server with, say npm start, then you run your test with npm test.
The problem with that is: by the time your test starts, your server is already running. Since you started your server with the real fetchBtcCurrency.getCurrency, jest can't do anything from this point on. Your server will continue to point towards the real module, not the mocked one.
Check this illustration: https://gist.githubusercontent.com/heyset/a554f9fe4f34101430e1ec0d53f52fa3/raw/9556a9dbd767def0ac9dc2b54662b455cc4bd01d/illustration.svg
The reason the assertion on the import inside the test works is because that import is made after the mock replaces the real file.
You didn't share your app or server file, but if you are creating the server and listening on the same module, and those are "hanging on global" (i.e: being called from the body of the script, and not part of a function), you'll have to split them. You'll need a file that creates the server (appending any route/middleware/etc to it), and you'll need a separate file just to import that first one and start listening.
For example:
app.js
const express = require('express');
const { getCurrency } = require('./fetchBtcCurrency');
const app = express()
app.get('/api/crypto/btc', async (req, res) => {
const currency = await getCurrency();
res.status(200).json(currency);
});
module.exports = { app }
server.js
const { app } = require('./app');
app.listen(4000, () => {
console.log('server is up on port 4000');
});
Then, on your start script, you run the server file. But, on your test, you import the app file. You don't start the server in parallel. You'll start and stop it as part of the test setup/teardown.
This will give jest the chance of replacing the real module with the mocked one before the server starts listening (at which point it loses control over it)
With that, your test could be:
cryptoBtc.test.js
require("dotenv").config();
const frisby = require("frisby");
const URL = "http://localhost:4000/";
const fetchBtcCurrency = require('./fetchBtcCurrency');
const { app } = require('./app');
jest.mock('./fetchBtcCurrency')
describe("Testing GET /api/crypto/btc", () => {
let server;
beforeAll((done) => {
server = app.listen(4000, () => {
done();
});
});
afterAll(() => {
server.close();
});
it('Verify if returns correct response with status code 200', async () => {
fetchBtcCurrency.getCurrency.mockImplementation(() => ({ a: 'b' }));
await frisby
.get(`${URL}api/crypto/btc`)
.expect('status', 200)
.expect('json', { a: 'b'});
});
});
Note that the order of imports don't matter. You can do the "mock" below the real import. Jest is smart enough to know that mocks should come first.

Export logging in Nodejs between files

I have two files in nodejs :
index.js
function.js
The index.js is my main file in which i call the functions inside function.js. In function.js i need to use logging, the problem is i didn't figure out how to use it.
function.js
module.exports = {
Exemplfunciton: async () => {
app.log('#### This is just an exemple im trying to run')
}
checkCalcul:async(a,b) = > {
log.(`The Val of A : ${a}, the Val of B: ${b}`
return a+b
}
}
index.js
const functionToCall = require('/function.js)
module.exports = app => {
functionToCall.Exemplfunciton()
functionToCall.checkCalcul(4,5)
}
Will return
app is not defined
tried it without the app in the function.js it returned to me
log not defined.
I only need to use the app.log between the functions ( my main one the index.js and the function.js )
Pass as an argument
module.exports = app => {
functionToCall.Exemplfunciton(app) // add here
}
Then consume
module.exports = {
Exemplfunciton: async (app) => { // add here
app.log('#### This is just an exemple im trying to run')
}
}
To log in Node.js, you should use console https://nodejs.org/api/console.html
Example
module.exports = {
ExampleFunction: async () => {
console.log('#### This is just an example I\'m trying to run')
}
}
const functionToCall = require('./function.js')
functionToCall.ExampleFunction() // logs #### This is just an example I\'m trying to run
Consider extracting the log functionality out into its own file that can be referenced by function.js, index.js, and anything else in your app. For example:
logger.js
module.exports = {
log: function() {
/* aggregate logs and send to your logging service, like TrackJS.com */
}
}
function.js
var logger = require(“./log.js”);d
module.exports = {
exampleFunction: function() {
logger.log(“foo bar”);
}
};
index.js
var functions = require(“./functions.js”);
var logger = require(“./log.js”);
functions.exampleFunction();
logger.log(“foo”);
You should send the logs off to a service like TrackJS to aggregate, report, and alert you to production problems.

node - using jest with esm package

I was wondering how I would incorporate the esm package https://www.npmjs.com/package/esm with jest on a node backend.
I tried setting up a setup file with require("esm") and require("esm")(module) at the very top of the file, but it's still giving me the SyntaxError: Unexpected token error.
I would have previously used node -r esm but jest doesn't support this.
When you perform require("esm")(module), think of it as you are creating an esm-transformer function that is pending a file to be transformed into an ES module.
Here's my attempt with node v8+ with:
default jest configuration
default esm configuration
utils-1.js:
export const add = (a, b) => a + b;
utils-2.js:
export const multiAdd = array => array.reduce((sum, next) => sum + next, 0)
_test_/utils-1.assert.js
import { add } from '../utils-1';
describe('add(a,b)', () => {
it('should return the addtion of its two inputs', () => {
expect(add(1,2)).toBe(3);
});
});
_test_/utils-2.assert.js
import { multiAdd } from '../utils-2';
describe('multiAdd(<Number[]>)', () => {
it('should return a summation of all array elements', () => {
expect(multiAdd([1,2,3,4])).toBe(10);
})
});
_test_/utils.test.js
const esmImport = require('esm')(module);
const utils_1 = esmImport('./utils-1.assert')
const utils_2 = esmImport('./utils-2.assert')
Hope this helps!

spying on a dependency in a nodejs unit test

I'm using mocha and sinon for nodejs unit tests. I have the following
users.js
const Database = require('./lib/Database');
exports.setupNewUser = (name) => {
var user = {
name: name
};
try {
Database.save(user);
}
catch(err) {
console.error('something failed');
}
}
Database.js
exports.save = (user) => {
console.log(`saving: ${user}`);
};
userTest.js
const sinon = require('sinon');
require('chai').should();
const users = require('../src/users');
describe('users', () => {
it('should log an error when the Database save fails', () => {
var databaseSpy = sinon.spy(Database, 'save').throws(); // this is supposed to work??
users.setupNewUser('Charles');
databaseSpy.should.be.called;
});
});
According to the sinon tutorials I've read, I should be able to create that databaseSpy but I keep getting this error: ReferenceError: Database is not defined
What am I missing?
This seems like it might be a pathing issue. Your require might not be getting the correct path.
users.js
const Database = require('./lib/Database');
Where is /lib/Database in relation with the users.js file? I think that might be a good place to start looking.

Jest - Testing Module Multiple Times in One Test Suite

I have a TypeScript module (should be irrelevant, as I think this affect JS too) and I'm trying to test a module I have. The module imports lots of data from external files and chooses which data should be returned based on the a variable.
I'm attempting to run some tests where I update that variable, re-require the module and run further tests in one file. But my issue is that the require of the file only runs once. I guess it's being cached. Is it possible to tell Jest's require function not to cache or to clear the cache between tests?
Here's some stripped back code of what I'm trying to achieve:
module.ts
import { getLanguage } from "utils/functions";
import * as messagesEn from "resources/translations/en";
import * as messagesFr from "resources/translations/fr";
// Determine the user's default language.
const language: string = getLanguage();
// Set messages based on the language.
let messages: LocaleMessages = messagesEn.default;
if (languageWithoutRegionCode === "fr") {
messages = messagesFr.default;
}
export { messages, language };
test.ts
import "jest";
// Mock the modules
const messagesEn = { "translation1": "English", "translation2": "Words" };
const messagesFr = { "translation1": "Francais", "translation2": "Mots" };
const getLangTest = jest.fn(() => "te-ST");
const getLangEn = jest.fn(() => "en-GB");
const getLangFr = jest.fn(() => "fr-FR");
jest.mock("resources/translations/en", () => ({"default": messagesEn}));
jest.mock("resources/translations/fr", () => ({"default": messagesFr}));
jest.mock("utils/functions", () => ({
getLanguage: getLangTest
})
);
describe("Localisation initialisation", () => {
it("Sets language", () => {
const localisation = require("./localisation");
expect(getLangTest).toHaveBeenCalled();
expect(localisation.language).toEqual("te-ST");
expect(localisation.messages).toEqual(messagesEn);
});
it("Sets english messages", () => {
// THIS GETS THE MODULE FROM THE CACHE
const localisation = require("./localisation");
expect(getLangEn).toHaveBeenCalled();
expect(localisation.language).toEqual("en-GB");
expect(localisation.messages).toEqual(messagesEn);
});
it("Sets french messages", () => {
// THIS GETS THE MODULE FROM THE CACHE
const localisation = require("./localisation");
expect(getLangFr).toHaveBeenCalled();
expect(localisation.language).toEqual("fr-FR");
expect(localisation.messages).toEqual(messagesFr);
});
});
I'm aware the second and third tests won't work anyway as I'd need to update the "utils/functions" mock. The issue is that the code in module.ts only runs once.
So, many thanks to the Jest folks on Discord. It's possible to actually clear the modules from the cache with the jest.resetModules() function.
So my test.ts file will look as follows:
describe("Localisation initialisation", () => {
beforeEach(() => {
jest.resetModules();
});
it("Sets language", () => {
const localisation = require("./localisation");
// Perform the tests
});
it("Sets english messages", () => {
const localisation = require("./localisation");
// Perform the tests
});
it("Sets french messages", () => {
const localisation = require("./localisation");
// Perform the tests
});
});
The beforeEach() call to jest.resetModules() ensures we're re-running the code in the module.

Categories