ruby rails - fixture_file_upload 无法使用 CarrierWave 的控制器测试
ruby on rails - fixture_file_upload not working with controller tests for CarrierWave
我在使用 CarrierWave 和 Minitest 对 ROR 5.2 项目进行控制器集成测试时遇到问题。我正在使用 fixture_file_upload 到 "upload" 一个文件,该文件在我的模型测试中工作正常,但由于 CarrierWave 属性 在我的模型中的存在验证而在我的控制器集成测试中失败。它总是 创建操作失败。更新操作偶尔也会随机失败,即使我没有更新 CarrierWave 属性.
我使用 byebug 检查了创建操作中 属性 的值,但 returns 没有; 属性 从未设置。我还检查了新创建模型的错误,它们是:"Icon can't be blank".
fixture_file_upload 在我的模型测试中工作正常,手动执行 creating/updating/uploading(不是在测试中)也工作正常。
我在 Google 上搜索了很多小时试图弄清楚我做错了什么,但我发现的所有内容都说要使用 fixture_file_upload,但我没有找到与我遇到的问题相关的任何内容有.
CarrierWave 初始化程序:
CarrierWave.configure do |config|
#To let CarrierWave work on heroku
config.root = Rails.root.join('tmp')
config.cache_dir = 'uploads/tmp'
if Rails.env.test? || Rails.env.development?
config.storage = :file
#config for tests is done in test/test_helper.rb
else
config.storage = :fog
config.fog_credentials = { #Configuration for Amazon S3
provider: 'AWS',
aws_access_key_id: Rails.application.credentials.aws[:access_key_id],
aws_secret_access_key: Rails.application.credentials.aws[:secret_access_key],
region: Rails.application.credentials.aws[:region]
}
config.fog_public = false
config.fog_directory = Rails.application.credentials.aws[:bucket_name]
config.fog_host = "#{Rails.application.credentials.aws[:asset_url]}/#{Rails.application.credentials.aws[:bucket_name]}"
end
end
测试助手:
ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
require 'rails/test_help'
include ActionDispatch::TestProcess #for fixture_file_upload
module UsersHelper
def login_as(user)
get login_user_url
assert_response :success
post user_login_url(session: { username: user.username, password: 'test1234' }) #have to hard code password here since passwords are stored encrypted
assert_redirected_to root_url, 'Login did not redirect'
end
def logout
get user_logout
end
end
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
# Add more helper methods to be used by all tests here...
end
class ActionDispatch::IntegrationTest
include UsersHelper
end
#setup carrierwave for tests
carrierwave_root = Rails.root.join('tmp', 'test', 'support', 'carrierwave')
carrierwave_template = Rails.root.join('test', 'fixtures', 'files')
CarrierWave.configure do |config|
config.root = carrierwave_root
config.cache_dir = carrierwave_root.join('carrierwave_cache')
config.enable_processing = false
end
#copy carrierwave fixture files to carrierwave root
puts 'Copying CarrierWave fixture files..'
puts carrierwave_template.join('uploads')
puts carrierwave_root
FileUtils.cp_r carrierwave_template.join('uploads'), carrierwave_root
Minitest.after_run do
#remove carrierwave files
puts 'Deleting CarrerWave fixture files...'
Dir.glob(Pathname.new(carrierwave_root).join('*')).each do |dir|
FileUtils.remove_entry(dir)
end
puts 'Cleaning CarrierWave cached files...'
CarrierWave.clean_cached_files!(0)
end
型号:
class Category < ApplicationRecord
mount_uploader :icon, IconUploader, dependent: :destroy
validates :name, length: { minimum: 2, maximum: 30 }, uniqueness: { case_sensitive: false }
validates :slug, length: { minimum: 2, maximum: 30 }, uniqueness: { case_sensitive: false }
validates :icon, presence: true
end
图标上传器:
class IconUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
after :remove, :delete_empty_upstream_dirs
def store_dir
"#{base_store_dir}/#{model.id}"
end
def base_store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}"
end
#override file name, for uniqueness
def filename
random_token = SecureRandom.hex(6/2) #length of 6 characters
token_var = "@#{mounted_as}_secure_token" #get token variable name
token = model.instance_variable_get(token_var) #get token from token variable name
token ||= model.instance_variable_set(token_var, random_token) #if token isn't already set, set it
@name ||= "#{token}_#{super}" if original_filename.present? and super.present? #create name, using instance variable so token isn't changed (function is called multiple times)
end
#set size limits
def size_range
1.kilobyte..256.kilobytes #1 kilobyte to 256 kilobytes
end
#resize image if width or height is greater than 256px, add padding if needed
process resize_and_pad: [256, 256] #don't use resize_to_fit, as it adds a white background at least to SVG images
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
def extension_whitelist
%w(jpg jpeg png svg)
end
#whitelist of content types
def content_type_whitelist
/image\// #whitelist images
end
private
#delete directory if it's empty
def delete_empty_upstream_dirs
path = ::File.expand_path(store_dir, root)
Dir.delete(path) #fails if path not empty dir
path = ::File.expand_path(base_store_dir, root)
Dir.delete(path) #fails if path not empty dir
rescue SystemCallError => e
Rails.logger.error(e.message) #log error
true #nothing, the dir is not empty
end
end
控制器创建操作:
def create
data = params.require(:category).permit([ :name, :icon, :icon_cache ])
@category = Category.new(data)
if @category.save
flash.notice = 'Category successfully created.'
redirect_to categories_path
else
render :add #show errors
end
end
控制器测试:
test "should post category_create when admin" do
login_as(users(:admin))
get add_category_url
assert_response :success
icon = fixture_file_upload(Rails.root.join('test', 'fixtures', 'files', 'category_icon.svg'))
#fails: validation error: "Icon can't be blank"
post category_create_url(category: { name: 'test901', icon: icon, icon_cache: '' })
assert_redirected_to categories_url
assert_equal 'Category successfully created.', flash[:notice]
end
模型测试:
test "should save when all details correct" do
category = Category.new(name: 'tools',
icon: fixture_file_upload(Rails.root.join('test', 'fixtures', 'files', 'category_icon.svg')))
#succeeds
assert category.save, 'Not saved when all details correct: ' + category.errors.full_messages.to_s
end
post category_create_url(category: { name: 'test901', icon: icon, icon_cache: '' })
应该是
post category_create_url, params: {category: { name: 'test901', icon: icon, icon_cache: '' }}
第一个是将参数发送到 route_helper 并导致尝试通过查询字符串参数传递文件,但这是行不通的。
第二个将参数发送到 post 方法,该方法正确地 post 将参数作为 multipart/form 数据,这将正确地 post 文件对象到控制器。
我在使用 CarrierWave 和 Minitest 对 ROR 5.2 项目进行控制器集成测试时遇到问题。我正在使用 fixture_file_upload 到 "upload" 一个文件,该文件在我的模型测试中工作正常,但由于 CarrierWave 属性 在我的模型中的存在验证而在我的控制器集成测试中失败。它总是 创建操作失败。更新操作偶尔也会随机失败,即使我没有更新 CarrierWave 属性.
我使用 byebug 检查了创建操作中 属性 的值,但 returns 没有; 属性 从未设置。我还检查了新创建模型的错误,它们是:"Icon can't be blank".
fixture_file_upload 在我的模型测试中工作正常,手动执行 creating/updating/uploading(不是在测试中)也工作正常。
我在 Google 上搜索了很多小时试图弄清楚我做错了什么,但我发现的所有内容都说要使用 fixture_file_upload,但我没有找到与我遇到的问题相关的任何内容有.
CarrierWave 初始化程序:
CarrierWave.configure do |config|
#To let CarrierWave work on heroku
config.root = Rails.root.join('tmp')
config.cache_dir = 'uploads/tmp'
if Rails.env.test? || Rails.env.development?
config.storage = :file
#config for tests is done in test/test_helper.rb
else
config.storage = :fog
config.fog_credentials = { #Configuration for Amazon S3
provider: 'AWS',
aws_access_key_id: Rails.application.credentials.aws[:access_key_id],
aws_secret_access_key: Rails.application.credentials.aws[:secret_access_key],
region: Rails.application.credentials.aws[:region]
}
config.fog_public = false
config.fog_directory = Rails.application.credentials.aws[:bucket_name]
config.fog_host = "#{Rails.application.credentials.aws[:asset_url]}/#{Rails.application.credentials.aws[:bucket_name]}"
end
end
测试助手:
ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
require 'rails/test_help'
include ActionDispatch::TestProcess #for fixture_file_upload
module UsersHelper
def login_as(user)
get login_user_url
assert_response :success
post user_login_url(session: { username: user.username, password: 'test1234' }) #have to hard code password here since passwords are stored encrypted
assert_redirected_to root_url, 'Login did not redirect'
end
def logout
get user_logout
end
end
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
# Add more helper methods to be used by all tests here...
end
class ActionDispatch::IntegrationTest
include UsersHelper
end
#setup carrierwave for tests
carrierwave_root = Rails.root.join('tmp', 'test', 'support', 'carrierwave')
carrierwave_template = Rails.root.join('test', 'fixtures', 'files')
CarrierWave.configure do |config|
config.root = carrierwave_root
config.cache_dir = carrierwave_root.join('carrierwave_cache')
config.enable_processing = false
end
#copy carrierwave fixture files to carrierwave root
puts 'Copying CarrierWave fixture files..'
puts carrierwave_template.join('uploads')
puts carrierwave_root
FileUtils.cp_r carrierwave_template.join('uploads'), carrierwave_root
Minitest.after_run do
#remove carrierwave files
puts 'Deleting CarrerWave fixture files...'
Dir.glob(Pathname.new(carrierwave_root).join('*')).each do |dir|
FileUtils.remove_entry(dir)
end
puts 'Cleaning CarrierWave cached files...'
CarrierWave.clean_cached_files!(0)
end
型号:
class Category < ApplicationRecord
mount_uploader :icon, IconUploader, dependent: :destroy
validates :name, length: { minimum: 2, maximum: 30 }, uniqueness: { case_sensitive: false }
validates :slug, length: { minimum: 2, maximum: 30 }, uniqueness: { case_sensitive: false }
validates :icon, presence: true
end
图标上传器:
class IconUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
after :remove, :delete_empty_upstream_dirs
def store_dir
"#{base_store_dir}/#{model.id}"
end
def base_store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}"
end
#override file name, for uniqueness
def filename
random_token = SecureRandom.hex(6/2) #length of 6 characters
token_var = "@#{mounted_as}_secure_token" #get token variable name
token = model.instance_variable_get(token_var) #get token from token variable name
token ||= model.instance_variable_set(token_var, random_token) #if token isn't already set, set it
@name ||= "#{token}_#{super}" if original_filename.present? and super.present? #create name, using instance variable so token isn't changed (function is called multiple times)
end
#set size limits
def size_range
1.kilobyte..256.kilobytes #1 kilobyte to 256 kilobytes
end
#resize image if width or height is greater than 256px, add padding if needed
process resize_and_pad: [256, 256] #don't use resize_to_fit, as it adds a white background at least to SVG images
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
def extension_whitelist
%w(jpg jpeg png svg)
end
#whitelist of content types
def content_type_whitelist
/image\// #whitelist images
end
private
#delete directory if it's empty
def delete_empty_upstream_dirs
path = ::File.expand_path(store_dir, root)
Dir.delete(path) #fails if path not empty dir
path = ::File.expand_path(base_store_dir, root)
Dir.delete(path) #fails if path not empty dir
rescue SystemCallError => e
Rails.logger.error(e.message) #log error
true #nothing, the dir is not empty
end
end
控制器创建操作:
def create
data = params.require(:category).permit([ :name, :icon, :icon_cache ])
@category = Category.new(data)
if @category.save
flash.notice = 'Category successfully created.'
redirect_to categories_path
else
render :add #show errors
end
end
控制器测试:
test "should post category_create when admin" do
login_as(users(:admin))
get add_category_url
assert_response :success
icon = fixture_file_upload(Rails.root.join('test', 'fixtures', 'files', 'category_icon.svg'))
#fails: validation error: "Icon can't be blank"
post category_create_url(category: { name: 'test901', icon: icon, icon_cache: '' })
assert_redirected_to categories_url
assert_equal 'Category successfully created.', flash[:notice]
end
模型测试:
test "should save when all details correct" do
category = Category.new(name: 'tools',
icon: fixture_file_upload(Rails.root.join('test', 'fixtures', 'files', 'category_icon.svg')))
#succeeds
assert category.save, 'Not saved when all details correct: ' + category.errors.full_messages.to_s
end
post category_create_url(category: { name: 'test901', icon: icon, icon_cache: '' })
应该是
post category_create_url, params: {category: { name: 'test901', icon: icon, icon_cache: '' }}
第一个是将参数发送到 route_helper 并导致尝试通过查询字符串参数传递文件,但这是行不通的。
第二个将参数发送到 post 方法,该方法正确地 post 将参数作为 multipart/form 数据,这将正确地 post 文件对象到控制器。