Rails 6 API RSpec请求测试冲突问题求助
解决React-Rails中RSpec接口测试与收藏删除功能冲突的问题
我之前也碰到过类似的坑,核心就是测试环境里没把登录用户和测试数据正确关联起来,导致控制器里的current_user.favourite_cocktails.find_by找不到记录,返回nil后调用delete直接报错。下面是一套能让功能和测试都正常跑的完整方案,包含所有需要调整的代码:
一、问题根源先理清楚
- 最初直接用
FavouriteCocktail.find(params[:id])删除的逻辑,测试能过但实际功能失效——因为这个逻辑没做用户校验,随便哪个用户都能删别人的收藏,而且前端大概率传的是鸡尾酒ID而非收藏记录ID,实际根本找不到对应记录。 - 重构后的
current_user.favourite_cocktails.find_by(cocktail_id: params[:id])才是符合业务逻辑的(只删当前用户的对应收藏),但测试里FactoryBot创建的收藏没绑定到测试用的登录用户,所以current_user.favourite_cocktails是空的,find_by返回nil就触发了NoMethodError。
二、完整代码修改方案
1. Gemfile(确保测试依赖齐全)
gem 'rails', '~> 7.0' gem 'react-rails' # 测试相关依赖 group :test do gem 'rspec-rails' gem 'factory_bot_rails' gem 'devise' # 假设你用devise做用户认证 gem 'shoulda-matchers' end
2. 路由配置(config/routes.rb)
保证路由符合RESTful风格,嵌套在api/v1下:
Rails.application.routes.draw do namespace :api do namespace :v1 do resources :favourite_cocktails, only: [:destroy] # 其他业务路由... end end end
3. 控制器逻辑(app/controllers/api/v1/favourite_cocktails_controller.rb)
保留正确的业务逻辑,同时加错误处理避免nil调用:
class Api::V1::FavouriteCocktailsController < ApplicationController before_action :authenticate_user! # 强制用户登录才能操作 def destroy @favourite_cocktail = current_user.favourite_cocktails.find_by(cocktail_id: params[:id]) if @favourite_cocktail @favourite_cocktail.destroy head :no_content else render json: { error: '收藏记录不存在' }, status: :not_found end end end
4. FactoryBot配置(spec/factories/favourite_cocktails.rb)
修改Factory,让它可以绑定到指定用户,默认也会自动关联一个用户:
FactoryBot.define do factory :favourite_cocktail do user { association :user } # 默认创建并关联一个新用户 cocktail { association :cocktail } # 假设你有Cocktail模型 end end
5. 模型约束(app/models/favourite_cocktail.rb)
确保模型有正确的关联和唯一性校验,避免重复收藏:
class FavouriteCocktail < ApplicationRecord belongs_to :user belongs_to :cocktail validates :user_id, uniqueness: { scope: :cocktail_id, message: '你已经收藏过这款鸡尾酒啦' } end
6. RSpec测试用例(spec/controllers/api/v1/favourite_cocktails_controller_spec.rb)
核心是登录测试用户,并创建和该用户绑定的收藏记录:
require 'rails_helper' RSpec.describe Api::V1::FavouriteCocktailsController, type: :controller do let(:test_user) { create(:user) } # 创建测试用用户 let(:test_cocktail) { create(:cocktail) } # 创建绑定到测试用户的收藏记录 let(:user_favourite) { create(:favourite_cocktail, user: test_user, cocktail: test_cocktail) } before do sign_in test_user # 登录测试用户,需要devise的测试helper支持 end describe 'DELETE #destroy' do context '当用户确实收藏过这款鸡尾酒时' do it '成功删除收藏记录并返回204' do user_favourite # 提前创建收藏记录 expect { delete :destroy, params: { id: test_cocktail.id } # 传鸡尾酒ID,匹配控制器逻辑 }.to change(FavouriteCocktail, :count).by(-1) expect(response).to have_http_status(:no_content) end end context '当用户没收藏过这款鸡尾酒时' do it '返回404错误提示' do delete :destroy, params: { id: 9999 } # 传一个不存在的鸡尾酒ID expect(response).to have_http_status(:not_found) expect(JSON.parse(response.body)['error']).to eq('收藏记录不存在') end end end end
7. 配置RSpec支持Devise(spec/rails_helper.rb)
在rails_helper里添加Devise的测试helper,让sign_in方法能正常用:
RSpec.configure do |config| # 其他配置项... config.include Devise::Test::ControllerHelpers, type: :controller end
三、验证步骤
- 跑测试:
bundle exec rspec spec/controllers/api/v1/favourite_cocktails_controller_spec.rb,所有用例应该都能通过; - 实际应用测试:登录用户,收藏一款鸡尾酒后点击删除,确认功能正常,且不会影响其他用户的收藏记录。
这样调整后,既保证了业务逻辑的正确性(只能删除自己的收藏),又让测试能正确关联用户和数据,彻底解决冲突问题。
内容的提问来源于stack exchange,提问作者Tunde
相关产品推荐
相关产品推荐

