Node, Sinon, Mocha mock default exported function - node.js

I'm trying to mock the import nodeVault so that I can set the return value for the read function.
I have the following code:
import nodeVault from 'node-vault';
const getVaultClient = (vaultToken) => {
return nodeVault({
apiVersion: 'v1',
endpoint: process.env.VAULT_URL,
token: vaultToken.auth.client_token,
});
}
const getCredentialsFromVault = async () => {
const vaultToken = await getVaultToken();
const vault = getVaultClient(vaultToken);
const { data } = await vault.read(process.env.VAULT_SECRET_PATH);
return { client_id: data.client_id, client_secret: data.client_secret, grant_type: 'client_credentials' };
};
I then have a unit test:
describe('AttributeLoad', () => {
let sandbox;
let pedApiMock;
let authApiMock;
let vaultAuthenticateMock;
let nodeVaultClientMock;
beforeEach(() => {
sandbox = createSandbox();
process.env = { VAULT_URL: 'testVault', VAULT_SECRET_PATH: 'testPath' };
pedApiMock = sandbox.stub(attributeLoader.pedApi, 'post');
authApiMock = sandbox.stub(attributeLoader.authApi, 'post');
vaultAuthenticateMock = sandbox.stub(vaultAuthAws.prototype, 'authenticate');
nodeVaultClientMock = sandbox.stub(nodeVault.prototype, ''); --------> Not sure what should go here
I have tried read and constructor
});
afterEach(() => {
sandbox.restore();
});
it('Should call api with correct request body and correctly filter successes and rejections', async () => {
const expectedOutput = {
rejectedLines: [
....
],
validLines: [
.....
]
};
const mockAuthResponse = {
data: {
access_token: 'mocktoken',
},
};
const mockVaultToken = {
auth: {
client_token: ''
}
};
const expectedAuthParams = {
client_id: '',
client_secret: '',
grant_type: 'client_credentials',
};
const mockVaultData = {
data: {
client_id: '',
client_secret: '',
}
};
pedApiMock.returns(Promise.resolve(mockResponse));
authApiMock.returns(Promise.resolve(mockAuthResponse));
vaultAuthenticateMock.returns(Promise.resolve(mockVaultToken));
nodeVaultClientMock.returns(Promise.resolve(mockVaultData));
const finalTestData = await attributeLoader.load(testInputData);
assert.calledWith(authApiMock, expectedAuthParams);
expect(finalTestData.rejectedLines).to.deep.equal(expectedOutput.rejectedLines);
expect(finalTestData.validLines).to.deep.equal(expectedOutput.validLines);
});
});
Everything I try gives this error:
"before each" hook for "Should call api with correct request body and correctly filter successes and rejections":
TypeError: Cannot stub non-existent property read* ---> or any function I try to mock
I've also tried to spy on nodeVault.
Any and all help would be greatly appreciated

So after some more digging I was able to mock node vault by using proxyquire:
import proxyquire from 'proxyquire';
const attributeLoader = proxyquire('../src/attributeLoader', { 'node-vault': () => { return { read: () => mockVaultData } } });
and that fixed it.

Related

Jest Mock Implementation is not working, instead original function is being called

