Jest mocking a function within another function - node.js

I have searched quite a bit, and whilst there are many questions asked about mocking functions, none of the solutions I've found work for my setup, and I'm not quite sure why. I have the following:
questionnaire-service.js
const service = require('./request-service')();
function questionnaireService() {
function createQuestionnaire() {
const opts = {
url: `http://some-url.com`,
body: {
data: "some-data"
}
};
return service.post(opts);
}
return Object.freeze({
createQuestionnaire
});
}
module.exports = questionnaireService;
request-service.js
const got = require('got');
const merge = require('lodash.merge');
function requestService() {
function post(options) {
let opts = {
method: 'POST',
headers: {
accept: 'application/json',
'Content-Type': 'application/json'
},
json: true,
body: options.body
};
opts = merge(opts, options);
return got(opts.url, opts);
}
return Object.freeze({
post
});
}
module.exports = requestService;
I am trying to write tests for the questionnaire service, and want to mock the 'post' function. I have tried the following
questionnaire-service.test.js
const requestService = require('./request-service')();
const questionnaireService = require('./questionnaire-service')();
const createdQuestionnaire = require('./test-fixtures/res/get_questionnaire.json');
describe('questionnaire service routes', () => {
it('Should create a new questionnaire', async () => {
const spy = jest.spyOn(requestService.post);
spy.mockReturnValue(createdQuestionnaire);
const response = await questionnaireService.createQuestionnaire();
expect(requestService.post).toBeCalled();
expect(response).toMatch(createdQuestionnaire);
}
it('Should create a new questionnaire', async () => {
jest.doMock('./questionnaire-service', () =>
jest.fn(() => ({
createQuestionnaire: () => createdQuestionnaire
}))
);
const response = await questionnaireService.createQuestionnaire();
expect(questionnaireService.createQuestionnaire).toBeCalled();
expect(response).toMatch(createdQuestionnaire);
}
it('Should create a new questionnaire', async () => {
jest.doMock('./request-service', () =>
jest.fn(() => ({
post: () => createdQuestionnaire
}))
);
const response = await questionnaireService.createQuestionnaire();
expect(requestService.post).toBeCalled();
expect(response).toMatch(createdQuestionnaire);
}
});
All of the above yield the same error: RequestError: getaddrinfo ENOTFOUND some_token some_token:443 which sounds like it's being thrown by the 'GOT' module not finding a url to hit. Can someone shed some light on how to get this to work correctly?

When you make a require("somemodule") it loads this module and executes it, so it executes require calls inside this module. Then all the modules are cached, so whenever you make a require again, it doesn't execute again, and returns already cached dependencies.
When you do doMock, you need to reset this cache, and make new require in your test for everything, that depends on mocked module.
jest.doMock('./questionnaire-service', () =>
jest.fn(() => ({
createQuestionnaire: () => createdQuestionnaire
}))
);
jest.resetModules();
const questionnaireService = require(`questionnaire-service`)();
const response = await questionnaireService.createQuestionnaire();

Related

Electron fetch is not defined when calling function from global shortcuts

I'm getting an error that fetch is not defined when calling my electron code from a global shortcut handler but not when calling it via IPC from the renderer.
Here's my code:
[utils/api.ts]
export const callAPI = async (inputText: string) => {
const data = {
input_text: inputText,
};
const res = await fetch(`${backendUrl()}/api/x`, {
method: 'post',
headers: jsonHeaders,
body: JSON.stringify(data),
});
[main.ts]
app
.whenReady()
.then(() => {
createWindow();
registerGlobalShortcuts(); // registering here
app.on('activate', () => {
if (mainWindow === null) createWindow();
});
})
.catch(console.log);
[globalShortcuts.ts]
import { callAPI } from './utils/api.ts';
const { globalShortcut } = require('electron');
const { clipboard } = require('electron');
const generateOutput = () => {
const text = clipboard.readText();
console.log('text', text);
callAPI(text);
};
export const registerGlobalShortcuts = () => {
globalShortcut.register('Control+CommandOrControl+C', () => {
console.log('shortcut hit');
generateOutput();
});
};
When I run this, and hit the keyboard shortcut, I get this error in the terminal:
(node:23565) UnhandledPromiseRejectionWarning: ReferenceError: fetch is not defined
at callOpenAI (/Users/hima/Workspace/general-desktop-agent/desktop-electron/src/main/lib
/textManipulation.ts:38:15)
at generateTextContinuationPrediction (/Users/hima/Workspace/general-desktop-agent/deskt
op-electron/src/main/globalShortcuts.ts:11:13)
at Function.<anonymous> (/Users/hima/Workspace/general-desktop-agent/desktop-electron/sr
c/main/globalShortcuts.ts:17:5)
But also in my code, in my renderer, I also call window.electron.callAPI(text) there as well, and fetch is found when doing that and the call works perfectly.
So I don't understand why when making the API call from the global shortcut that it says fetch is not defined?

Jest run async endpoints beforeall, afterall tests

[Junior dev!!]
->Node v18.08
->jest 29.0.1
->MySQL
Hi i m running several jest test in diferent folders of each endpoint on my API. I have several files where i m creating an account => test several endpoints => clean DB.. I have at least 14 files like these. So.. When an endpoint in those 14 file is not working the test doesn't go until the end and clean the DB so i need to go back and change the user name.. A clearly waste of time..
I'm learning about the Hook beforeAll and afterAll but they are not working.
i will show here my code without the hooks (test working)
const axios = require("axios");
const API_URL = process.env.API_TEST_URL;
let TOKEN;
//Creating User
test("POST test register user", async () => {
const data = {
pseudo: "TEST",
password: "123",
date: "2022-08-29 16:31:25",
};
const url = `${API_URL}/register`;
const response = await axios.post(url, data);
expect(response.status).toBe(200);
expect(response.data).toHaveProperty("token");
TOKEN = response.data.token;
});
//Endpoints to test
test("POST/test", async () => {
const url = `${API_URL}/data/actions`;
const data = {
action: "running",
date: "2021-09-30 18:14:24",
};
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.post(url, data, { headers });
expect(response.status).toBe(200);
});
//Deleting all info from DB
test("DELETE test account", async () => {
const url = `${API_URL}/user/delete/all-data`;
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.delete(url, { headers });
expect(response.status).toBe(200);
});
And when i want to add the hooks doesn't work
const axios = require("axios");
const API_URL = process.env.API_TEST_URL;
let TOKEN;
//creating before all an user
beforeAll(() => {
test("POST test register user", async () => {
const data = {
pseudo: "TEST",
password: "123",
date: "2022-08-29 16:31:25",
};
const url = `${API_URL}/register`;
const response = await axios.post(url, data);
expect(response.status).toBe(200);
expect(response.data).toHaveProperty("token");
TOKEN = response.data.token;
});
});
//testing endpoints
test("POST/action test", async () => {
const url = `${API_URL}/data/actions`;
const data = {
action: "running",
date: "2021-09-30 18:14:24",
};
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.post(url, data, { headers });
expect(response.status).toBe(200);
});
// if the endpoint doesn't work afterAll clean all DB
afterAll(() => {
test("DELETE test account", async () => {
const url = `${API_URL}/user/delete/all-data`;
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.delete(url, { headers });
expect(response.status).toBe(200);
});
});
what do you think. Could you help me ? Because the info on jest is only showing how to do it with a function.. Can we test something inside beforeAll & afterAll ??
I have something similar in a project that I'm working on.
I had to make sure that the beforeAll was an async function and put the await.
beforeAll(async () => {
connection = await createConnection();
await connection.runMigrations();
const id = uuidv4();
const password = await hash('admin', 8);
await connection.query(
`insert into users (id, name, email, password, is_admin, created_at, driver_license)
values ('${id}', 'Admin', 'admin#test.com.br', '${password}', true, 'now()', 'XXXXXXX')`
);
});
afterAll(async () => {
await connection.dropDatabase();
await connection.close();
});
Another thing is that I have done a test using a test function inside the beforeAll and it has returned an error, so maybe just executing the post without the test may work.
Edit: Reading the docs about the test inside the beforeAll and beforeEach, it says that you can not use a test inside it.

Jest mocks only work at top of file, never inside "describe" or "it" blocks

I'm trying to introduce a small change to an existing project without unit tests and decided I'd try to learn enough about nodejs and jest to include tests with my change. However, I cannot get mocks to work like I'd expect in, say, python. The project uses the "kubernetes-client" library from godaddy and tries to create a config object from the envvar "KUBECONFIG", like this:
// a few lines into server.js
// Instantiate Kubernetes client
const Client = require('kubernetes-client').Client
const config = require('kubernetes-client').config;
if (process.env.KUBECONFIG) {
client = new Client({
config: config.fromKubeconfig(config.loadKubeconfig(process.env.KUBECONFIG)),
version: '1.13'
});
}
else {
client = new Client({ config: config.getInCluster(), version: '1.9' });
}
In my testing environment, I don't want any API calls, so I'm trying to mock it out:
// __tests__/server.test.js
// Set up mocks at the top because of how server.js sets up k8s client
const k8sClient = require('kubernetes-client');
const narp = 'narp';
jest.mock('kubernetes-client', () => {
const noConfigmaps = jest.fn(() => {
throw narp;
});
const namespaces = jest.fn().mockReturnValue({
configmaps: jest.fn().mockReturnValue({
get: noConfigmaps
})
});
const addCustomResourceDefinition = jest.fn().mockReturnThis()
const mockClient = {
api: {
v1: {
namespaces
}
},
addCustomResourceDefinition: jest.fn().mockReturnThis(),
};
return {
Client: jest.fn(() => mockClient),
config: {
fromKubeconfig: jest.fn().mockReturnThis(),
loadKubeconfig: jest.fn().mockReturnThis(),
getInCluster: jest.fn().mockReturnThis()
},
};
});
const app = require('../server.js')
const supertest = require('supertest');
const requestWithSuperTest = supertest(app.app);
describe('Testing server.js', () => {
afterAll(() => {
app.server.close();
});
describe('Tests with k8s client throwing error when fetching configmaps', () => {
it("finds a resource's ingressGroup by name", () => {
var resource = {
"spec": {
"ingressClass": "foo",
"ingressTargetDNSName": "foo"
}
};
var ingressGroups = [
{
"ingressClass": "bar",
"hostName": "bar",
"name": "barName"
},
{
"ingressClass": "foo",
"hostName": "foo",
"name": "fooName"
}
];
expect(app.findMatchingIngressGroupForResource(resource, ingressGroups)).toBe("fooName");
});
it('GET /healthcheck should respond "Healthy"', async () => {
const resp = await requestWithSuperTest.get('/healthcheck');
console.log("Response in Testing Endpoints: " + JSON.stringify(resp));
expect(resp.status).toEqual(200);
expect(resp.type).toEqual(expect.stringContaining('text'));
expect(resp.text).toEqual('Healthy');
});
it('Tests getIngressGroups() rejects with error when it cannot get configmaps', async () => {
app.getIngressGroups()
.then()
.catch(error => {
expect(error).toEqual("Failed to fetch Ingress Groups: " + narp);
});
});
});
});
With this setup, the tests pass (although I suspect it's meaningless). If I try to move the mocks inside the describe or it block using a beforeEach function (or not) so that I can change the behavior to return mock data instead of throwing an error, I immediately get errors with the k8s client complaining it can't find my kubeconfig/clusterconfig:
$ npm run testj
> testj
> jest --detectOpenHandles
kubernetes-client deprecated require('kubernetes-client').config, use require('kubernetes-client/backends/request').config. server.js:45:44
kubernetes-client deprecated loadKubeconfig see https://github.com/godaddy/kubernetes-client/blob/master/merging-with-kubernetes.md#request-kubeconfig- server.js:49:42
FAIL __tests__/server.test.js
● Test suite failed to run
ENOENT: no such file or directory, open 'NOT_A_FILE'
44 | if (process.env.KUBECONFIG) {
45 | client = new Client({
> 46 | config: config.fromKubeconfig(config.loadKubeconfig(process.env.KUBECONFIG)),
| ^
47 | version: '1.13'
48 | });
49 | }
at node_modules/kubernetes-client/backends/request/config.js:335:37
at Array.map (<anonymous>)
at Object.loadKubeconfig (node_modules/kubernetes-client/backends/request/config.js:334:28)
at Object.eval [as loadKubeconfig] (eval at wrapfunction (node_modules/kubernetes-client/node_modules/depd/index.js:425:22), <anonymous>:5:11)
at Object.<anonymous> (server.js:46:46)
If anybody has run into this kind of behavior before or sees some obviously-wrong lines, I'd really appreciate any tips or information. Thanks!
I had to change a few things to get this working:
jest.doMock() instead of jest.mock()
use of let app inside the describe block instead of const app at module-scope
a beforeEach() which calls jest.resetModules()
an afterEach() which calls app.close()
in the it block which overrides the mock(s), explicitly call jest.resetModules() before overriding
in the it block which overrides the mock(s), call app.close() and re-initialize app before invoking the actual function-under-test/expect
Resulting test file:
// Set up mocks at the top because of how server.js sets up k8s client
const k8sClient = require('kubernetes-client');
const supertest = require('supertest');
const narp = 'narp';
describe('Testing server.js', () => {
let app;
let requestWithSuperTest;
beforeEach(() => {
jest.resetModules();
jest.doMock('kubernetes-client', () => {
const noConfigmaps = jest.fn(() => {
throw narp;
});
const namespaces = jest.fn().mockReturnValue({
configmaps: jest.fn().mockReturnValue({
get: noConfigmaps
})
});
const addCustomResourceDefinition = jest.fn().mockReturnThis()
const mockClient = {
api: {
v1: {
namespaces
}
},
addCustomResourceDefinition: jest.fn().mockReturnThis(),
};
return {
Client: jest.fn(() => mockClient),
config: {
fromKubeconfig: jest.fn().mockReturnThis(),
loadKubeconfig: jest.fn().mockReturnThis(),
getInCluster: jest.fn().mockReturnThis()
},
};
});
app = require('../server.js');
requestWithSuperTest = supertest(app.app);
});
afterEach(() => {
app.server.close();
});
it("finds a Resource's ingressGroup by name", () => {
var resource = {
"spec": {
"ingressClass": "foo",
"ingressTargetDNSName": "foo"
}
};
var ingressGroups = [
{
"ingressClass": "bar",
"hostName": "bar",
"name": "barName"
},
{
"ingressClass": "foo",
"hostName": "foo",
"name": "fooName"
}
];
expect(app.findMatchingIngressGroupForResource(resource, ingressGroups)).toBe("fooName");
});
it('GET /healthcheck should respond "Healthy"', async () => {
const resp = await requestWithSuperTest.get('/healthcheck');
console.log("Response in Testing Endpoints: " + JSON.stringify(resp));
expect(resp.status).toEqual(200);
expect(resp.type).toEqual(expect.stringContaining('text'));
expect(resp.text).toEqual('Healthy');
});
it('Tests getIngressGroups() rejects with error when it cannot get configmaps', async () => {
expect.assertions(1);
await app.getIngressGroups()
.catch(error => {
expect(error).toEqual("Failed to fetch Ingress Groups: " + narp);
});
});
it('Tests getIngressGroups() succeeds when it gets configmaps', async () => {
expect.assertions(1);
jest.resetModules();
jest.doMock('kubernetes-client', () => {
const noConfigmaps = jest.fn(() => {
console.log('Attempted to get mocked configmaps');
return Promise.resolve({
body: {
items: []
}
});
});
const namespaces = jest.fn().mockReturnValue({
configmaps: jest.fn().mockReturnValue({
get: noConfigmaps
})
});
const addCustomResourceDefinition = jest.fn().mockReturnThis()
const mockClient = {
api: {
v1: {
namespaces
}
},
addCustomResourceDefinition: jest.fn().mockReturnThis(),
};
return {
Client: jest.fn(() => mockClient),
config: {
fromKubeconfig: jest.fn().mockReturnThis(),
loadKubeconfig: jest.fn().mockReturnThis(),
getInCluster: jest.fn().mockReturnThis()
},
};
});
app.server.close();
app = require('../server.js');
await app.getIngressGroups()
.then(result => {
expect(result).toEqual([])
});
});
});

Jest mock Knex transaction

I have the following lambda handler to unit test. It uses a library #org/aws-connection which has a function mysql.getIamConnection which simply returns a knex connection.
Edit: I have added the mysql.getIamConnection function to the bottom of the post
Edit: If possible, I'd like to do the testing with only Jest. That is unless it becomes to complicated
index.js
const {mysql} = require('#org/aws-connection');
exports.handler = async (event) => {
const connection = await mysql.getIamConnection()
let response = {
statusCode: 200,
body: {
message: 'Successful'
}
}
try {
for(const currentMessage of event.Records){
let records = JSON.parse(currentMessage.body);
await connection.transaction(async (trx) => {
await trx
.table('my_table')
.insert(records)
.then(() =>
console.log(`Records inserted into table ${table}`))
.catch((err) => {
console.log(err)
throw err
})
})
}
} catch (e) {
console.error('There was an error while processing', { errorMessage: e})
response = {
statusCode: 400,
body: e
}
} finally {
connection.destroy()
}
return response
}
I have written some unit tests and I'm able to mock the connection.transaction function but I'm having trouble with the trx.select.insert.then.catch functions. H
Here is my testing file
index.test.js
import { handler } from '../src';
const mocks = require('./mocks');
jest.mock('#org/aws-connection', () => ({
mysql: {
getIamConnection: jest.fn(() => ({
transaction: jest.fn(() => ({
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockReturnThis()
})),
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockReturnThis(),
destroy: jest.fn().mockReturnThis()
}))
}
}))
describe('handler', () => {
test('test handler', async () =>{
const response = await handler(mocks.eventSqs)
expect(response.statusCode).toEqual(200)
});
});
This test works partially but it does not cover the trx portion at all. These lines are uncovered
await trx
.table('my_table')
.insert(records)
.then(() =>
console.log(`Records inserted into table ${table}`))
.catch((err) => {
console.log(err)
throw err
})
How can set up my mock #org/aws-connection so that it covers the trx functions as well?
Edit:
mysql.getIamConnection
async function getIamConnection (secretId, dbname) {
const secret = await getSecret(secretId)
const token = await getToken(secret)
let knex
console.log(`Initialzing a connection to ${secret.proxyendpoint}:${secret.port}/${dbname} as ${secret.username}`)
knex = require('knex')(
{
client: 'mysql2',
connection: {
host: secret.proxyendpoint,
user: secret.username,
database: dbname,
port: secret.port,
ssl: 'Amazon RDS',
authPlugins: {
mysql_clear_password: () => () => Buffer.from(token + '\0')
},
connectionLimit: 1
}
}
)
return knex
}
Solution
#qaismakani's answer worked for me. I wrote it slightly differently but the callback was the key. For anyone interested here is my end solution
const mockTrx = {
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockResolvedValue()
}
jest.mock('#org/aws-connection', () => ({
mysql: {
getIamConnection: jest.fn(() => ({
transaction: jest.fn((callback) => callback(mockTrx)),
destroy: jest.fn().mockReturnThis()
}))
}
}))
Updating your mock to look like this might do the trick:
const { mysql } = require("#org/aws-connection");
jest.mock("#org/aws-connection", () => ({
mySql: {
getIamConnection: jest.fn()
}
}));
const mockTrx = {
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockResolveValue() // Resolve any data here
};
mysql.getIamConnection.mockReturnValue({
transaction: jest.fn((callback) => callback(mockTrx)),
});
You need to mock the transaction so that it executes your callback with a dummy trx. To do this, you need to make sure that all the functions inside the trx object return a reference back to it or a promise so that you can chain it appropriately.
Instead of mocking knex implementation, I've written knex-mock-client which allows you to mimic real db with an easy API.
Change your mock implementation with
import { handler } from "../src";
import { getTracker } from "knex-mock-client";
const mocks = require("./mocks");
jest.mock("#org/aws-connection", () => {
const knex = require("knex");
const { MockClient } = require("knex-mock-client");
return {
mysql: {
getIamConnection: () => knex({ client: MockClient }),
},
};
});
describe("handler", () => {
test("test handler", async () => {
const tracker = getTracker();
tracker.on.insert("my_table").responseOnce([23]); // setup's a mock response when inserting into my_table
const response = await handler(mocks.eventSqs);
expect(response.statusCode).toEqual(200);
});
});

How do I export the result of a promise chain to a separate module?

I would like to use the result of googleApi.js inside index.js but am having trouble accessing the data from index.js. The result is either undefined or a promise object depending on the solutions I have tried. Could someone please point me in the right direction?
index.js:
const googleClient = require('./googleApi.js')
let one = '52.4362,4.7291'
let two = '52.2672,5.0813'
const test = async() => {
const result = await googleClient.getTimeAndDistance(one,two)
console.log(result)
}
test()
googleApi.js
const googleMapsClient = require('#google/maps').createClient({
key: 'MY_API_KEY',
Promise: Promise
})
getTimeAndDistance = (origin, destination) => {
googleMapsClient.directions({
origin: origin,
destination: destination,
language: 'en',
units: 'metric',
})
.asPromise()
.then((res) => {
return res.json.routes[0].legs[0]
})
.catch((err) => {
console.log(err)
})
}
module.exports.getTimeAndDistance = getTimeAndDistance
The code in googleApi.js works and I am able to log the result to the console, but I am unable to access that data outside of the module even when I export the function.
In index.js return googleMapsClient from inside getTimeAndDistance to make available the res.json.routes[0].legs[0] outside the getTimeAndDistance.
const googleMapsClient = require('#google/maps').createClient({
key: 'MY_API_KEY',
Promise: Promise
})
getTimeAndDistance = (origin, destination) => {
return googleMapsClient.directions({
//^^^^^
origin: origin,
destination: destination,
language: 'en',
units: 'metric',
})
.asPromise()
.then((res) => {
return res.json.routes[0].legs[0]
})
.catch((err) => {
console.log(err)
})
}
module.exports.getTimeAndDistance = getTimeAndDistance

Resources