Mock internal dependency - node.js

I have a request that has an internal dependency to a Facebook graph objects that performs another request against the FB graph API.
I'm wondering if it is possible to use sinon to mock the graph object so that it wouldn't actually perform a request in a test but would execute the callback function with a value that I provide in the test instead.
server.post("/facebookLogin", function(req, res) {
graph.setAccessToken(req.body.fbtoken);
graph.get("me?fields=email", function(err, obj) {
if (!err) {
var email = obj.email;
checkUserAlreadyRegistered(email, function(user) {
if (user) {
return res.send(200, {user:user, token: decorateToken(user.id)});
} else {
return res.send(404);
}
});
} else {
return res.send(500);
}
});
});

I had the exact same issue, and digging into the fbgraph source code I found out that even though it's using "graphql", internally is a network request with request so you can easily intercept it with nock:
// https://github.com/criso/fbgraph/blob/master/lib/graph.js#L34 <-- fb graph url
const fbMock = nock('https://graph.facebook.com/v4.0/')
.get('/me')
.query(true)
.reply(200, {
id: '123123',
name: 'fb username',
email: 'user#fb.com'
})
it('should not call fb"', (done) => {
chai.request(server)
.post('/facebookLogin')
.send({ fbtoken: 'token_fb' })
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(200)
expect(fbMock).to.have.been.requested
done()
})
}
note: the /v4.0/ part could be different depending on your configuration but the default value is 2.9 so be sure to use the same one you set with the setVersion method

Related

Mocking in jest and express

I have some code in an Express route which talks to AWS Cognito and am having trouble working out how to mock it in tests.
cognitoExpress.validate(accessTokenFromClient, (err, response) => {
if (err) return res.status(401).json({ error: err });
res.json({ data: `Hello ${response.username}!` });
});
Then in my test I want to say cognitoExpress.validate should be called once and return {username: 'test user'} so that it doesnt hit the network and doesnt actually call AWS Cognito
it('It should returns 200 with a valid token', async done => {
const { cognitoExpress } = require('../helpers/cognitoExpress');
// I have tried
jest.mock('../helpers/cognitoExpress');
// and this
jest.mock('../helpers/cognitoExpress', () => ({
validate: jest.fn()
}));
const token = 'sfsfdsfsdfsd';
const response = await request.get('/').set('Authorization', token);
expect(cognitoExpress.validate).toHaveBeenCalledWith(token);
expect(response.body).toEqual({ data: 'Hello test user' });
done();
});
Thanks in advance....
let spyInstance = undefined;
beforeAll(() => {
spyInstance = jest.spyOn(cognitoExpress.prototype, "validate").mockImplementation(() => {
// Replace the body of 'validate' here, ensure it sets
// response body to {username: 'test user'} without calling AWS
...
});
});
afterAll(() => {
expect(spyInstance).toBeDefined();
expect(spyInstance).toHaveBeenCalledTimes(1);
jest.restoreAllMocks();
});
it("It should call mocked cognitoExpress.validate once", async done => {
...
});
A similar and working test in my project. Instead of cognitoExpress.validate it mocks and tests SampleModel.getData
Create file ../helpers/__mocks__/cognitoExpress.js with mocked function you want to use. It is essential to call the folder __mocks__. You can modify a functions and return any data you want.
example
module.exports = {
validate: () => { username: 'test user' }
}
Now you can use jest.mock('../helpers/cognitoExpress'), but I recommend you to place it to some global or test setup file, not to separate tests.
Jest Manual Mocks

Angular 7 HttpClient: ToPromise does not work with put request

I'm trying to create an HTTP put request API using express that calls a function upon competition. I successfully got the API calls to update the database but the functions within .then() and .catch() are not being called. Does toPromise() not work for put requests?
On the frontend, I tried adding types to the function and defining it as asynchronous. On the backend, I tried restructuring the .then() and catch() statements. Nothing seems to work.
app.component.ts
public async save(): Promise<any> {
return this.httpClient.put<any>('/api/update-account', {
name: this.name,
email: this.email
}).toPromise()
.then(() => {
// Run if successful put request
success()
})
.catch(() => {
// Run if error
fallback()
})
}
server.js
app.put('/api/update-account', (req, res) => {
let name = req.body.name;
let email = req.body.email;
sql.connect(dbConfig).then(() => {
return sql.query`UPDATE users SET Name=${name} WHERE Email=${email}`
.then(result => {
console.log(result);
sql.close();
}).catch(err => {
console.log(err);
sql.close();
})
}).catch(err => {
console.log(err);
sql.close();
})
});
I expect success() or fallback() to run after the API request, but neither are being called.
You need to return a response using express. For example:
return res.status(200).send('My value');
Because you don't return a response anywhere, nothing is returned to the client.

Sinon stub.resolves() not acting as desired

