Attempted to wrap undefined property findOneAndUpdate as function - javascript

I wrote a unit test for my PUT /cars/:id using Sinon.js:
unitTest.js:
const sinon = require('sinon');
const chai = require('chai');
const expect = chai.expect;
const mongoose = require('mongoose');
// Monck mongodb Models
require('sinon-mongoose');
const Car = require('../../models/Car');
describe('PUT /cars/:id', () => {
it('Should update a car successfully', (done) => {
const updateBody = {
name: 'new car name',
}
const CarMock = sinon.mock(new Car(updateBody));
const car = CarMock.object;
const expectedResult = {
statusCode: 200
}
const id = mongoose.Types.ObjectId();
CarMock.expects('findOneAndUpdate').withArgs(id).yields(null, expectedResult);
car.findOneAndUpdate(id, updateBody, (error, res) => {
if (error) done(error)
CarMock.verify();
CarMock.restore();
expect(res.statusCode).to.equal(200);
done();
})
})
})
when I run the test I got this error:
PUT /cars/:id
Should update a car successfully:
TypeError: Attempted to wrap undefined property findOneAndUpdate as function
at wrapMethod (node_modules\sinon\lib\sinon\util\core\wrap-method.js:72:21)
at Object.expects (node_modules\sinon\lib\sinon\mock.js:71:13)
at Object.mock.expects (node_modules\sinon-mongoose\dist\index.js:49:37)
at Context.it (server\test\cars\unitTests.js:106:29)
What's the mistake I hade made when writing this test?

As a solution to pass the test I update the code to be:
const sinon = require('sinon');
const chai = require('chai');
const expect = chai.expect;
const mongoose = require('mongoose');
// Monck mongodb Models
require('sinon-mongoose');
const Car = require('../../models/Car');
describe('PUT /cars/:id', () => {
it('Should update a car successfully', (done) => {
const updateBody = {
name: 'new car name',
}
const CarMock = sinon.mock(Car);
const car = CarMock.object;
const expectedResult = {
statusCode: 200
}
const id = mongoose.Types.ObjectId();
CarMock.expects('findOneAndUpdate').withArgs({_id: id}, updateBody).yields(null, expectedResult);
car.findOneAndUpdate(id, updateBody, (error, res) => {
if (error) done(error)
CarMock.verify();
CarMock.restore();
expect(res.statusCode).to.equal(200);
done();
})
})
})

Related

Stub out node-vault import

