我正在使用Passport.js进行身份验证(本地策略)并使用Mocha和Supertest进行测试。
如何使用Supertest创建会话并进行经过身份验证的请求?
答案 0 :(得分:51)
正如zeMirco所指出的,底层的superagent
模块支持会话,为您自动维护cookie。但是,可以通过未记录的功能使用superagent.agent()
中的supertest
功能。
只需使用require('supertest').agent('url')
代替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();
}
};
};
答案 1 :(得分:44)
您应该使用superagent。它是较低级别的模块,由supertest
使用。请查看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
});
现在,您可以使用user1
进行经过身份验证的请求。
答案 2 :(得分:26)
试试这个,
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();
});
答案 3 :(得分:9)
作为Andy回答的附录,为了让Supertest为您启动服务器,您可以这样做:
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();
});
});
});
答案 4 :(得分:5)
对不起,但建议的解决方案都不适用于我。
使用supertest.agent()
我无法使用app
实例,我需要预先运行服务器并指定http://127.0.0.1:port
,而且我不能使用supertest的期望(断言) ),我不能使用supertest-as-promised
lib等等......
cookies
案件根本不适用于我。
所以,我的解决方案是:
如果您使用 Passport.js ,它会使用“Bearer token”机制,您可以在规范中使用以下示例:
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();
});
});
});
您可能希望使用辅助函数来验证用户身份:
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);
});
};
过得愉快!
答案 5 :(得分:3)
我将假设你正在使用CookieSession中间件。
正如grub所说,您的目标是获取cookie值以传递给您的请求。但是,无论出于何种原因(至少在我的测试中),supertest不会在同一测试中触发2个请求。因此,我们必须对如何获得正确的cookie值进行逆向工程。首先,您需要使用模块来构建cookie:
var Cookie = require("express/node_modules/connect/lib/middleware/session/cookie")
, cookieSignature = require("express/node_modules/cookie-signature")
是的,那很难看。我将它们放在测试文件的顶部。
接下来,我们需要构造cookie值。我把它放到beforeEach
进行需要经过身份验证的用户的测试:
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
之前已定义在我的beforeEach
链中定义了我要“登录”的用户的部分。 session
的结构是Passport(至少当前)将当前用户信息插入会话的方式。
如果您正在使用基于Cookie的会话,那么带有var val
和"j:"
的{{1}}行会从Passport将退回的Connect CookieSession中间件中删除。最后,我们序列化cookie。我把"s:"
放在那里,因为这就是我配置cookie会话中间件的方式。此外,"session"
在其他地方定义,它必须是您传递给Express / Connect CookieSession中间件的秘密。我将其存入App.config.cookieSecret
,以便我以后可以访问它。
现在,在实际测试中,您需要使用该cookie。例如,我有以下测试:
Test.cookie
请注意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
}
和set
与"cookie"
的通话。这将导致请求使用我们构建的cookie。
现在你已经伪造了你的应用程序,认为用户已登录,而且你不必让实际的服务器保持运行。
答案 6 :(得分:0)
这是一种整洁的方法,具有可重用的额外好处。
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()
})
}
)
})
})
奖励材料
要使其更具可重用性,请将函数放入名为“ myMochaSuite.js”的文件中,您可以在测试api服务器时将其替换为“ describe”。成为向导,将所有之前/之后的内容放入此“套件”中。例如:
// 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()
})
}
)
})
})
现在,您为所有测试提供了一个更具可重用性的套件“包装器”,使它们变得整洁。
答案 7 :(得分:0)
GraphQl完整示例:
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();
});
})