Here's the function I'm writing tests for:
ensureUserDoesNotExist(request, response, next) {
this.User.findOne({ where: { email: request.body.email }})
.then(user => {
if (user) {
response.sendStatus(403);
} else {
next();
}
});
}
And here's the test that I cannot get to pass:
it('should return a 403 if a matching user is found', () => {
mockRequest.body.email = 'test#email.com';
userController.User.findOne.resolves(true); // This is a previously created sinon stub
userController.ensureUserDoesNotExist(mockRequest, mockResponse, mockNext);
assert(mockResponse.sendStatus.calledWith(403));
});
It fails, simply claiming that the stub isn't called (at all, for what it's worth).
I strongly suspect this is to do with the promise - or Sinon's interaction with it - but am having a complete mind-blank in trying to figure out exactly what. The code works as intended (or it did when I last looked before playing about with it). Can anyone help me out?
Your assertion is evaluated before the end request
You need to return the promise
ensureUserDoesNotExist(request, response, next) {
return this.User.findOne({ where: { email: request.body.email }})
.then(user => {
if (user) {
response.sendStatus(403);
} else {
next();
}
});
}
and assert in then clause
it('should return a 403 if a matching user is found', () => {
mockRequest.body.email = 'test#email.com';
userController.User.findOne.resolves(true); // This is a previously created sinon stub
userController.ensureUserDoesNotExist(mockRequest, mockResponse, mockNext).then(() => {
assert(mockResponse.sendStatus.calledWith(403));
});
});
The test also must return a promise to indicate an asynchronous test to Mocha. You can use the one returned by the then call:
it('should return a 403 if a matching user is found', () => {
mockRequest.body.email = 'test#email.com';
userController.User.findOne.resolves(true); // This is a previously created sinon stub
return userController.ensureUserDoesNotExist(mockRequest, mockResponse, mockNext).then(() => {
assert(mockResponse.sendStatus.calledWith(403));
});
});

How to get result of validateFunc in pre of auto created API Rest Hapi