How can the 'node-vault' module be stubbed out using Sinon.JS?
Here is the sample code to be tested:
const NodeVault = require('node-vault');
class BearerAuth {
getVaultConfig (token) {
return {
apiVersion: 'v1',
endpoint: app.get('vault'),
token: token
};
}
verify (token) {
const vault = NodeVault(this.getVaultConfig(token));
const resp = vault.tokenLookupSelf().then((result) => {
console.log(`Vault token: ${token}, successfully authenticated.`);
return Promise.resolve({
'name': result.data.meta.username
});
}).catch((err) => {
console.error(err.message);
return Promise.reject(new this.ApplicationError.Authentication('Bearer token is incorrect.'));
});
return resp;
}
}
module.exports = BearerAuth;
}
The test code which attempts to stub out the module using sinon.stub:
const assert = require('assert');
const sinon = require('sinon');
const nodeVault = require('node-vault');
const BearerAuth = require('bearer');
describe('Bearer AuthConfig', () => {
beforeEach(async () => {
class TestBearerAuth extends BearerAuth {
// override some other methods
}
testAuth = new TestBearerAuth();
const vaultConfig = {
endpoint: 'http://localhost:8200',
token: '123'
};
testAuth.getVaultConfig = sinon.stub.returns(vaultConfig);
});
it('returns user when valid token', async () => {
const user = await testAuth.verify(null, 'mytoken');
assert.deepEqual(user, {name: 'sre'});
})
}
This fails when run with this error:
1) Bearer Auth
returns user when valid token:
TypeError: Attempted to wrap undefined property undefined as function
at wrapMethod (node_modules/sinon/lib/sinon/util/core/wrap-method.js:70:21)
at TestBearerAuth.stub [as getVaultConfig] (node_modules/sinon/lib/sinon/stub.js:65:44)
at TestBearerAuthConfig.verify (src/modules/auth/bearer.js:34:34)
at Context.<anonymous> (test/unit/modules/auth/test-bearer.js:57:39)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
The node-vault library has the following export in index.d.ts:
declare function NodeVault(options?: NodeVault.VaultOptions): NodeVault.client;
export = NodeVault;
The unit test code from the node-vault library provided the solution:
Test code:
const assert = require('assert');
const sinon = require('sinon');
const nodeVault = require('node-vault');
const BearerAuth = require('bearer');
describe('Bearer AuthConfig', () => {
beforeEach(async () => {
request = sinon.stub();
response = sinon.stub();
response.statusCode = 200;
response.body = {
data: {
meta: {
username: 'jack'
}
}
};
request.returns({
then (fn) {
return fn(response);
},
catch (fn) {
return fn();
}
});
class TestBearerAuth extends BearerAuth {
getVaultConfig (token) {
return {
endpoint: 'http://localhost:8200',
token: '123',
namespace: 'test',
'request-promise': {
defaults: () => request // dependency injection of stub
}
};
}
}
testAuth = new TestBearerAuth();
});
it('returns user when valid token', async () => {
const user = await testAuth.verify(null, 'mytoken');
assert.deepEqual(user, {name: 'sre'});
})
}
Adding this here in case someone else runs into a similar issue.

How to set up Jest with a setup file to run beforeEach across multiple test files

I have two Models which I am testing in individual files and in each file I am using beforeEach to clear the DB and seed the DB. I am also using afterEach to drop the DB and close the connection to MongoDB.
I am getting a MongoDB error for a duplicate key of my users so it seems that the beforeEach isn't running perfectly beforeEach test. If I run the files isolated then no issues. It is only when I run the whole test suite.
user.test.js
const mongoose = require('mongoose');
const request = require('supertest');
const app = require('../app');
const User = require('../models/user.model');
const userData = require('../db/data/userData');
// Valid User for Loggin In
// {
// username: 'Bcrypt User',
// password: 'YECm9jCO8b',
// };
beforeEach((done) => {
mongoose.connect(process.env.DB_URI, () => {
const seedDB = async () => {
await User.deleteMany({});
await User.insertMany(userData);
};
seedDB().then(() => {
done();
});
});
});
afterEach((done) => {
mongoose.connection.db.dropDatabase(() => {
mongoose.connection.close(() => done());
});
});
message.test.js
const mongoose = require('mongoose');
const request = require('supertest');
const app = require('../app');
const User = require('../models/user.model');
const userData = require('../db/data/userData');
const messageData = require('../db/data/messageData');
const Message = require('../models/message.model');
const validUserAccount = {
username: 'Bcrypt User',
password: 'YECm9jCO8b',
};
beforeEach((done) => {
mongoose.connect(process.env.DB_URI, () => {
const seedDB = async () => {
await User.deleteMany({});
await User.insertMany(userData);
await Message.deleteMany({});
await Message.insertMany(messageData);
};
seedDB().then(() => {
done();
});
});
});
afterEach((done) => {
mongoose.connection.db.dropDatabase(() => {
mongoose.connection.close(() => done());
});
});
So far I have tried using a jest.config.js and also tried add config to package.json but can't seem to figure out a global file to run before each
Have you checked setupFilesAfterEnv option https://jestjs.io/docs/configuration#setupfilesafterenv-array?

How can i write a mocha/chai test for a database connection along with queries?

I'm trying to test the dbMysqlConnect function in the file I that's being tested, but I'm having trouble actually testing the function for I always get an error when trying to establish a connection. How would you go about writing a test for a situation like this? Any help would be appreciated.
Error in console window:
Error: connect ECONNREFUSED 127.0.0.1:3306
Test File:
const mysql = require('mysql');
const mssql = require('mssql');
const actions = require('../src/actions');
const knowledgePortalAPI = require ('../src/api/knowledgePortalAPI');
const rewire = require('rewire');
const { expect } = require('chai');
const MockAdapter = require('axios-mock-adapter');
const Store = require('./store');
//import the updatedState and Event Variables from the store.
const newStore = new Store();
let updatedState = newStore.getUpdatedState();
let event = newStore.getEvent();
let initialState = newStore.getInitialState();
const _ = require('lodash');
describe('testing actions.js file', () => {
it('testing dbMysqlConnect function', async () => {
updatedState = _.cloneDeep(initialState);
const router = rewire('../src/actions');
const dbMysqlConnect = router.__get__('dbMysqlConnect');
let memoObject = await dbMysqlConnect(updatedState, event);
});
});
actions.js:
const mysql = require('mysql');
const mssql = require('mssql');
const axios = require('axios');
const AD = require('ad');
const bp = require('botpress');
const configMS = require('./configMS');
const configMY = require('./configMY');
const Querystring = require('querystring');
const flatten = require('flat');
const moment = require('moment');
const { viewClaimSummary, viewClaimLineDetail, viewClaimSummaryMulti, providerDemographics, claimConsolidated, memoService } = require('./api');
const realTimeFlowDialogEngine = require('./RealTimeFlowDialogEngine/documentDialogEngine');
const claimsDocumentAutoRoutingEngine = require('./RealTimeFlowDialogEngine/claimsSOPDocumentRouter');
const messageStressTest = require('./RealTimeFlowDialogEngine/messageStressTest');
/**
* Description of the action goes here
* #param {String} params.name=value Description of the parameter goes here
* #param {Number} [params.age] Optional parameter
*/
/**
* Demo call to MySQL DB
*/
async function dbMysqlConnect(state, event, params) {
var con = mysql.createConnection({
host: configMY.host,
user: configMY.user,
password: configMY.password,
database: configMY.database
})
con.connect(function (err) {
if (err) {
throw err;
}
con.query('select * from users', function (err, result, fields) {
if (err) {
throw err;
}
//console.log(result[0]);
event.reply('#builtin_text', {
text: `User name is : ${result[0].First_name}`,
typing: true
})
})
})
}
Since you're using rewire, you should be able to replace mysql with a mock implementation. I'd recommend sinon as a helpful tool for authoring that mock, though you don't have to do so.
const { stub } = require('sinon');
describe('testing actions.js file', () => {
it('testing dbMysqlConnect function', async () => {
updatedState = _.cloneDeep(initialState);
const router = rewire('../src/actions');
const mockUsers = [];
const mockConnection = {
connect: stub().yields(),
query: stub().yields(null, mockUsers)
};
const mockMySql = {
createConnection: stub().returns(mockConnection);
};
router.__set__('mysql', mockMySql);
const dbMysqlConnect = router.__get__('dbMysqlConnect');
let memoObject = await dbMysqlConnect(updatedState, event);
});
});

Stub not being called

I have a unit test for one of my controllers, which has a single function looking up data (sequelize) and adding the results as json to the response object.
I have two stubs, one of which is called the other is not.
Controller
exports.findAll = (req, res) => {
Idea.findAll().then(ideas => {
console.log(ideas);
return res.json(ideas);
}).catch(err => {
console.log(err);
});
};
Controller test
const chai = require('chai');
const {match, stub, resetHistory, spy} = require('sinon');
const proxyquire = require('proxyquire');
var sinonChai = require("sinon-chai");
chai.should();
chai.use(sinonChai);
const {makeMockModels} = require('sequelize-test-helpers');
describe('Idea Controller', function () {
const data = {
id: 1,
title: 'Stubbed Title',
text: 'Stubbed text'
};
describe('findAll()', function () {
it('Success case ', function () {
const mockResponse = () => {
const res = {};
res.json = stub().returns(res);
return res;
};
let res = mockResponse();
const Idea = {findAll: stub()};
const mockModels = makeMockModels({Idea});
Idea.findAll.resolves(data);
const ideaController = proxyquire('../../src/controllers/IdeaController', {
'../models': mockModels
});
ideaController.findAll({}, res);
Idea.findAll.should.have.been.called; // passes
res.json.should.have.been.called; //fails
});
})
});
Since findAll is async function you should move assertion inside .then.

Firebase function for dialogflow unittest not returning

I have the following firebase cloud function that does work as written.
'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();
const config = require('config');
// 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(config.get('dialogflow.intents.welcome_user'), async (conv) => {
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)
});
// check for existing bank, if not present, create it
var bankRef = db.collection(config.get('firestore.bank_collection_key')).doc(userInfo.email);
const bankSnapshot = await bankRef.get()
if (bankSnapshot.exists) {
conv.ask("Welcome back to Family Bank. I just opened the bank I found for " + userInfo.name + ". How can I help you?");
} else {
// create new account
conv.ask("Welcome to Family Bank. I just created a new bank for " + userInfo.name + ". I can help you create and manage accounts. What would you like to do?");
}
})
exports.accessAccount = functions.https.onRequest(app);
I also wrote the following unittest that does successfully stub Auth0, Firestore, etc., but it fails to get a return value. Here's the test
const chai = require('chai');
const assert = chai.assert;
const sinon = require('sinon');
const admin = require('firebase-admin');
const test = require('firebase-functions-test')();
const config = require('config');
var UsersManager = require('auth0/src/auth/UsersManager');
describe('Cloud Functions', () => {
let myFunctions, adminInitStub, firestoreStub, collectionStub;
before(() => {
test.mockConfig({"familybank": {"auth0": {"domain": "mockdomain", "clientid": "mockid"}}});
adminInitStub = sinon.stub(admin, 'initializeApp');
oldFirestore = admin.firestore;
firestoreStub = sinon.stub();
Object.defineProperty(admin, 'firestore', { get: () => firestoreStub });
collectionStub = sinon.stub();
firestoreStub.returns({ collection: collectionStub });
sinon.stub(UsersManager.prototype, 'getInfo').callsFake( function fakeGetProfile() {
return Promise.resolve({"email": "someuser#domain.com", "name": "Joe Banks"});
});
myFunctions = require('../index');
});
after(() => {
adminInitStub.restore();
admin.firestore = oldFirestore;
test.cleanup();
});
describe('accessAccount', () => {
it('should return a 200', (done) => {
const bankOwnerParam = 'someuser#domain.com';
const bankStub = sinon.stub();
collectionStub.withArgs(config.get('firestore.bank_collection_key')).returns({ doc: bankStub });
bankStub.withArgs(bankOwnerParam).returns({ get: () => Promise.resolve({ mykey: 'mydata', exists: true })});
const req = {"responseId":"RESPONSEID","queryResult":{"queryText":"GOOGLE_ASSISTANT_WELCOME","action":"input.welcome","parameters":{},"allRequiredParamsPresent":true,"fulfillmentMessages":[{"text":{"text":[""]}}],"outputContexts":[{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/google_assistant_welcome"},{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/actions_capability_audio_output"},{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/google_assistant_input_type_voice"},{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/actions_capability_media_response_audio"}],"intent":{"name":"projects/familybank/agent/intents/65b6c584-be5d-456b-ad77-341abdb4dcb4","displayName":"Default Welcome Intent"},"intentDetectionConfidence":1.0,"languageCode":"en-us"},"originalDetectIntentRequest":{"source":"google","version":"2","payload":{"isInSandbox":true,"surface":{"capabilities":[{"name":"actions.capability.AUDIO_OUTPUT"},{"name":"actions.capability.MEDIA_RESPONSE_AUDIO"}]},"requestType":"SIMULATOR","inputs":[{"rawInputs":[{"query":"Talk to Family Bank","inputType":"VOICE"}],"intent":"actions.intent.MAIN"}],"user":{"lastSeen":"2018-11-15T14:41:36Z","accessToken":"TOKEN","locale":"en-US","userId":"USERID"},"conversation":{"conversationId":"SESSIONID","type":"NEW"},"availableSurfaces":[{"capabilities":[{"name":"actions.capability.AUDIO_OUTPUT"},{"name":"actions.capability.SCREEN_OUTPUT"},{"name":"actions.capability.WEB_BROWSER"}]}]}},"session":"projects/familybank/agent/sessions/SESSIONID"};
const res = {
response: (code) => {
assert.equal(code, 200);
done();
}
};
myFunctions.accessAccount(req, res);
});
});
})
When I run the test, I get the following failure
root#fc19e6bca144:/appfiles/functions# npm test
> familybank#0.0.1 test /appfiles/functions
> mocha --reporter spec
Cloud Functions
accessAccount
1) should return a 200
0 passing (3s)
1 failing
1) Cloud Functions
accessAccount
should return a 200:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/appfiles/functions/test/index.test.js)
npm ERR! Test failed. See above for more details.
I can't figure out why this doesn't return a value. I followed https://firebase.google.com/docs/functions/unit-testing#testing_http_functions, and followed the links to some related files.
Thanks to the hint in the comment above, I was able to get a working solution with the following change
it('should return a 200', async () => {
const bankOwnerParam = 'someuser#domain.com';
const bankStub = sinon.stub();
collectionStub.withArgs(config.get('firestore.bank_collection_key')).returns({ doc: bankStub });
bankStub.withArgs(bankOwnerParam).returns({ get: () => Promise.resolve({ mykey: 'mydata', exists: true })});
const req = {"responseId":"RESPONSEID","queryResult":{"queryText":"GOOGLE_ASSISTANT_WELCOME","action":"input.welcome","parameters":{},"allRequiredParamsPresent":true,"fulfillmentMessages":[{"text":{"text":[""]}}],"outputContexts":[{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/google_assistant_welcome"},{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/actions_capability_audio_output"},{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/google_assistant_input_type_voice"},{"name":"projects/familybank/agent/sessions/SESSIONID/contexts/actions_capability_media_response_audio"}],"intent":{"name":"projects/familybank/agent/intents/65b6c584-be5d-456b-ad77-341abdb4dcb4","displayName":"Default Welcome Intent"},"intentDetectionConfidence":1.0,"languageCode":"en-us"},"originalDetectIntentRequest":{"source":"google","version":"2","payload":{"isInSandbox":true,"surface":{"capabilities":[{"name":"actions.capability.AUDIO_OUTPUT"},{"name":"actions.capability.MEDIA_RESPONSE_AUDIO"}]},"requestType":"SIMULATOR","inputs":[{"rawInputs":[{"query":"Talk to Family Bank","inputType":"VOICE"}],"intent":"actions.intent.MAIN"}],"user":{"lastSeen":"2018-11-15T14:41:36Z","accessToken":"TOKEN","locale":"en-US","userId":"USERID"},"conversation":{"conversationId":"SESSIONID","type":"NEW"},"availableSurfaces":[{"capabilities":[{"name":"actions.capability.AUDIO_OUTPUT"},{"name":"actions.capability.SCREEN_OUTPUT"},{"name":"actions.capability.WEB_BROWSER"}]}]}},"session":"projects/familybank/agent/sessions/SESSIONID"};
const res = {
response: (code) => {
assert.equal(code, 200);
}
};
await myFunctions.accessAccount(req, res);
});

Categories

Resources