Import a file after the Jest environment has been torn down - javascript

I'm making a simple API with Express and I'm trying to add tests with Jest but when I try to run the tests it displays the next error:
ReferenceError: You are trying to `import` a file after the Jest environment has been torn down.
at BufferList.Readable (node_modules/readable-stream/lib/_stream_readable.js:179:22)
at BufferList.Duplex (node_modules/readable-stream/lib/_stream_duplex.js:67:12)
at new BufferList (node_modules/bl/bl.js:33:16)
at new MessageStream (node_modules/mongodb/lib/cmap/message_stream.js:35:21)
at new Connection (node_modules/mongodb/lib/cmap/connection.js:52:28)
/home/jonathangomz/Documents/Node/Express/Devotionals/node_modules/readable-stream/lib/_stream_readable.js:111
var isDuplex = stream instanceof Duplex;
^
TypeError: Right-hand side of 'instanceof' is not callable
I'm not sure to trust the result if right after jest break (or something like that):
My test is:
const app = require("../app");
const request = require("supertest");
describe("Testing root router", () => {
test("Should test that true === true", async () => {
jest.useFakeTimers();
const response = await request(app).get("/");
expect(response.status).toBe(200);
});
});
My jest configuration on package.json:
"jest": {
"testEnvironment": "node",
"coveragePathIgnorePatterns": [
"/node_modules/"
]
}
Notes:
I read about jest.useFakeTimers() but It's not working and I'm not sure if I'm using in the wrong way. I also tried adding it to the beforeEach method but nothing.

In my case, I had to add the package to transformIgnorePatterns in the jest config.

Add jest.useFakeTimers('modern') before the asynchronous call. Add jest.runAllTimers() after the asynchronous call. This will fast-forward timers for you.
const app = require("../app")
const request = require("supertest")
describe("Testing root router", () => {
test("Should test that true === true", async () => {
//Before asynchronous call
jest.useFakeTimers("modern")
const response = await request(app).get("/")
//After asynchronous call
jest.runAllTimers()
expect(response.status).toBe(200)
})
})

Try adding --testTimeout=10000 flag when calling jest, it works for me.
Information based on Testing NodeJs/Express API with Jest and Supertest
--testTimeout flag - This increases the default timeout of Jest which is 5000ms. This is important since the test runner needs to refresh the database before running the test

By adding jest.useFakeTimers() just after all your import.
What about making your test async ?
const app = require("../app");
const request = require("supertest");
describe("Testing root router",async () => {
test("Should test that true === true", async () => {
jest.useFakeTimers();
const response = await request(app).get("/");
expect(response.status).toBe(200);
});
});

Related

Jest TestEnvironment - TypeError: Cannot add property next, object is not extensible

I want to test a node API using Jest. I am testing the routes and websockets. Testing the routes was no problem. I simply started the server using the setupFile option.
To test the websockets I wanted to pass the io object to the tests. This is not possible through the setupFile since the tests are running in their own context. Thus I changed to the testEnvironment option. My testEnvironment file is the following
const NodeEnvironment = require('jest-environment-node');
class CustomEnvironment extends NodeEnvironment {
constructor(config, context) {
super(config, context);
this.setupServer();
}
async setup() {
await super.setup();
console.log('Setup Test Environment.');
this.global.io = this.io;
this.global.baseUrl = 'http://localhost:' + this.port;
}
async teardown() {
await super.teardown();
console.log('Teardown Test Environment.');
}
getVmContext() {
return super.getVmContext();
}
setupServer() {
// Code for starting the server and attaching the io object
this.port = portConfig.http;
this.io = io;
}
}
module.exports = CustomEnvironment;
This works and the io object is passed to the test. I have multiple test files for different parts of the API. Running those with the setupFile was no problem but now Jest is only able to run one file. All following test suites are failing with the following message
● Test suite failed to run
TypeError: Cannot add property next, object is not extensible
at Function.handle (node_modules/express/lib/router/index.js:160:12)
at Function.handle (node_modules/express/lib/application.js:174:10)
at new app (node_modules/express/lib/express.js:39:9)
I am not able to find any documentation on that error. I tried disabling some of the test files but it always fails after the first one, no matter which one it is.
The structure of the test files is the following if relevant:
const axios = require('axios');
describe('Test MODULE routes', () => {
const baseUrl = global.baseUrl;
const io = global.io;
const models = require('../../../models'); // sequelize models which are used in tests
describe('HTTP METHOD + ROUTE', () => {
test('ROUTE DESCRIPTION', async () => {
const response = await axios({
method: 'get',
url: baseUrl + 'ROUTE'
});
expect(response.status).toBe(200);
});
});
// different routes
});
I fixed the error. It had nothing to do with jest but with an module.exports invocation in the server setup which overwrote the export of the CustomEnvironment with an express server.