I am trying to test an API by mocking the database function, but the imported function is being called instead of the mocked one.
Here are the code snippets
const supertest = require('supertest');
const axios = require('axios');
const querystring = require('querystring');
const { app } = require('../app');
const DEF = require('../Definition');
const tripDb = require('../database/trip');
const request = supertest.agent(app); // Agent can store cookies after login
const { logger } = require('../Log');
describe('trips route test', () => {
let token = '';
let companyId = '';
beforeAll(async (done) => {
// do something before anything else runs
logger('Jest starting!');
const body = {
username: process.env.EMAIL,
password: process.env.PASSWORD,
grant_type: 'password',
client_id: process.env.NODE_RESOURCE,
client_secret: process.env.NODE_SECRET,
};
const config = {
method: 'post',
url: `${process.env.AUTH_SERV_URL}/auth/realms/${process.env.REALM}/protocol/openid-connect/token`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
data: querystring.stringify(body),
};
const res = await axios(config);
token = res.data.access_token;
done();
});
const shutdown = async () => {
await new Promise((resolve) => {
DEF.COM.RCLIENT.quit(() => {
logger('redis quit');
resolve();
});
});
// redis.quit() creates a thread to close the connection.
// We wait until all threads have been run once to ensure the connection closes.
await new Promise(resolve => setImmediate(resolve));
};
afterAll(() => shutdown());
test('post correct data', async (done) => {
const createTripMock = jest.spyOn(tripDb, 'addTrip').mockImplementation(() => Promise.resolve({
pk: `${companyId}_trip`,
uid: '1667561135293773',
lsi1: 'Kotha Yatra',
lsi2: companyId,
name: 'Kotha Yatra',
companyId,
origin: {
address: 'Goa, India',
location: {
lat: 15.2993265,
lng: 74.12399599999999,
},
},
destination: {
address: 'Norway',
location: {
lat: 60.47202399999999,
lng: 8.468945999999999,
},
},
path: [
{
lat: 15.2993265,
lng: 74.12399599999999,
},
{
lat: 60.47202399999999,
lng: 8.468945999999999,
},
],
isDeleted: false,
currentVersion: 1,
geofences: [],
}));
const response = await request.post('/api/trips').set('Authorization', `Bearer ${token}`).send(tripPayload);
expect(createTripMock).toHaveBeenCalled();
expect(response.status).toEqual(200);
expect(response.body.status).toBe('success');
done();
});
});
The database function:
const addTrip = (trip) => {
// const uid = trip.uid ? trip.uid : (Date.now() * 1000) + Math.round(Math.random() * 1000);
const uid = (Date.now() * 1000) + Math.round(Math.random() * 1000);
const item = {
pk: `${trip.companyId}_trip`,
uid: `v${trip.version ? trip.version : 0}#${uid}`,
lsi1: trip.name,
lsi2: trip.companyId,
name: trip.name,
companyId: trip.companyId,
origin: trip.origin,
destination: trip.destination,
path: trip.path,
isDeleted: false,
};
if (!trip.version || trip.version === 0) {
item.currentVersion = 1;
} else {
item.version = trip.version;
}
if (trip.geofences) item.geofences = trip.geofences;
const params = {
TableName: TN,
Item: item,
ConditionExpression: 'attribute_not_exists(uid)',
};
// console.log('params ', params);
return new Promise((resolve, reject) => {
ddb.put(params, (err, result) => {
// console.log('err ', err);
if (err) {
if (err.code === 'ConditionalCheckFailedException') return reject(new Error('Trip id or name already exists'));
return reject(err);
}
if (!trip.version || trip.version === 0) {
const newItem = { ...item };
delete newItem.currentVersion;
newItem.version = 1;
newItem.uid = `v1#${item.uid.split('#')[1]}`;
const newParams = {
TableName: TN,
Item: newItem,
ConditionExpression: 'attribute_not_exists(uid)',
};
// console.log('new params ', newParams);
ddb.put(newParams, (v1Err, v1Result) => {
// console.log('v1 err ', v1Err);
if (v1Err) return reject(v1Err);
item.uid = item.uid.split('#')[1];
return resolve(item);
});
} else {
item.uid = item.uid.split('#')[1];
return resolve(item);
}
});
});
};
module.exports = {
addTrip,
};
I was mocking the above database function when I was making a request to add API, instead, the original function is being called and I was getting the result that I had written in the mock Implementation.
What should I do to just mock the result ,when the function is called and no implementation of the original function should happen.
Even this did not give an error
expect(createTripMock).toHaveBeenCalled();
Still the database function call is happening
I tried using mockReturnValue, mockReturnValueOnce, mockImplemenationOnce but not luck.
Can anyone help me with this?

Cypress automation test continuously generating authentication token while doing authentication test in SharePoint Sites

