Ruby 想请教一下关于 Rails7 中关于集成测试的一个问题

2604249649 · 2023年05月07日 · 最后由 2604249649 回复于 2023年05月08日 · 298 次阅读

环境

  1. ruby version: 3.2.2
  2. rails version: 7.0.4.3

问题

我按照《Ruby on Rails 教程》一书,使用 cookies 完成记住用户的功能,但是之后进行集成测试时发生了如下错误:

signed 方法找不到

ERROR UsersLoginTest#test_login_with_valid_information_followed_by_logout (3.96s)
Minitest::UnexpectedError:      NoMethodError: undefined method `signed' for #<Rack::Test::CookieJar:0x00007f6e07adc6f0
  ......
            app/helpers/sessions_helper.rb:14:in `current_user'
            app/helpers/sessions_helper.rb:25:in `logged_in?'
            test/integration/users_login_test.rb:38:in `block in <class:UsersLoginTest>'

相关内容如下:

  • 集成测试 test/integration/users_logn_test.rb 文件
class UsersLoginTest < ActionDispatch::IntegrationTest
  ...

  test "login with valid information followed by logout" do
    get login_path
    post login_path, params: { session: { email: @user.email, password: "password" } }
    assert_redirected_to @user # 检查重定向地址是否正确
    follow_redirect! # 访问重定向地址
    assert_select "a[href=?]", login_path, count: 0
    assert_select "a[href=?]", logout_path
    assert_select "a[href=?]", user_path(@user)

    delete logout_path
    assert_not logged_in?
    assert_redirected_to root_url
    follow_redirect!
    assert_select "a[href=?]", login_path
    assert_select "a[href=?]", logout_path, count: 0
    assert_select "a[href=?]", user_path(@user), count: 0
  end
end
  • 辅助模块 app/helpers/sessions_helper.rb
module SessionsHelper
  # 登录指定用户
  def log_in(user)
    # 利用Rails提供的session方法在浏览器中创建一个临时cookie,内容是加密后的用户ID
    # 后续请求中,可以使用session[:user_id]取回这个ID
    session[:user_id] = user.id
  end

  # 返回当前登录的用户(如果有的话)
  def current_user
    # 如果session[:user_id]中有用户ID,就使用其检索用户
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id]) # 否则检查cookies[:user_id],并检索登录持久会话中存储的用户
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        log_in user # 将用户ID存入session
        @current_user = user
      end
    end
  end

  # 判断用户是否已经登录,已登录返回true,否则返回false
  def logged_in?
    !current_user.nil?
  end

  # 退出当前用户
  def log_out
    forget(current_user)
    session.delete(:user_id) # 从会话中删除用户ID
    @current_user = nil? # 可选步骤:将当前用户设为nil
  end

  # 在持久会话中记住用户
  def remember(user)
    user.remember # 为用户生成一个记忆令牌,并将其摘要记录到数据库记录中
    # permanent方法用于设置当前cookie的过期时间为20年后,signed方法用于对cookie值进行签名和加密
    cookies.permanent.signed[:user_id] = user.id # 对用户ID进行签名和加密
    cookies.permanent[:remember_token] = user.remember_token
  end

  # 忘记持久会话
  def forget(user)
    user.forget
    # 删除cookie
    cookies.delete(:user_id)
    cookies.delete(:remember_token)
  end
end
  • 相关的控制器 app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)

    respond_to do |format|
      if user && user.authenticate(params[:session][:password])
        # 保存用户登录信息
        log_in user
        remember user
        # 用户登录成功,重定向到用户资料页面
        format.html { redirect_to user }
      else
        # flash闪现消息在一个请求的生命周期内是持续存在的,但是重新渲染页面不算一次新的请求
        # 因为使用flash.now代替(flash.now专门用于在重新渲染的页面中显示闪现消息)
        flash.now[:danger] = "Log in failed, please check your email and password!"
        format.html { render :new, status: :bad_request }
      end
    end
  end

  def destroy
    log_out
    redirect_to root_url
  end
end

我在stackoverflow上看到有一个类似的问题,但是解决方案对我不起作用。同时我翻了一下Rails issues列表,找到了一个类似的问题Signed cookies not available in controller tests看了一遍,也没找到合适解决的方案,说是集成测试不能访问加密后的 cookies,我点搞不懂了。

有大佬知道怎么解决上面这个问题么?谢谢

assert_not logged_in? 去掉,不直接调用方法验证,改为通过发起请求验证是否登录状态,比如访问一个需要登录才能访问的页面,然后 assert_redirected_to login_url

zhengpd 回复

可行,但是我有点不理解 Rails 为啥要这样做

2604249649 回复

ActionDispatch::IntegrationTest doesn't support access to encrypted cookies by design because they are integration tests and don't allow access to anything that a browser would not.

你提到的那个 issue 有说,IntegrationTest 不允许访问浏览器访问不到的东西。rails 的 IntegrationTest 主要是通过发起 url 请求去测试一些业务流程实现是否正确,类似模拟浏览器行为,大概因为这样所以有这个限制设计

zhengpd 回复

嗯嗯,谢谢

2604249649 关闭了讨论。 05月08日 10:15
需要 登录 后方可回复, 如果你还没有账号请 注册新账号