我有一个带有Web视图和JSON API的Rails 4.2.7应用程序。Web视图和API有单独的基本控制器。我希望API基本控制器继承自ActionController::API
。但是,在Rails 4中ActionController::API
是在rails-api
gem中实现的,而我使用的是标准rails
gem。
我试图创建一个ActionController::API
“克隆”从继承ActionController::Metal
和包括所包含所有模块ActionController::API
(见源rails-api
在这里和rails
5 这里)。结果代码为:
class Api::V1::BaseApiController < ActionController::Metal
include AbstractController::Rendering
include ActionController::UrlFor
include ActionController::Redirecting
# originally ApiRendering
include ActionController::Rendering
include ActionController::Renderers::All
include ActionController::ConditionalGet
include ActionController::RackDelegation
# Originally BasicImplicitRender
include ActionController::ImplicitRender
include ActionController::StrongParameters
include ActionController::ForceSSL
include ActionController::DataStreaming
# Before callbacks should also be executed as early as possible so
# also include them at the bottom.
include AbstractController::Callbacks
# …
Run Code Online (Sandbox Code Playgroud) 我想使用 RSpec 彻底测试 Rails 应用程序授权设置 (Pundit)。
Pundit 文档和其他值得尊敬的来源建议为 Pundit 策略类编写单元测试。这些测试是测试行为(好)还是实施细节(坏)?
如果我们实际上正在测试行为,那么如果我们最终从 Pundit 切换到另一个授权 gem 或滚动我们自己的授权,我们的测试难道不应该仍然通过吗?
假设对该authorize
方法的调用被意外地从控制器操作中删除,使其开放供公共访问。权威人士的政策测试将不断通过。如果我们没有其他测试涵盖这一点,我们的测试套件可能都是绿色的,而我们的应用程序存在严重的安全漏洞。我们怎样才能避免这种情况呢?也许为 Pundit 控制器类编写单元测试,为控制器单独进行单元测试并模拟authorize
控制器测试中的方法以确保调用它们?
编辑:再想一想,我对类的实现细节和公共 API 之间的差异做出了判断。调用特定的公共方法、传递特定的参数并期望特定的返回值是对任何类进行单元测试(和使用)的要求。请忽略第3项,因为我原来的论点是无效的。
authorize
不调用该方法,它们也会失败。然而,这些将是集成测试,并且应该比单元测试 Pundit 策略类慢。这是否是测试授权设置的更好方法?提前致谢。
我对控制器规范中的每个 HTTP 方法/控制器操作组合重复了一次以下测试:
it "requires authentication" do
get :show, id: project.id
# Unauthenticated users should be redirected to the login page
expect(response).to redirect_to new_user_session_path
end
Run Code Online (Sandbox Code Playgroud)
我找到了以下三种方法来重构它并消除重复。哪一个最合适?
在我看来,共享示例是最合适的解决方案。但是,为了将 传递params
给共享示例而必须使用块感觉有点尴尬。
shared_examples "requires authentication" do |http_method, action|
it "requires authentication" do
process(action, http_method.to_s, params)
expect(response).to redirect_to new_user_session_path
end
end
RSpec.describe ProjectsController, type: :controller do
describe "GET show", :focus do
let(:project) { Project.create(name: "Project Rigpa") }
include_examples "requires authentication", :GET, :show do
let(:params) { {id: project.id} }
end
end
end
Run Code Online (Sandbox Code Playgroud)
以下代码使用 RSpec 3.5 和用于文件上传的Shrine gem在 Rails 4.2 应用程序的模型规范内测试图像验证。
我的问题是:
文件上传设置的其他方面在控制器和功能规范中进行了测试,这些与此问题无关。
RSpec.describe ShareImage, :type => :model do
describe "#image", :focus do
let(:image_file) do
# Could not get fixture_file_upload to work, but that's irrelevant
Rack::Test::UploadedFile.new(File.join(
ActionController::TestCase.fixture_path, 'files', filename))
end
let(:share_image) { FactoryGirl.build(:share_image, image: image_file) }
before(:each) { share_image.valid? }
context "with a valid image file" do
let(:filename) { 'image-valid.jpg' }
it "attaches the image to this record" do
expect(share_image.image.metadata["filename"]).to eq filename
end
end
context "with JPG extension and …
Run Code Online (Sandbox Code Playgroud) 使用 RSpec 测试多个 Rails 控制器的 RESTful 操作会产生大量代码重复。以下代码是我第一次尝试使用共享示例来解决问题。
这是我不喜欢代码的地方,找不到更好的方法,希望您帮助改进:
let
在控制器规范内的块内设置特定变量(高耦合)。我尝试使用模型名称来推断工厂名称并在共享示例中创建测试数据。它可以很好地创建记录和记录变量。但是,某些模型需要存在关联,而 FactoryGirl.attributes_for 不会创建关联记录,因此验证失败。因此,对于不同的模型,valid_attributes 的创建方式不同。我能想到的在共享示例中创建 valid_attributes 的唯一(可能是坏的)方法是传递一个包含用于创建属性的代码的字符串,并在共享示例中对其进行评估(eval)eval
用于调用 Rails 的路由/路径助手。此应用程序中的不同控制器具有不同的重定向行为。创建或更新记录后,一些控制器重定向到#show 操作,其他控制器重定向到#index。问题是,当期望重定向到#show, AFAIK 时,我们必须知道记录 ID 才能构建预期的 URL。我们不知道控制器规范中的记录 ID。我们只在共享示例中知道它。那么,如果我们还不知道 URL 是什么(因为我们不知道记录 ID),我们如何将预期的重定向 URL 从控制器规范传递到共享示例?另外,如果您发现任何其他问题,请告诉我。
控制器规格:
# spec/controllers/quotes_controller_spec.rb
require "rails_helper"
RSpec.describe QuotesController, :focus, :type => :controller do
login_admin
let(:model) { Quote }
let(:record) { FactoryGirl.create(:quote) }
let(:records) { FactoryGirl.create_pair(:quote) }
let(:valid_attributes) { FactoryGirl.attributes_for(:quote, quote: "New quote") }
let(:invalid_attributes) { valid_attributes.update(quote: nil) }
include_examples "GET #index"
include_examples "GET #show"
include_examples "GET #new" …
Run Code Online (Sandbox Code Playgroud) Rails 4.2 应用程序的索引视图有一个表,表头带有排序链接。如果用户单击“电子邮件”标题,记录将按电子邮件排序,依此类推。单击排序链接时,页面将重新加载(使用类似的查询字符串q=email+asc
)。尚未使用 AJAX。
我写了以下测试。它有效,但我相信应该有更好的方法来测试它。
it "sorts by e-mail when the 'E-mail' column header is clicked", :focus do
visit "/admin/users"
expected_id_order = User.order(:email).map(&:id)
# Once the page is reloaded (the new sort order is applied), the "stale"
# class will disappear. Note that due to Turbolinks, only the part of
# the DOM that is modified by a request will have its elements replaced.
page.execute_script("$('tr.user:first').addClass('stale')")
within "thead" do
click_link("E-mail")
end
# Force Capybara to wait until the page …
Run Code Online (Sandbox Code Playgroud)