All the test is passing successfully but still the cypress test is generating token continuously.
cypress.config.js
const { defineConfig } = require("cypress");
const spauth = require("node-sp-auth");
let getLoginTest = async () => {
const username = "****#****.com";
const password = "******";
const pageUrl = "https://*****.sharepoint.com"
// Connect to SharePoint
const data = await spauth.getAuth(pageUrl, {
username: username,
password: password
});
return data;
};
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
on('task', {
// deconstruct the individual properties
async getLogin() {
try {
const res = await getLoginTest();
return res;
} catch (error) {
return error;
}
}
});
},
},
});
Below I have mentioned the tests case which I have created."Testspec.cy.js"
// <reference types="cypress" />
require('cypress-xpath')
describe('SharePoint Authentication', () => {
beforeEach(() => {
cy.task("getLogin").then(token => {
cy.visit({
method: "GET",
url: "https://*****.sharepoint.com/SitePages/Home.aspx",
headers: token.headers
});
});
});
it('SharePoint Authentication Test', () => {
cy.xpath('//*[contains(text(),"Customer Dashboard")]').should('be.visible');
cy.get('.alphabet > :nth-child(3)').click();
cy.contains('Leader').click();
});
});
Test Screen
Here is the screenshot of the cypress test

cannot mock a fetch call with `fetch-mock-jest 1.5.1` lib

I'm trying to mock a fetch call using thisfetch-mock-jest but it the code still trys to go to the remote address and eventually fail with error message FetchError: request to https://some.domain.io/app-config.yaml failed, reason: getaddrinfo ENOTFOUND some.domain.io].
Here the the test code
import { AppConfig } from '#backstage/config';
import { loadConfig } from './loader';
import mockFs from 'mock-fs';
import fetchMock from 'fetch-mock-jest';
describe('loadConfig', () => {
beforeEach(() => {
fetchMock.mock({
matcher: '*',
response: `app:
title: Example App
sessionKey: 'abc123'
`
});
});
afterEach(() => {
fetchMock.mockReset();
});
it('load config from remote path', async () => {
const configUrl = 'https://some.domain.io/app-config.yaml';
await expect(
loadConfig({
configRoot: '/root',
configTargets: [{ url: configUrl }],
env: 'production',
remote: {
reloadIntervalSeconds: 30,
},
})
).resolves.toEqual([
{
context: configUrl,
data: {
app: {
title: 'Example App',
sessionKey: 'abc123',
},
},
},
]);
expect(fetchMock).toHaveBeenCalledTimes(1);
});
function defer<T>() {
let resolve: (value: T) => void;
const promise = new Promise<T>(_resolve => {
resolve = _resolve;
});
return { promise, resolve: resolve! };
}
});
loadConfig has the fetch code that I'm trying to mock.
export async function loadConfig(
options: LoadConfigOptions,
): Promise<AppConfig[]> {
const loadRemoteConfigFiles = async () => {
const configs: AppConfig[] = [];
const readConfigFromUrl = async (remoteConfigProp: RemoteConfigProp) => {
const response = await fetch(remoteConfigProp.url);
if (!response.ok) {
throw new Error(
`Could not read config file at ${remoteConfigProp.url}`,
);
}
remoteConfigProp.oldETag = remoteConfigProp.newETag ?? undefined;
remoteConfigProp.newETag =
response.headers.get(HTTP_RESPONSE_HEADER_ETAG) ?? undefined;
remoteConfigProp.content = await response.text();
return remoteConfigProp;
};
.......
return configs;
}
let remoteConfigs: AppConfig[] = [];
if (remote) {
try {
remoteConfigs = await loadRemoteConfigFiles();
} catch (error) {
throw new Error(`Failed to read remote configuration file, ${error}`);
}
}
........ do some stuff with config then return
return remoteConfigs;
}
The config is a yaml file, that eventually gets parsed and converted into config object.
Any idea why is it failing to mock the fetch call?
replaced
import fetchMock from 'fetch-mock-jest';
with
const fetchMock = require('fetch-mock').sandbox();
const nodeFetch = require('node-fetch');
nodeFetch.default = fetchMock;
and fetchMock.mockReset(); with fetchMock.restore();

Jest - How to mock a function inside available function

