Rails 6 API项目中RSpec测试ActiveStorage附件上传失败如何解决?
Rails API模式下ActiveStorage附件上传RSpec测试失败解决方案
问题根因
你遇到的测试失败核心有两个原因:
- 测试请求使用了
as: :json格式:JSON协议本身不支持传输二进制文件,Rack会自动将你构造的上传文件对象序列化为哈希,导致Controller中拿到的params[:company_logo]是HashWithIndifferentAccess类型,和真实前端multipart/form-data格式上传得到的ActionDispatch::Http::UploadedFile类型不一致,这就是你观察到的参数类型差异的来源。 - 缺少强参数校验:你报错中反复出现的
permitted: false提示,说明参数没有经过permit授权,ActiveStorage要求附件参数必须是已授权状态才能正常处理。
修复步骤
第一步:修复Controller逻辑
添加强参数校验,避免未授权参数被ActiveStorage拒绝:
def update_company_logo if shop_params[:company_logo].present? if @current_user.shop.company_logo.attach(shop_params[:company_logo]) render json: @current_user, status: :ok else # 补充错误响应,避免上传失败无返回 render json: { errors: @current_user.shop.errors.full_messages }, status: :unprocessable_entity end end end private def shop_params params.permit(:company_logo) end
第二步:修复RSpec测试代码
首先确认spec/rails_helper.rb中已经引入了测试文件上传的依赖(Rails 6.1+默认已引入,无则手动添加):
RSpec.configure do |config| config.include ActionDispatch::TestProcess::FixtureFile end
测试用例正确写法如下:
# 构造真实的上传文件对象,不要用mock let(:company_logo) { Rack::Test::UploadedFile.new(file_fixture('400x400.png').to_s, 'image/png') } let(:user) { create(:user) } # 按你原有的用户构造逻辑调整即可 let(:shop) { create(:shop, user: user) } # 按你原有的店铺构造逻辑调整即可 it "attaches the company_logo" do post company_logo_shops_url, params: { company_logo: company_logo }, headers: valid_headers(user) # 去掉as: :json配置,Rack会自动识别为multipart/form-data格式 expect(shop.reload.company_logo).to be_attached end
注意事项
- 测试用的
400x400.png需要放在spec/fixtures/files/目录下,确保file_fixture可以正常读取 - 不需要手动构造
ActionDispatch::Http::UploadedFile对象,使用Rack::Test::UploadedFile即可被Rack自动转换为对应类型 - 测试完成后可在测试配置中添加ActiveStorage文件清理逻辑,避免测试产生的冗余文件占磁盘空间
内容的提问来源于stack exchange,提问作者Jan
相关产品推荐
相关产品推荐

