我正在使用 Passport.js 进行身份验证(本地策略)并使用 Mocha 和 Supertest 进行测试。
如何使用 Supertest 创建会话并发出经过身份验证的请求?
我正在使用 Passport.js 进行身份验证(本地策略)并使用 Mocha 和 Supertest 进行测试。
如何使用 Supertest 创建会话并发出经过身份验证的请求?
正如 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();
}
};
};
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.
尝试这个,
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();
});
作为 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();
});
});
});
抱歉,建议的解决方案都不适用于我。
由于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);
});
};
有一个富有成效的一天!
我将假设您使用的是 CookieSession 中间件。
正如 grub 提到的,您的目标是获取 cookie 值以传递给您的请求。但是,无论出于何种原因(至少在我的测试中), supertest 都不会在同一个测试中触发 2 个请求。因此,我们必须对如何获得正确的 cookie 值进行逆向工程。首先,您需要 require 用于构建 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 的会话,则 Passport 将回退的 Connect CookieSession 中间件中删除var val
带有"j:"
和的行。"s:"
最后,我们序列化 cookie。我放在"session"
那里,因为这就是我配置我的 cookie 会话中间件的方式。此外,App.config.cookieSecret
在其他地方定义,它必须是您传递给 Express/Connect CookieSession 中间件的秘密。我把它藏起来,Test.cookie
以便以后可以访问它。
现在,在实际测试中,您需要使用该 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
with"cookie"
和的调用Test.cookie
。这将导致请求使用我们构建的 cookie。
现在,您已经假装您的应用程序认为用户已登录,并且您不必保持实际的服务器运行。
这是一种简洁的方法,它具有可重用的额外好处。
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()
})
}
)
})
})
现在,您为所有测试提供了一个更加可重用的套件“包装器”,让它们变得整洁。
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();
});
})