I have functions in a file as below:
import logger from 'logger-module';
import spir from '../spir';
const spirA = new spir(
process.env.USERNAME,
process.env.PASSWORD,
process.env.API_KEY,
process.env.SMS_WORKSPACE_ID,
process.env.SMS_TEMPLATE_ID
);
const spirSMS = params => {
return new Promise((resolve, reject) => {
spirA.sms(params).then(resolve, reject);
});
};
export const send = params => {
logger.info('Sending new sms...');
const { to: recipients, invalidNumber } = params;
const promises = recipients.map(number =>
spirSMS({ to: number, subject: params.subject, body: params.body })
);
return Promise.all(promises).then(() => {
logger.info(`SMS has been sent to ${recipients.toString()}!`);
return {
message: `SMS has been sent to ${recipients.toString()}`,
data: {
recipients,
invalid_recipients: invalidNumber
}
};
});
};
How to I can mock spirSMS inside recipients.map
I found it to be a loop function so I'm not sure how to mock it.
Many thanks!
I would probably use Classes here for better testability of your code. somethink like this
class SomeClass {
constructor(spirSms) {
this.spirSms_ = spirSms;
}
send(params) {
logger.info('Sending new sms...');
const { to: recipients, invalidNumber } = params;
const promises = recipients.map(number =>
this.spirSms_({ to: number, subject: params.subject, body: params.body })
);
return Promise.all(promises).then(() => {
logger.info(`SMS has been sent to ${recipients.toString()}!`);
return {
message: `SMS has been sent to ${recipients.toString()}`,
data: {
recipients,
invalid_recipients: invalidNumber
}
};
});
}
}
module.exports = SomeClass;
and then you will have some main.js which will instantiate your class and inject spirSms
const spirA = new spir(
process.env.USERNAME,
process.env.PASSWORD,
process.env.API_KEY,
process.env.SMS_WORKSPACE_ID,
process.env.SMS_TEMPLATE_ID
);
const spirSMS = params => {
return new Promise((resolve, reject) => {
spirA.sms(params).then(resolve, reject);
});
};
const someClass = new SomeClass(spirSMS);
someClass.send(params);
Now in your test when you will test SomeClass, you can inject any mock spirSms which will basically do what you are looking for.
Hope this helps.

how to fix octokit.authenticate() is deprecated

I am starting the ginit module but getting the error like this:
=> octokit.authenticate() is deprecated. Use "auth" constructor
option instead.
How can I fix it?
my code
module.exports = {
getInstance: () => {
return octokit;
},
setGithubCredentials : async () => {
const credentials = await inquirer.askGithubCredentials();
octokit.authenticate(
_.extend(
{
type: 'basic',
},
credentials
)
);
},
}
Maybe you code from this article: https://www.sitepoint.com/javascript-command-line-interface-cli-node-js/
And my solution is below
const Octokit = require("#octokit/rest");
const Configstore = require("configstore");
const pkg = require("../package.json");
const _ = require("lodash");
const CLI = require("clui");
const Spinner = CLI.Spinner;
const chalk = require("chalk");
const inquirer = require("./inquirer");
const conf = new Configstore(pkg.name);
module.exports = {
getInstance: () => {
return global.octokit;
},
getStoredGithubToken: () => {
return conf.get("github.token");
},
setGithubCredentials: async () => {
const credentials = await inquirer.askGithubCredentials();
const result = _.extend(
{
type: "basic"
},
credentials
);
global.octokit = Octokit({
auth: result
});
},
registerNewToken: async () => {
const status = new Spinner("Authenticating you, please wait...");
status.start();
try {
const response = await global.octokit.oauthAuthorizations.createAuthorization({
scopes: ["user", "public_repo", "repo", "repo:status"],
note: "ginits, the command-line tool for initalizing Git repos"
});
const token = response.data.token;
if (token) {
conf.set("github.token", token);
return token;
} else {
throw new Error(
"Missing Token",
"GitHub token was not found in the response"
);
}
} catch (err) {
throw err;
} finally {
status.stop();
}
}
};
Try something like:
const Octokit = require('#octokit/rest');
module.export = {
getInstance({username, password}) {
return Octokit({
auth: {
username,
password,
},
});
}
}
The PR introducing the auth property shows some other examples of specifying credentials.

Resources