I have this code:
var async = require('async'),
util = require('util');
var Parse = require('parse/node');
function signup(userInfo, callback) {
var username = userInfo.username,
email = userInfo.email,
password = userInfo.password;
var user = new Parse.User();
user.set('username', username);
user.set('email', email);
user.set('password', password);
user.signUp(null, {
success: (user) => {
// console.log('BaaS Signup success ' + util.inspect(user));
callback(null, user);
},
error: (user, error) => {
// console.log('BaaS Signup error ' + util.inspect(error));
callback(JSON.stringify(error));
}
});
}
I want to unit test it, and as such need to be able to test the content of the success and error function.
If user was a library, I could use proxyquire and put a stub returning a promise and be done with it, however, user is constructed.
How can I stub Parse.User() so that I can control the resulting object?
This seems to work:
// test `success` callback
let testUser = { user : 'test' };
let stub = sinon.stub(Parse.User.prototype, 'signUp')
.yieldsTo('success', testUser);
You should be able to stub the constructor just like you'd stub any other funtion, and just return your fake object:
sinon.stub(Parse, "User")
.andReturn(fakeUser);
let u = new Parse.User();
//u === fakeUser
While it is ugly as hell, I ve succeed in making my test work with this:
var chai = require('chai'),
expect = chai.expect,
util = require('util');
var proxyquire = require('proxyquire').noPreserveCache();
var sinon = require('sinon');
describe('SignUp', () => {
it('Expect to sign up the user and return the user', (done) => {
var stub = {
parse: {
User: function User() {
this.set = () => {};
this.signUp = (a, c) => {
c.success({id: 'UserID'});
};
}
}
},
baas = proxyquire('./../baas.js', {
'parse/node': stub.parse
});
baas.signup({
username: 'User',
email: 'user#test.com',
password: 'Password'
}, (err, user) => {
try {
expect(err).to.not.be.ok;
expect(user).to.be.ok;
done();
} catch(err) {
done(err);
}
});
});
});
The answer I received made me realize I don't use sinon like I should, and all my test are built by hacking around part of proxyquire and sinon together.
I will need to reread the documentation.
Related
**Edit: Re-written with a simple example that works first:
So I've got a test file and 2 modules.
moduleA has a dependency, moduleB
// moduleA.js
const ModuleB = require('./moduleB');
function functionA() {
return 20 + ModuleB.functionB();
};
module.exports = { functionA };
// moduleB.js
const functionB = () => {
return 10;
}
module.exports = { functionB }
My test file stubs out functionB (returned from moduleB) using proxyquire:
const sinon = require('sinon');
const proxyquire = require('proxyquire');
describe('Unit Tests', function() {
it('should work', () => {
const mockedFn = sinon.stub();
mockedFn.returns(30);
const copyModuleA = proxyquire('./moduleA', {
'./moduleB': {
functionB: mockedFn
}
});
console.log(copyModuleA.functionA());
})
});
So it outputs 50 (stubbed functionB 30 + functionA 20)
Now I'm trying to take this example into my code:
moduleA in this case is a file called validation.js. It is dependent on moduleB, in this case a sequelize model, Person, with the function I want to mock: findOne
validation.js exports module.exports = { validateLogin };, a function that calls validate, which returns a function that uses Person.findOne()
So in my mind, as with the simple example, I need to create a stub, point to the validation module in proxyquire, and reference the dependency and its findOne function. Like this:
const stubbedFindOne = sinon.stub();
stubbedFindOne.resolves();
validationModule = proxyquire('../../utils/validation', {
'../models/Person': {
findOne: stubbedFindOne
}
});
This should stub Person.findOne in validation.js. But it doesn't seem to. And I have no idea why.
let validationModule;
describe('Unit Tests', () => {
before(() => {
const stubbedFindOne = sinon.stub();
stubbedFindOne.resolves();
validationModule = proxyquire('../../utils/validation', {
'../models/Person': {
findOne: stubbedFindOne
}
});
})
it.only('should return 422 if custom email validation fails', async() => {
const wrongEmailReq = { body: {email: 'nik#hotmail.com'} };
const res = {
statusCode: 500,
status: (code) => {this.statusCode = code; return this},
};
const validationFn = validationModule.validateLogin();
const wrongEmail = await validationFn(wrongEmailReq, res, ()=>{});
expect(wrongEmail.errors[0].msg).to.be.equal('Custom Authorisation Error');
return;
})
And this is my validation.js file:
const Person = require('../models/Person');
// parallel processing
const validate = validations => {
return async (req, res, next) => {
await Promise.all(validations.map(validation => validation.run(req)));
const errors = validationResult(req);
if (errors.isEmpty()) {
return next();
}
const error = new Error();
error.message = process.env.NODE_ENV === 'development'? 'Validation Failed':'Error';
error.statusCode = !errors.isEmpty()? 422:500;
error.errors = errors.array({onlyFirstError: true});
next(error);
return error;
};
};
const validateLogin = () => {
const validations = [
body('email')
.isString()
// snip
.custom(async (value, {req}) => {
try{
const person = await Person.findOne({ where: { email: value } });
if(!person) return Promise.reject('Custom Authorisation Error');
} catch(err) {
throw err;
}
})
.trim(),
];
return validate(validations);
}
module.exports = {
validateLogin
};
So the code in both the small sample and my app is correct, apart from how I stub the function. It shouldn't resolve or reject anything (I tried both out of desperation). It should return null in order to satisfy the conditional rather than jump to the catch block:
try{
const person = await Person.findOne({ where: { email: value } });
if(!person) return Promise.reject('Custom Authorisation Error');
} catch(err) {
throw err;
}
Hope the simple example helps someone else with proxyquire though
I'm having a problem right now when i want to remove some code out of my route to put it into a service. I'm just trying to follow the best practices of developing an application.
This is my route right now:
const express = require('express');
const cityRouter = express.Router();
const axios = require('axios');
const NodeCache = require('node-cache');
const myCache = new NodeCache();
cityRouter.get('/:cep', async (request, response) => {
try {
const { cep } = request.params;
const value = myCache.get(cep);
if (value) {
response.status(200).send({
city: value,
message: 'Data from the cache',
});
} else {
const resp = await axios.get(`https://viacep.com.br/ws/${cep}/json/`);
myCache.set(cep, resp.data, 600);
response.status(200).send({
city: resp.data,
message: 'Data not from the cache',
});
}
} catch (error) {
return response.status(400);
}
});
module.exports = cityRouter;
I'm using axios to retrieve data from an API, where i have a variable called "cep" as a parameter and then using node-cache to cache it.
And it works with out problems:
enter image description here
But, when i try to put the same code into a service, and then call it into my route:
My service:
const axios = require('axios');
const NodeCache = require('node-cache');
const myCache = new NodeCache();
function verificaCache(cep) {
return async function (request, response, next) {
const value = myCache.get(cep);
console.log(cep);
if (value) {
response.status(200).send({
city: value,
message: 'Data from the cache',
});
} else {
const resp = await axios.get(`https://viacep.com.br/ws/${cep}/json/`);
myCache.set(cep, resp.data, 600);
response.status(200).send({
city: resp.data,
message: 'Data not from the cache',
});
}
next();
};
}
module.exports = verificaCache;
My route using the service:
const express = require('express');
const cityRouter = express.Router();
const verificaCache = require('../services/VerificaCacheService');
cityRouter.get('/:cep', async (request, response) => {
const { cep } = request.params;
verificaCache(cep);
response.status(200);
});
module.exports = cityRouter;
By some reason, it doesn't work:
enter image description here
What is the problem that i can't see? I'm a beginner so i'm kinda lost right now.
You have created a high-order function by returning a function in verificaCache(), so to properly call it you need to do it like that await verificaCache(cep)(req, res), remember, the first time you call it, you have a function being returned, since you want the tasks inside of that function to be executed, you need to call it as well.
Take a reading about high-order functions here: https://blog.alexdevero.com/higher-order-functions-javascript/
My recommendation, you could just get rid of the other function you are returning to simplify your code, and let the service only handle business logic, all the http actions should be handled on the controller level:
// Service
function verificaCache(cep) {
const value = myCache.get(cep);
if (value) {
return { city: value, message: 'Data from the cache'})
}
// No need of an else statement because the
// execution will stop at the first return if the condition passes
const resp = await axios.get(`https://viacep.com.br/ws/${cep}/json/`);
myCache.set(cep, resp.data, 600);
return { city: resp.data, message: 'Data not from the cache'};
}
// Controller
cityRouter.get('/:cep', async (request, response) => {
const { cep } = request.params;
try {
const data = verificaCache(cep);
// Use json() instead of send()
response.status(200).json(data);
} catch(error) {
// Handle errors here
console.log(error);
}
});
Estamos juntos!
Importing certain function from some folder which has needed module with this function doesn't work.
I'm using nodemailer for sending emails. I have 3 different folders with modules. The problem is in importing (require) email sending function to current module from another one. It becomes undefined and error is myFunc is not a function.
I'm doing pretty simple things like requiring function from folder with index.js which includes needed function. But it becomes undefined when I try to use it.
services/mailTransport.js
const nodemailer = require('nodemailer');
const mailTransporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: 'test#test.com',
pass: 'myPassword'
}
});
module.exports = mailTransporter;
services/index.js
const mailTransporter = require("./mailTransporter");
module.exports = { mailTransporter }
utils/mailTemplate.js
const { mailTransporter } = require("../services");
const sendEmail = function (obj, msg) {
return new Promise( (res, rej) => {
let mailOptions = {
from: 'test#test.com',
to: `${obj.to}`,
subject: `${obj.subject}`,
text: "plain text",
html: "<b>" + msg + "</b>"
};
mailTransporter.sendMail(mailOptions, (error, info) => {
mailTransporter.close();
if (error) {
rej(error);
}
console.log('Message sent: %s', info.messageId);
res(info.messageId);
});
})
}
module.exports = { sendEmail };
And finally I want to use it here in projects/emails.js
const { sendEmail } = require("../utils/mailTemplate");
const { vendorNotificationMessage } = require("../utils/emailMessages");
async function notifyVendors(steps) {
try {
for(let step of steps) {
if(step.vendor) {
const message = vendorNotificationMessage(step);
step.to = step.vendor.email;
step.subject = "Step cancelling notification!";
await sendEmail(step, message);
}
}
} catch(err) {
console.log(err);
console.log("Error in notifyVendors");
}
}
module.exports = { notifyVendors };
I expect that email will be sent using that sendEmail function. But it stops with the error TypeError: sendEmail is not a function.
The correct syntax for exporting something from a module is
exports.globalObjectName = localObjectName
So in your first file the export statement should look like this
exports.mailTransporter = mailTransporter
I do not think that you need {} when you use module.exports and require. Try module.exports = sendEmail ; and const sendEmail = require("../utils/mailTemplate");
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.
I'm learning double tests with a simple example:
const Database = require('./Database')
const setupNewUser = (info, callback) => {
const user = {
name: info.name,
nameLowercase: info.name.toLowerCase()
}
try {
Database.save(user, callback)
} catch (err) {
callback(err)
}
}
module.exports = setupNewUser
I have a function that takes an object and a callback:
const Database = {
save: (user, callback) => {
callback(user)
}
}
module.exports = Database
How can test that save is called with both an object and a callback. Below is what I'm trying:
it('it calls Database.save with a callback', () => {
const saveSpy = sinon.spy(Database, 'save')
const arg = {
name: info.name,
nameLowercase: info.name.toLowerCase()
}
setupNewUser(info, function() {})
//I'm able to assert that save is called with arg sinon.assert.calledWith(saveSpy, arg)
sinon.assert.calledWith(saveSpy, arg, function() {}) //This is failing
})
You should .stub your API calls vs .spy. I would also recommend using sinon.sandbox so your test cleanup is easy to manage. Read about it here
describe('Setup new user', function() {
const sandbox = sinon.createSandbox();
afterEach(function() {
sandbox.restore();
});
it('should call Database.save with a callback', function(){
const databaseSaveStub = sandbox.stub(Database, 'save');
const user = {
name: 'Some Name',
nameLowercase: 'some name'
};
const callbackFn = sandbox.spy();
setupNewUser(user, callbackFn);
sinon.assert.calledOnce(Database.save);
sinon.assert.calledWith(databaseSaveStub, user, callbackFn);
});
});