How to call a firebase functions from a unit test using Firebase Emulators?

I have the firebase emulators running.
My application can call a firebase cloud function that can create a custom user claim if the user is allowed to (depending on a value in the database)
I'm trying to invoke that function:
import * as firebase from '#firebase/testing';
import * as fs from 'fs';
const app = firebase.initializeTestApp({
databaseName: 'mydb',
auth: {
uid: 'useruid'
},
});
describe('Firebase Functions', () => {
beforeEach(async () => {
await firebase.loadDatabaseRules({
databaseName: 'mydb',
rules: fs.readFileSync('database.rules.json', 'utf8'),
});
});
afterAll(async () => {
await Promise.all(firebase.apps().map((app) => app.delete()));
});
const cloudfunction = app.functions().httpsCallable('cloudfunction')
it('throws error if user is not authenticated', async () => {
await firebase.assertFail(cloudfunction())
})
});
This is throwing an error:
Error: Error: Response for preflight has invalid HTTP status code 404
In my opinion, you don't need to call firebase from your unit test, because it is not your business is firebase working correctly or not, you need to test your business logic so that means that you will mock the method which calls to the firebase and test expectable returning values of this method, for example, you can write 2 cases when notification will be sent without error and second if firebase service will return failure, for those 2 cases your code should have appropriate behavior.
Testing firebase service will be done by the team working on the firebase
There exists firebase-functions-test for this. This allows you to wrap your cloud functions and call them directly, as well as create stubs for document updates. When running in the context of the emulator, the firebase-admin SDK still interacts with the emulator. A basic setup might look like the following (assuming typescript):
// functions/src/test/functions.test.ts
import {expect} from "chai";
import "mocha";
import firebaseFunctionTest from "firebase-functions-test";
import * as cf from "../index";
const projectId = "demo-project";
const test = firebaseFunctionTest({
projectId,
});
after(() => test.cleanup());
describe("cloud functions", () => {
afterEach(async () => {
await test.firestore.clearFirestoreData(projectId);
});
describe("myFunction", () => {
const myFunction = test.wrap(cf.myFunction);
// Create mock data with the admin SDK and test function.
}
}
In your package.json you can the put a script like.
"scripts": {
"test:unit": "mocha -r ts-node/register --timeout 5000 --exit src/**/*.test.ts",
"test": "npm run build && npx firebase -P demo-project emulators:exec 'npm run test:unit'",
...
}
You might also want to have a look at https://github.com/firebase/quickstart-testing/tree/master/unit-test-cloud-functions
and https://firebase.google.com/docs/functions/unit-testing.

How to mock module which depends implicitly per test in Jest?

I have an integration test where I make actual DB calls to the MongoDB database. But in order to test whether the transaction is expired or not, I need to mock the DB for that particular test. There are many reasons for me to make the actual DB call, I'm mentioning the state just for the sake of this example.
Jest has jest.doMock function but that is helpful only when I wanted to import the function within the test but in my case, It's the DB function which I wanted to mock for that particular test when is getting called inside the express middleware.
There is another option to mock the entire ../db module but that will complicate the tests a lot in my actual project. It would be very easy for me if I can mock the DB call for a specific test and for rest all the tests it should make the real DB calls.
Is there a way to do it in Jest?
// a.ts
import express from "express"
import db from "../db";
const app = express()
app.get("/api/deduct-balance/:txn_id", (req, res) => {
const txn = await db.findById(txn_id)
// return error message if txn expired
if (txn.exipre_at <= new Date()) {
return res.status(401).json({ error: "txn expired" });
}
// otherwise update the txn state
txn.state = "DEDUCTED";
await txn.save()
return res.status(200).json();
});
// a.test.ts
import db from "../db";
describe("mixed tests", () => {
test("should make REAL db calls", async () => {
await axios.get("/api/deduct-balance/123")
const txn = await db.findById("123");
expect(txn.state).toBe("DEDUCTED");
});
test("should use MOCKED value", async () => {
// need a way to mock the DB call so that I can return an expired transaction
// when I hit the API
const { data } = await axios.get("/api/deduct-balance/123")
expect(data).toBe({
error: {
message: "txn expired"
}
});
});
})
Integration tests are overkill for this scenario. Simple unit tests would suffice. They are fast to execute, test exactly one thing and you should have lots of them.
Because you're defining the handler as an anonymous function it's hard to unit test by default. So the first action is to make it easier to test by extracting it.
// deduct-balance-handlers.ts
export const deductBalanceByTransaction = async (req, res) => {
const txn = await db.findById(txn_id)
// return error message if txn expired
if (txn.exipre_at <= new Date()) {
return res.status(401).json({ error: "txn expired" });
}
// otherwise update the txn state
txn.state = "DEDUCTED";
await txn.save()
return res.status(200).json();
}
It will also makes the app configuration more clean.
// a.ts
import express from "express"
import db from "../db";
import { deductBalanceByTransaction } from './deduct-balance-handlers';
const app = express()
app.get("/api/deduct-balance/:txn_id", deductBalanceByTransaction);
Now it's easy to reuse the handler in your test without relying on the web framework or database.
// a.test.ts
import db from "../db";
import { deductBalanceByTransaction } from './deduct-balance-handlers';
jest.mock('../db');
describe("deduct-balance", () => {
test("Expired transaction should respond with 401 status", async () => {
const response = mockResponse();
deductBalanceByTransaction(request, response);
expect(response.status).toBe(401);
});
})
For simplicity's sake I left the part of creating a mock response and mocking the module out of the code. More can be learned about mocking here: https://jestjs.io/docs/en/manual-mocks

Jest mockedCoeus.mockImplementation throws a TypeError

I'm using Jest to write a test and mock a function that calls an HTTP request.
import { mocked } from "ts-jest/utils";
import * as pull from "../src/pull";
import fs = require("fs");
// read the reponse data from a file.
const response = JSON.parse(
fs.readFileSync("./__fixtures__/pr.json", "utf8")
);
// have jest mock the function and set it's response.
jest.mock("../src/pull");
const mockedCoeus = mocked(pull.getPullRequest, true);
mockedCoeus.mockImplementation(async () => {
return response as any;
});
// write the test.
describe("#get details for a PR", () => {
it("should load user data", async () => {
const data = await pull.getPullRequest(165, "data-ios");
expect(data).toBeDefined();
expect(data.updated_at).toEqual("2020-04-10T16:46:30Z");
});
});
The test passes, however, I get the following error when running npm jest
TypeError: mockedCoeus.mockImplementation is not a function
I've looked at other reported errors having to do with the placement of jest.mock however, it does not seem to be the case here. Why is this error thrown but the tests pass? How can I fix it?

How to clear a module mock between tests in same test suite in Jest?

I've mocked some nodejs modules (one of them, for example, is fs). I have them in a __mocks__ folder (same level als node_modules) folder and the module mocking works. However, whichever "between test clearing" option I use, the next test is not "sandboxed". What is going wrong here?
A very simplified example of the mocked fs module is:
// __mocks__/fs.js
module.exports = {
existsSync: jest.fn()
.mockReturnValueOnce(1)
.mockReturnValueOnce(2)
.mockReturnValueOnce(3)
}
I'm simply expecting that in every test, whenever init() is called (see below), existsSync starts again at value 1: the first value of jest.fn().mockReturnValue(). In the testfile I have the following structure:
// init.test.js
const init = require("../init");
const { existsSync } = require("fs");
jest.mock("fs");
describe("initializes script", () => {
afterEach(() => {
// see below!
});
test("it checks for a package.json in current directory", () => {
init();
});
test("it stops script if there's a package.json in dir", () => {
init(); // should be run in clean environment!
});
}
And once again very simplified, the init.js file
const { existsSync } = require("fs");
console.log("value of mocked response : ", existsSync())
I'm getting the following results for existsSync() after the first and second run ofinit() respectively when I run in afterEach():
jest.resetModules() : 1, 2
existsSync.mockReset(): 1, undefined
existsSync.mockClear(): 1, 2
existsSync.mockRestore(): 1, undefined
Somebody know what I'am doing wrong? How do I clear module mock between tests in the same suite? I'll glady clarify if necessary. Thanks!
Reset the modules and require them again for each test:
describe("initializes script", () => {
afterEach(() => {
jest.resetModules()
});
beforeEach(() => {
jest.mock("fs");
})
test("it checks for a package.json in current directory", () => {
const init = require("../init");
init();
});
test("it stops script if there's a package.json in dir", () => {
const init = require("../init");
init();
});
}
I had problems with the solution above. I managed to solve the issue with the next snippet.
afterEach(() => {
Object.keys(mockedModule).forEach(method => mockedModule[method].mockReset())
})
I would prefer to have a native method doing this though. Something like mockedModule.mockReset().
For local variables, the scope of declaration is important.
const mockFunc1 = jest.fn() // possibly bad mock reset/clear between tests
describe('useGetMetaData', () => {
const mockFunc2 = jest.fn() // good mock reset/clear between tests
afterEach(() => {/* reset/clear mocks */})
test.todo('implement tests here')
})

Categories

Resources