Using testdouble I want to replace a dependency - javascript

In my code, I am using the library jsonwebtoken. To expedite the testing process, I would like to replace the library with a fake dependency using testdouble.
To test the creation of my fake dependency, I replaced the functionality of the sign method to just return a basic string. However, I noticed it is not working and is still running the original jsonwebtoken method.
What am I doing wrong here?
main.js
const jwt = require('jsonwebtoken');
const signHere = (id) => {
const token = jwt.sign({ id }, 'fakesignature');
return token;
}
module.exports = { signHere }
unit.test.js
const { expect } = require('chai');
const td = require('testdouble');
describe('Replace the jsonwebtoken library', function () {
let subject;
before(function () {
subject = require('./main.js');
const fakeJWT = td.replace('jsonwebtoken');
fakeJWT.sign = td.function();
td.when(fakeJWT.sign(td.matchers.anything, td.matchers.anything)).thenReturn('test');
})
it('should replace the return value', function() {
const value = subject.signHere(1);
expect(value).to.equal('test');
})
})

Related

Mocking Secrets Manager module for JavaScript jest unit tests

I'm having trouble getting the AWS Secrets Manager module mocked for the jest unit tests... The part it errors on is the .promise(). When I remove that, the code doesn't work for the real Secrets Manager so I think it needs to stay there. How do I mock the getSecretData function so that getSecretData.promise() will work for the mock?
Here is the SecretsManager.js code:
import AWS from 'aws-sdk';
export class SecretsManager {
constructor() {
AWS.config.update({
region: 'us-east-1',
});
this.secretsManager = new AWS.SecretsManager();
}
async getSecretData(secretName) {
try {
const response = await this.secretsManager.getSecretValue({
SecretId: secretName,
}).promise();
const secretString = response.SecretString;
const parsedSecret = JSON.parse(secretString);
return parsedSecret;
} catch (e) {
console.log('Failed to get data from AWS Secrets Manager.');
console.log(e);
throw new Error('Unable to retrieve data.');
}
}
}
Here is the SecretsManager.test.js code:
import { SecretsManager } from '../utils/SecretsManager';
jest.mock('aws-sdk', () => {
return {
config: {
update(val) {
},
},
SecretsManager: function () {
return {
async getSecretValue({
SecretId: secretName
}) {
return {
promise: function () {
return {
UserName: 'test',
Password: 'password',
};
}
};
}
};
}
}
});
describe('SecretsManager.js', () => {
describe('Given I have a valid secret name', () => {
describe('When I send a request for test_creds', () => {
it('Then the correct data is returned.', async () => {
const mockReturnValue = {
UserName: 'test',
Password: 'password',
};
const logger = getLogger();
const secretManager = new SecretsManager();
const result = await secretManager.getSecretData('test_creds');
expect(result).toEqual(mockReturnValue)
});
});
describe('When I send a request without data', () => {
it('Then an error is thrown.', async () => {
const secretManager = new SecretsManager();
await expect(secretManager.getSecretData()).rejects.toThrow();
});
});
});
});
This is the error I get when running the tests:
this.secretsManager.getSecretValue(...).promise is not a function
Any suggestions or pointers are greatly appreciated!
Thank you for looking at my post.
I finally got it to work... figures it'd happen shortly after posting the question, but instead of deleting the post I'll share how I changed the mock to make it work incase it helps anyone else.
Note: This is just the updated mock, the tests are the same as in the question above.
// I added this because it's closer to how AWS returns data for real.
const mockSecretData = {
ARN: 'x',
Name: 'test_creds',
VersionId: 'x',
SecretString: '{"UserName":"test","Password":"password"}',
VersionStages: ['x'],
CreatedDate: 'x'
}
jest.mock('aws-sdk', () => {
return {
config: {
update(val) {
},
},
SecretsManager: function () {
return {
getSecretValue: function ( { SecretId } ) {
{
// Adding function above to getSecretValue: is what made the original ".promise() is not a function" error go away.
if (SecretId === 'test_creds') {
return {
promise: function () {
return mockSecretData;
}
};
} else {
throw new Error('mock error');
}
}
}
};
}
}});
I ran into this issue as well. There may be a more elegant way to handle this that also allows for greater control and assertion, but I haven't found one. Note that the in-test option may work better with newer versions of Jest.
I personally solved this issue by making use of manual mocks and a custom mock file for aws-sdk. In your case, it would look something like the following:
# app_root/__tests__/__mocks__/aws-sdk.js
const exampleResponse = {
ARN: 'x',
Name: 'test_creds',
VersionId: 'x',
SecretString: '{"UserName":"test","Password":"password"}',
VersionStages: ['x'],
CreatedDate: 'x'
};
const mockPromise = jest.fn().mockResolvedValue(exampleResponse);
const getSecretValue = jest.fn().mockReturnValue({ promise: mockPromise });
function SecretsManager() { this.getSecretValue = getSecretValue };
const AWS = { SecretsManager };
module.exports = AWS;
Then in your test file:
// ... imports
jest.mock('aws-sdk');
// ... your tests
So, in a nutshell:
Instead of mocking directly in your test file, you're handing mocking control to a mock file, which Jest knows to look for in the __mocks__ directory.
You create a mock constructor for the SecretsManager in the mock file
SecretsManager returns an instance with the mock function getSecretValue
getSecretValue returns a mock promise
the mock promise returns the exampleResponse
Bada boom, bada bing. You can read more here.
I ran into a same issue, I have tried to solve as below. It worked perfectly in my case.
Terminalsecret.ts
import AWS from 'aws-sdk';
AWS.config.update({
region: "us-east-1",
});
const client = new AWS.SecretsManager();
export class Secret {
constructor(){}
async getSecret(secretName: string) {
let secret: any;
const data = await client.getSecretValue({ SecretId: secretName).promise();
if ('SecretString' in data) {
secret = data.SecretString;
} else {
const buff = Buffer.alloc(data.SecretBinary as any, 'base64');
secret = buff.toString('ascii');
}
const secretParse = JSON.parse(secret);
return secretParse[secretName];
}
}
Terminalsecret.test.ts
import { SecretsManager as fakeSecretsManager } from 'aws-sdk';
import { Secret } from './terminalSecret';
jest.mock('aws-sdk');
const setup = () => {
const mockGetSecretValue = jest.fn();
fakeSecretsManager.prototype.getSecretValue = mockGetSecretValue;
return { mockGetSecretValue };
};
describe('success', () => {
it('should call getSecretValue with the argument', async () => {
const { mockGetSecretValue } = setup();
mockGetSecretValue.mockReturnValueOnce({
promise: async () => ({ SecretString: '{"userName": "go-me"}' })
});
const fakeName = 'userName';
const terminalSecretMock: TerminalSecret = new TerminalSecret()
terminalSecretMock.getTerminalSecret(fakeName);
expect(mockGetSecretValue).toHaveBeenCalledTimes(1);
});
});

JavaScript: Jest doesn't recognise static functions

I'm following a tutorial which is using jest to test the javascript. The instructor created a static function called genesis() on a class called Block and it worked for him just fine, but when I tried to do it I got TypeError: block.genesis is not a function. If I remove the static keyword it recognises the function and the test passes.
Here is the class:
const { GENESIS_DATA } = require('./config');
class Block {
constructor({ timestamp, lastHash, hash, data }) {
this.timestamp = timestamp;
this.lastHash = lastHash;
this.hash = hash;
this.data = data;
}
static genesis() {
return new Block(GENESIS_DATA);
}
}
module.exports = Block;
And the test:
const Block = require('./block');
const { GENESIS_DATA } = require('./config');
describe('Block', () => {
const timestamp = 'a-date';
const lastHash = 'a-hash';
const hash = 'another-hash';
const data = ['blockchain', 'data'];
const block = new Block({ timestamp, lastHash, hash, data });
describe('genesis()', () => {
const genesisBlock = block.genesis();
it('returns a block instance', () => {
expect(genesisBlock instanceof Block).toBe(true);
});
it('returns the genesis data', () => {
expect(genesisBlock).toEqual(GENESIS_DATA);
});
});
});
The genesis method is part of the class, not the instance. You want to call Block.genesis() instead of block.genesis()

Is it possible to nock an external service call inside a route

I'm trying to test a rest api which calls an external service.
server.js:
const express = require('express');
const app = express();
const router = express.Router();
const redirectUrl = require('../utils/redirection')
let baseUrl = 'myUrl';
let externalUrl = 'externalUrl';
router.get('/redirect', async (req, res) => {
const { productName } = req.query;
baseUrl = baseUrl + '/' + productName;
externalUrl = externalUrl + '/' + productName;
await redirectUrl(res)(timeOut, externalUrl, baseUrl)
})
app.use(router);
app.listen(3000);
utils/redirection.js:
edirectUrl = res => (timeOut, url, redirectUrl) => {
return new Promise((resolve, reject) => {
let cleared = false;
const timer = setTimeout(() => {
cleared = true;
return resolve(res.redirect(302, redirectUrl));
}, timeout);
return fetch(url)
.then(
response => {
if (!cleared) {
clearTimeout(timer);
const {location} = response.headers;
return resolve(res.redirect(302, location));
}
return null;
})
.catch(err => {
if (!cleared) {
clearTimeout(timer);
return reject(err);
}
});
});
}
test.js:
const requrest = require('request');
const chai = require('chai');
const server = require('../server/server');
const { expect } = chai;
describe('My test', () => {
it('should redirects to the suitable page', () => {
nock('url/to/external/service')
.get('/${productName}')
.reply(302, {
headers: {
location: 'this the page location'
}})
const { status, headers } = request(app).get('/redirect')
expect(status).to.equal(302);
expect(headers.location).to.not.equal(0);
})
})
When I execute the test, the request launches the API call. Then the redirectUrl was called inside. But nock does not intercept the request and the server call the external api. Does nock could intercept a depth HTTP request? or I missed something in my code? Does any suggestion please to resolve this problem?
Without a running example it's hard to tell what is going wrong. But I can tell there's a few things you can change here (I was also struggling with Nock today).
First, since you're dealing with redirects, take a look at this comment. This worked for me: https://github.com/nock/nock/issues/147#issuecomment-71433752
Second, pay attention: the syntax to specify headers is wrong, this is the correct one:
nock('url/to/external/service')
.get('/${productName}')
.reply(302, 'whatever you want', {
location: 'http://the.other.page.location'
})
Third: split your server.js file into two: server.js and app.js.
app.js
const app = express()
...
module.exports = app
server.js
const app = require('./path/to/app.js')
...
app.listen(3000)
This way you can import your app.js into your test suite without the listen part, which will avoid problems with network ports when new tests are run.
Let me know if this helped...

Stubbing auth0 in firebase functions

I have the following Firebase Function that makes use of Auth0 to get a user profile.
'use strict';
const {
dialogflow,
Image,
} = require('actions-on-google')
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
// database collection and key names
const DB_BANK_COLLECTION_KEY = 'bank'
// the action name from all Dialogflow intents
const INTENT_WELCOME_USER = 'Default Welcome Intent';
// Initialize the Auth0 client
var AuthenticationClient = require('auth0').AuthenticationClient;
var auth0 = new AuthenticationClient({
domain: functions.config().familybank.auth0.domain,
clientID: functions.config().familybank.auth0.clientid
});
const app = dialogflow();
app.intent(INTENT_WELCOME_USER, async (conv) => {
console.log('Request: ' + JSON.stringify(conv.request));
const userInfo = await auth0.getProfile(conv.user.access.token)
.catch( function(err) {
console.error('Error getting userProfile from Auth0: ' + err);
conv.close("Something went wrong. Please try again in a few minutes. " + err)
});
console.log('userInfo: ' + JSON.stringify(userInfo));
// check for existing bank, if not present, create it
var bankRef = db.collection(DB_BANK_COLLECTION_KEY).doc(userInfo.email);
const bankSnapshot = await bankRef.get()
})
exports.accessAccount = functions.https.onRequest(app);
I tried to mock auth0 in my tests using the following code (and several permutations), but the actual function always gets called instead of the mock.
const chai = require('chai');
const assert = chai.assert;
const sinon = require('sinon');
// Require firebase-admin so we can stub out some of its methods.
const admin = require('firebase-admin');
const test = require('firebase-functions-test')();
var AuthenticationClient = require('auth0').AuthenticationClient;
var auth0 = new AuthenticationClient({
domain: "mock",
clientID: "mock"
});
describe('Cloud Functions', () => {
let myFunctions, adminInitStub;
before(() => {
test.mockConfig({"familybank": {"auth0": {"domain": "mockdomain", "clientid": "mockid"}}});
adminInitStub = sinon.stub(admin, 'initializeApp');
sinon.stub(admin, 'firestore')
.get(function() {
return function() {
return "data";
}
});
sinon.stub(auth0, 'getProfile').callsFake( function fakeGetProfile(accessToken) {
return Promise.resolve({"email": "daniel.watrous#gmail.com", "accessToken": accessToken});
});
myFunctions = require('../index');
});
after(() => {
adminInitStub.restore();
test.cleanup();
});
describe('accessAccount', () => {
it('should return a 200', (done) => {
const req = {REQUESTDATA};
const res = {
redirect: (code, url) => {
assert.equal(code, 200);
done();
}
};
myFunctions.accessAccount(req, res);
});
});
})
Is there some way to mock auth0 for my offline tests?
I discovered that rather than initialize the Auth0 AuthenticationClient, I could first require the UsersManager, where the getProfile (which wraps getInfo) is defined.
var UsersManager = require('auth0/src/auth/UsersManager');
In my before() method, I can then create a stub for getInfo, like this
sinon.stub(UsersManager.prototype, 'getInfo').callsFake( function fakeGetProfile() {
return Promise.resolve({"email": "some.user#company.com"});
});
All the calls to auth0.getProfile then return a Promise that resolves to the document shown in my stub fake function.

How to mock a function in another function nodejs

Trying to write a unittest for the below module in /utility/sqsThing.js. However I'm having diffuculty mocking the sqs.sendMessage method. Anyone know how I should go about this. I'm using the sinon library, and mocha for running the tests.
The function that I'm trying to unittest utility/sqsThing.js:
const AWS = require('aws-sdk');
AWS.config.update({ region: 'us-east-1' });
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
const outputQueURL = 'https:awsUrl';
const SQSOutputSender = (results) => {
const params = {
MessageBody: JSON.stringify(results),
QueueUrl: outputQueURL,
};
// Method that I want to mock
sqs.sendMessage(params, function (err, data) {
if (err) {
console.log('Error');
} else {
console.log('Success', data.MessageId);
}
});
};
My attempt at mocking the sqs.sendMessage method in a unittest sqsThingTest.js:
const sqsOutputResultSender = require('../utility/sqsThing');
const AWS = require('aws-sdk');
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
const mochaccino = require('mochaccino');
const { expect } = mochaccino;
const sinon = require('sinon');
describe('SQS thing test', function() {
beforeEach(function () {
sinon.stub(sqs, 'sendMessage').callsFake( function() { return 'test' });
});
afterEach(function () {
sqs.sendMessage.restore();
});
it('sqsOutputResultSender.SQSOutputSender', function() {
// Where the mock substitution should occur
const a = sqsOutputResultSender.SQSOutputSender('a');
expect(a).toEqual('test');
})
});
Running this unittest with mocha tests/unit/sqsThingTest.js however I get:
AssertionError: expected undefined to deeply equal 'test'.
info: Error AccessDenied: Access to the resource https://sqs.us-east-1.amazonaws.com/ is denied..
It looks like the mock did not replace the aws api call. Anyone know how I can mock sqs.SendMessage in my test?
You could use rewire js it is a library that lets you inject mocked properties into your module you want to test.
Your require statement would look something like this:
var rewire = require("rewire");
var sqsOutputResultSender = rewire('../utility/sqsThing');
Rewire will allow you to mock everything in the top-level scope of you sqsThing.js file.
Also you need to return the value of sqs.sendMessage this will remove the issue expected undefined to deeply equal 'test'
Your original file would look the same just with a return statement.
//utility/sqsThing.js
const AWS = require('aws-sdk');
AWS.config.update({ region: 'us-east-1' });
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
const outputQueURL = 'https:awsUrl';
const SQSOutputSender = (results) => {
const params = {
MessageBody: JSON.stringify(results),
QueueUrl: outputQueURL,
};
// Method that I want to mock
return sqs.sendMessage(params, function (err, data) {
if (err) {
console.log('Error');
} else {
console.log('Success', data.MessageId);
}
});
};
You would then write your unit test as follows:
//sqsThingTest.js
var rewire = require("rewire");
var sqsOutputResultSender = rewire('../utility/sqsThing');
const mochaccino = require('mochaccino');
const { expect } = mochaccino;
const sinon = require('sinon');
describe('SQS thing test', function() {
beforeEach(function () {
sqsOutputResultSender.__set__("sqs", {
sendMessage: function() { return 'test' }
});
});
it('sqsOutputResultSender.SQSOutputSender', function() {
// Where the mock substitution should occur
const a = sqsOutputResultSender.SQSOutputSender('a');
expect(a).toEqual('test');
})
});
This example returns an object with a property of sendMessage but this could be replaces with a spy.
Rewire Docs
Try moving the declaration of sqsOutputResultSender after you have stubbed the sendmessage function
var sqsOutputResultSender;
const AWS = require('aws-sdk');
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
const mochaccino = require('mochaccino');
const { expect } = mochaccino;
const sinon = require('sinon');
describe('SQS thing test', function() {
beforeEach(function () {
sinon.stub(sqs, 'sendMessage').callsFake( function() { return 'test' });
sqsOutputResultSender = require('../utility/sqsThing');
});
afterEach(function () {
sqs.sendMessage.restore();
});
it('sqsOutputResultSender.SQSOutputSender', function() {
// Where the mock substitution should occur
const a = sqsOutputResultSender.SQSOutputSender('a');
expect(a).toEqual('test');
})
});

Categories