I am new to Hapi.js.I am using "hapi-auth-jwt2" module for authentication token and role verification. I set the scope and sent that scope from the callback of validateFunc . It will worked very well for checking te role based authentication. But i want the result i am returning from the validateFunc but don't know where i can get that.
validateFunc: function (token, request, callback) {
Async.auto({
session: function (done) {
Session.findByCredentials(token.sessionId, token.sessionKey, done);
},
user: ['session', function (results, done) {
if (!results.session) {
return done();
}
User.findById(results.session.user, done);
}],
}, (err, results) => {
if (err) {
return callback(err);
}
if (!results.session) {
return callback(null, false);
}
results.scope = token.scope;
callback(null, Boolean(results.user), results);
});
}
});
};
`
It verify the scope or Role in the domain i.e:-
routeOptions: {
scope:{
createScope:"admin"
},
create: {
pre : function(payload, Log){
console.log("preee runnnig........");
console.log(payload);
}
}
I am getting the payload Json what i am sending from the client side but i want the results i am sending from the callback of validateFunc, because i want to use that data here in pre prior to send the request.I am working on implicitly created API via Rest Hapi Module.
So how can i get that datain pre hooks from the validateFunc . Any help is much appreciated.
Thanks
This is actually a feature that is being worked on and hopefully will be done soon.
For now, you can omit the generated create endpoint and replace it with your own in order to access the request object.
The resulting code would look something like this:
'use strict';
const RestHapi = require('rest-hapi');
module.exports = function (server, mongoose, logger) {
server.route({
method: 'POST',
path: '/pathName',
config: {
handler: function(request, reply) {
/* modify request.payload here and access auth info through request.auth.credentials */
const Model = mongoose.model('modelName');
return RestHapi.create(Model, request.payload, logger)
.then(function (result) {
return reply(result);
})
.catch(function (error) {
return reply(error);
});
},
tags: ['api'],
plugins: {
'hapi-swagger': {}
}
}
});
};

How to authenticate Supertest requests with Passport?

I'm using Passport.js for authentication (local strategy) and testing with Mocha and Supertest.
How can I create a session and make authenticated requests with Supertest?
As zeMirco points out, the underlying superagent module supports sessions, automatically maintaining cookies for you. However, it is possible to use the superagent.agent() functionality from supertest, through an undocumented feature.
Simply use require('supertest').agent('url') instead of require('supertest')('url'):
var request = require('supertest');
var server = request.agent('http://localhost:3000');
describe('GET /api/getDir', function(){
it('login', loginUser());
it('uri that requires user to be logged in', function(done){
server
.get('/api/getDir')
.expect(200)
.end(function(err, res){
if (err) return done(err);
console.log(res.body);
done()
});
});
});
function loginUser() {
return function(done) {
server
.post('/login')
.send({ username: 'admin', password: 'admin' })
.expect(302)
.expect('Location', '/')
.end(onResponse);
function onResponse(err, res) {
if (err) return done(err);
return done();
}
};
};
You should use superagent for that. It is lower level module and used by supertest. Take a look at the section Persisting an agent:
var request = require('superagent');
var user1 = request.agent();
user1
.post('http://localhost:4000/signin')
.send({ user: 'hunter#hunterloftis.com', password: 'password' })
.end(function(err, res) {
// user1 will manage its own cookies
// res.redirects contains an Array of redirects
});
Now you can use user1 to make authenticated requests.
Try this,
var request=require('supertest');
var cookie;
request(app)
.post('/login')
.send({ email: "user#gluck.com", password:'password' })
.end(function(err,res){
res.should.have.status(200);
cookie = res.headers['set-cookie'];
done();
});
//
// and use the cookie on the next request
request(app)
.get('/v1/your/path')
.set('cookie', cookie)
.end(function(err,res){
res.should.have.status(200);
done();
});
As an addendum to Andy's answer, in order to have Supertest startup your server for you, you can do it like this:
var request = require('supertest');
/**
* `../server` should point to your main server bootstrap file,
* which has your express app exported. For example:
*
* var app = express();
* module.exports = app;
*/
var server = require('../server');
// Using request.agent() is the key
var agent = request.agent(server);
describe('Sessions', function() {
it('Should create a session', function(done) {
agent.post('/api/session')
.send({ username: 'user', password: 'pass' })
.end(function(err, res) {
expect(req.status).to.equal(201);
done();
});
});
it('Should return the current session', function(done) {
agent.get('/api/session').end(function(err, res) {
expect(req.status).to.equal(200);
done();
});
});
});
I'm sorry, but neither of suggested solutions doesn't work for me.
With supertest.agent() I can't use the app instance, I'm required to run the server beforehand and specify the http://127.0.0.1:port and moreover I can't use supertest's expectations (assertions), I can't use the supertest-as-promised lib and so on...
The cookies case won't work for me at all.
So, my solution is:
If you are using Passport.js, it utilizes the "Bearer token" mechanism and you can use the following examples in your specs:
var request = require('supertest');
var should = require('should');
var app = require('../server/app.js'); // your server.js file
describe('Some auth-required API', function () {
var token;
before(function (done) {
request(app)
.post('/auth/local')
.send({
email: 'test#example.com',
password: 'the secret'
})
.end(function (err, res) {
if (err) {
return done(err);
}
res.body.should.to.have.property('token');
token = res.body.token;
done();
});
});
it('should respond with status code 200 and so on...', function (done) {
request(app)
.get('/api/v2/blah-blah')
.set('authorization', 'Bearer ' + token) // 1) using the authorization header
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) {
return done(err);
}
// some `res.body` assertions...
done();
});
});
it('should respond with status code 200 and so on...', function (done) {
request(app)
.get('/api/v2/blah-blah')
.query({access_token: token}) // 2) using the query string
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) {
return done(err);
}
// some `res.body` assertions...
done();
});
});
});
You may want to have a helper function to authenticate users:
test/auth-helper.js
'use strict';
var request = require('supertest');
var app = require('app.js');
/**
* Authenticate a test user.
*
* #param {User} user
* #param {function(err:Error, token:String)} callback
*/
exports.authenticate = function (user, callback) {
request(app)
.post('/auth/local')
.send({
email: user.email,
password: user.password
})
.end(function (err, res) {
if (err) {
return callback(err);
}
callback(null, res.body.token);
});
};
Have a productive day!
I'm going to assume that you're using the CookieSession middleware.
As grub mentioned, your goal is to get a cookie value to pass to your request. However, for whatever reason (at least in my testing), supertest won't fire 2 requests in the same test. So, we have to reverse engineer how to get the right cookie value. First, you'll need to require the modules for constructing your cookie:
var Cookie = require("express/node_modules/connect/lib/middleware/session/cookie")
, cookieSignature = require("express/node_modules/cookie-signature")
Yes, that's ugly. I put those at the top of my test file.
Next, we need to construct the cookie value. I put this into a beforeEach for the tests that would require an authenticated user:
var cookie = new Cookie()
, session = {
passport: {
user: Test.user.id
}
}
var val = "j:" + JSON.stringify(session)
val = 's:' + cookieSignature.sign(val, App.config.cookieSecret)
Test.cookie = cookie.serialize("session",val)
Test.user.id was previously defined in the portion of my beforeEach chain that defined the user I was going to "login". The structure of session is how Passport (at least currently) inserts the current user information into your session.
The var val lines with "j:" and "s:" are ripped out of the Connect CookieSession middleware that Passport will fallback on if you're using cookie-based sessions. Lastly, we serialize the cookie. I put "session" in there, because that's how I configured my cookie session middleware. Also, App.config.cookieSecret is defined elsewhere, and it must be the secret that you pass to your Express/Connect CookieSession middleware. I stash it into Test.cookie so that I can access it later.
Now, in the actual test, you need to use that cookie. For example, I have the following test:
it("should logout a user", function(done) {
r = request(App.app)
.del(App.Test.versionedPath("/logout"))
.set("cookie", Test.cookie)
// ... other sets and expectations and your .end
}
Notice the call to set with "cookie" and Test.cookie. That will cause the request to use the cookie we constructed.
And now you've faked your app into thinking that user is logged in, and you don't have to keep an actual server running.
Here is a neat approach which has the added benefit of being reusable.
const chai = require("chai")
const chaiHttp = require("chai-http")
const request = require("supertest")
const app = require("../api/app.js")
const should = chai.should()
chai.use(chaiHttp)
describe("a mocha test for an expressjs mongoose setup", () => {
// A reusable function to wrap your tests requiring auth.
const signUpThenLogIn = (credentials, testCallBack) => {
// Signs up...
chai
.request(app)
.post("/auth/wizard/signup")
.send({
name: "Wizard",
...credentials,
})
.set("Content-Type", "application/json")
.set("Accept", "application/json")
.end((err, res) => {
// ...then Logs in...
chai
.request(app)
.post("/auth/wizard/login")
.send(credentials)
.set("Content-Type", "application/json")
.set("Accept", "application/json")
.end((err, res) => {
should.not.exist(err)
res.should.have.status(200)
res.body.token.should.include("Bearer ")
// ...then passes the token back into the test
// callBack function.
testCallBack(res.body.token)
})
})
}
it.only("flipping works", done => {
// "Wrap" our test in the signUpThenLogIn function.
signUpLogIn(
// The credential parameter.
{
username: "wizard",
password: "youSHALLpass",
},
// The test wrapped in a callback function which expects
/// the token passed back from when signUpLogIn is done.
token => {
// Now we can use this token to run a test...
/// e.g. create an apprentice.
chai
.request(app)
.post("/apprentice")
.send({ name: "Apprentice 20, innit" })
// Using the token to auth!
.set("Authorization", token)
.end((err, res) => {
should.not.exist(err)
res.should.have.status(201)
// Yep. apprentice created using the token.
res.body.name.should.be.equal("Apprentice 20, innit")
done()
})
}
)
})
})
BONUS MATERIAL
To make it even more reusable, put the function into a file called "myMochaSuite.js" which you can replace "describe" with when testing your api server. Be a wizard and put all your before/after stuff in this "suite". e.g.:
// tests/myMochaSuite.js
module.exports = (testDescription, testsCallBack) => {
describe(testDescription, () => {
const signUpThenLogIn = (credentials, testCallBack) => {
// The signUpThenLogIn function from above
}
before(async () => {
//before stuff like setting up the app and mongoose server.
})
beforeEach(async () => {
//beforeEach stuff clearing out the db
})
after(async () => {
//after stuff like shutting down the app and mongoose server.
})
// IMPORTANT: We pass signUpLogIn back through "testsCallBack" function.
testsCallBack(signUpThenLogIn)
})
}
// tests/my.api.test.js
// chai, supertest, etc, imports +
const myMochaSuite = require("./myMochaSuite")
// NB: signUpThenLogIn coming back into the tests.
myMochaSuite("my test description", signUpThenLogIn => {
it("just works baby", done => {
signUpThenLogIn(
{username: "wizard", password: "youSHALLpass"},
token => {
chai
.request(app)
.get("/apprentices/20")
// Using the incoming token passed when signUpThenLogIn callsback.
.set("Authorization", token)
.end((err, res) => {
res.body.name.equals("Apprentice 20, innit")
done()
})
}
)
})
})
Now you have a even more reusable suite "wrapper" for all your tests, leaving them uncluttered.
GraphQl full Example:
const adminLogin = async (agent) => {
const userAdmin = await User.findOne({rol:"admin"}).exec();
if(!userAdmin) return new Promise.reject('Admin not found')
return agent.post('/graphql').send({
query: ` mutation { ${loginQuery(userAdmin.email)} }`
})//.end((err, {body:{data}}) => {})
}
test("Login Admin", async (done) => {
const agent = request.agent(app);
await adminLogin(agent);
agent
.post("/graphql")
.send({query: `{ getGuests { ${GuestInput.join(' ')} } }`})
.set("Accept", "application/json")
.expect("Content-Type", /json/)
.expect(200)
.end((err, {body:{data}}) => {
if (err) return done(err);
expect(data).toBeInstanceOf(Object);
const {getGuests} = data;
expect(getGuests).toBeInstanceOf(Array);
getGuests.map(user => GuestInput.map(checkFields(user)))
done();
});
})

Resources