Rails API应用:无法正常运行请求类Rspec测试
Rails API 请求类 Rspec 测试问题及解决方案
控制器代码
# frozen_string_literal: true module Api module V1 class MarketplacesController < ApplicationController before_action :authenticate_api_v1_user! ... def index render json: Marketplace.all, status: :ok, include: 'url' end ... end end end
三次尝试及问题
第一种尝试:自定义登录方法失败
在辅助文件编写login方法:
# spec/requests_helper.rb 文件 def login(user) post api_v1_user_session_path, params: { email: user.email, password: user.password }.to_json, as: :json end
测试文件调用:
# spec/requests/api/v1/marketplace_spec.rb 文件 require "rails_helper" require "requests_helper" RSpec.describe Marketplace, :type => :request do let(:user) { create(:user) } context "已登录状态" do it "返回所有市场数据" do login(user) get "/api/v1/marketplaces" expect(response.body).not_to include('redirected') # 临时断言,排查重定向HTML问题 end end end
问题:login返回凭证无效错误,请求体被双重转义:
"{\"email\":\"test_user@test.te\",\"password\":\"password\"}"
用户工厂密码与请求一致,但登录失败,最终请求返回未认证错误:
{"errors":["You need to sign in or sign up before continuing."]}
第二种尝试:使用Devise sign_in辅助方法
改用Devise的sign_in,在spec/rails_helper.rb添加配置:
... config.include Devise::Test::IntegrationHelpers, type: :request
测试代码:
# spec/requests/api/v1/marketplace_spec.rb 文件 require "rails_helper" require "requests_helper" RSpec.describe Marketplace, :type => :request do let(:user) { create(:user) } context "已登录状态" do it "返回所有市场数据" do sign_in user get "/api/v1/marketplaces" expect(response.body).not_to include('redirected') # 临时断言,排查重定向HTML问题 end end end
问题:请求返回HTML重定向内容,不符合API预期:
<html><body>You are being <a href="http://www.example.com/api/v1/auth/sign_in">redirected</a>.</body></html>
第三种尝试:修改默认URL配置
在config/environments/test.rb添加:
... config.action_controller.default_url_options = { host: 'localhost:3000', port: '3000' }
问题:仍返回HTML重定向,地址改为localhost:3000,但问题未解决。
解决方案
1. 修复自定义登录方法的参数序列化问题
手动to_json导致请求体被双重转义,移除手动序列化,让as: :json自动处理:
# spec/requests_helper.rb 文件 def login(user) post api_v1_user_session_path, params: { email: user.email, password: user.password }, as: :json end
2. 确保用户工厂正确加密密码
Devise User模型需要password_confirmation来正确加密密码,更新工厂:
# spec/factories/users.rb 文件 FactoryBot.define do factory :user, class: 'User' do name { 'A user' } uid { 'test_user@test.te' } email { 'test_user@test.te' } password { 'password'} password_confirmation { 'password' } # 添加此行 end end
3. 配置Devise针对API返回JSON而非重定向
在API的基控制器中重写Devise的认证失败响应,强制返回JSON:
# app/controllers/api/v1/application_controller.rb module Api module V1 class ApplicationController < ActionController::API include Devise::Controllers::Helpers protect_from_forgery with: :null_session # 重写认证失败逻辑,返回JSON def authenticate_api_v1_user! unless user_signed_in? render json: { errors: ['You need to sign in or sign up before continuing.'] }, status: :unauthorized end end # 拦截Devise的重定向,针对JSON请求返回错误 def redirect_to(options = {}, response_options = {}) if request.format.json? render json: { errors: ['You need to sign in or sign up before continuing.'] }, status: :unauthorized else super end end end end end
4. 测试中设置正确的请求头(针对sign_in方法)
使用sign_in时,确保请求头指定接受JSON格式,避免服务器返回HTML:
# spec/requests/api/v1/marketplace_spec.rb 文件 require "rails_helper" RSpec.describe "Marketplaces API", type: :request do let(:user) { create(:user) } context "已登录状态" do it "返回所有市场数据" do sign_in user get "/api/v1/marketplaces", headers: { 'Accept' => 'application/json' } expect(response).to have_http_status(:ok) expect(response.content_type).to match(/application\/json/) # 添加具体的业务断言 # expect(JSON.parse(response.body)).to have_key('marketplaces') end end end
内容的提问来源于stack exchange,提问作者Fito
相关产品推荐
相关产品推荐

