You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Jest和Supertest测试Passport.js本地认证

问题描述

需要为Passport.js本地认证逻辑编写测试时遇到以下问题:

  • 默认配置下,认证成功、失败均返回302状态码触发重定向,状态码完全一致,无法直接通过状态码区分认证结果
  • 尝试编写自定义认证回调逻辑,在认证失败时返回401未授权状态码并重定向到/users/login,但前端不会自动完成跳转,只会显示附带跳转路由链接的页面
  • 后续尝试通过断言response.headers['Location']判断重定向地址,但实际获取到的Location值为undefined

最初使用的自定义认证路由代码:

/*
app.post('/users/login', passport.authenticate('local', {
    successRedirect: '/users/dashboard',
    failureRedirect: '/users/login', 
    failureFlash: true,
}));*/
app.post('/users/login', (req, res, next) => {
  passport.authenticate('local', (err, user, info) => {
    if (err) return next(err);
    if (!user) res.redirect(401, '/users/login'); // failure
    req.logIn(user, (err) => {
      if (err) return next(err); 
      return res.redirect('/users/dashboard'); //success
  });
  })(req, res, next);
});

最初编写的测试用例(仅能断言状态码,不符合前端实际跳转逻辑):

test('should return a status code: 401', async () => {
    const response = await request(app).post('/users/login').send({
        // incorrect login
        email: 'jane@gmail.com',
        password: 'asdfa'
    })
    expect(response.statusCode).toBe(401)
})

后续调整的测试用例(无法获取到Location头):

test('should redirect to /users/login', async () => {
    const response = await request(app).post('/users/login').send({
        email: 'jane@gmail.com',
        password: 'incorrectPassword'
    })
    expect(response.headers['Location']).toEqual('/users/login');
})

回退到默认配置的登录路由:

app.post('/users/login', passport.authenticate('local', {
    successRedirect: '/users/dashboard',
    failureRedirect: '/users/login', 
    failureFlash: true
}));
解决方案

核心原因说明

  • 401状态码不会触发浏览器自动跳转:HTTP规范中仅3xx系列的重定向状态码会被浏览器自动识别并执行跳转,401属于客户端错误状态码,即使携带Location头,浏览器也不会自动跳转,这就是自定义回调返回401+重定向时前端不跳的根本原因,这种写法不符合HTTP语义,生产环境不推荐使用。
  • 拿不到Location头是因为Supertest默认自动跟随重定向:Supertest发起请求时默认会自动跟进3xx重定向,最终返回的是重定向后目标页面的响应,自然不会携带Location头。

正确测试写法(服务端渲染+重定向场景)

不需要修改Passport默认的302重定向逻辑,测试时添加.redirects(0)配置禁止Supertest自动跟随重定向,即可拿到原始重定向响应,同时通过状态码+Location头判断认证结果即可。

  • 登录失败场景测试:
test('登录失败时重定向到登录页', async () => {
  const response = await request(app)
    .post('/users/login')
    .send({
      email: 'jane@gmail.com',
      password: 'incorrectPassword'
    })
    .redirects(0) // 关键配置:禁止自动跟随重定向
  expect(response.statusCode).toBe(302)
  expect(response.headers.location).toBe('/users/login')
})
  • 登录成功场景测试:
test('登录成功时重定向到控制台页', async () => {
  const response = await request(app)
    .post('/users/login')
    .send({
      email: 'correct@example.com',
      password: 'correctPassword'
    })
    .redirects(0)
  expect(response.statusCode).toBe(302)
  expect(response.headers.location).toBe('/users/dashboard')
  // 可额外断言session中是否存入用户信息,确认登录状态生效
})

额外注意事项

之前写的自定义认证回调存在逻辑漏洞:判断!user的分支执行res.redirect后没有加return阻断流程,代码会继续向下执行req.logIn逻辑,引发请求处理错乱,如果要使用自定义回调,需要补全return:

if (!user) {
  return res.redirect(401, '/users/login'); // 加return终止后续逻辑执行
}

如果是前后端分离架构,不需要重定向的场景,可以直接在自定义回调里返回JSON响应:认证失败返回401+错误信息,认证成功返回200+用户信息,测试时直接断言状态码和响应体即可,不需要处理重定向逻辑。

内容的提问来源于stack exchange,提问作者NoobSailboat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 06:46:04