我希望彻底测试我的控制器方法,然后转到我的下一部分代码,但想知道如何将这个方法分解为可测试的块
private
def create_real_user
return unless current_or_guest_user.is_guest?
generated_password = Devise.friendly_token.first(8)
@user = User.new(
is_guest: false,
first_name: params[:first_name],
last_name: params[:last_name],
email: params[:email],
password: generated_password,
password_confirmation: generated_password,
braintree_id: @result.transaction.customer_details.id
)
@user.save(validate: false)
RegistrationMailer.welcome(@user, generated_password).deliver_now
end
在网站用户完成交易后调用此方法
def create
@result = Braintree::Transaction.sale(
amount: @transaction_total,
payment_method_nonce: params[:payment_method_nonce],
)
if @result.success?
create_real_user
update_completed_transaction
guest_user.destroy
redirect_to thank_you_path
else
update_transaction
@error_message = BraintreeErrors::Errors.new.error_message(@result)
flash.now[:alert] = @error_message
flash.keep
redirect_to new_transaction_path
end
end
正如您所看到的,有一些方法调用,但我想单独测试它们。
我将如何使用rspec
进行设置由于
答案 0 :(得分:1)
我认为@Nabeel在将代码分解为可测试块方面处于正确的轨道(问题的第一部分)。我稍后将介绍你的第二部分(如何在RSpec中设置)。
我建议进一步重构。就个人而言,我的控制器几乎没有逻辑。我更喜欢将逻辑转换为“管理器”(只是一个与控制器具有并行名称的PORO)。我喜欢这个,因为我发现测试PORO比测试控制器容易得多。我不知道上面控制器的名称是什么,所以我们只需称它为FooController
。经理将是FooManager
。
#controllers/foo_controller.rb
class FooController < ApplicationController
def create
# sometimes I have to do pre-processing of params, but I
# try to keep this to a minimum as is violates my 'dumb
# controller' mantra. My preference is to pass them is
# 'as is' whenever possible.
@results = FooManager.create(params)
redirect_to @results[:success] ? thank_you_path : new_transaction_path
end
end
#managers/foo_manager.rb
class FooManager
class << self
# my managers' public methods are always named exactly
# the same as their paired controller methods so I
# don't have to remember what I named things.
def create(params)
@params = params # make params available to all subsequent methods
{success: process_braintree_sale}
end
private
# as written, this method will fail because @transaction_total
# hasn't been defined yet.
def process_braintree_sale
@braintree_sale = Braintree::Transaction.sale(
amount: @transaction_total,
payment_method_nonce: @params[:payment_method_nonce],
)
@braintree_sale.success ? post_process_success : post_process_failure
end
# other methods as Nabeel outlined above.
end
end
现在,为了您的测试,您可以(我喜欢通过方法打破我的测试):
#spec/managers/foo_manager/create
require 'rails_helper'
RSpec.describe FooManager do
describe "#create" do # I will only test the #create method in this file
context "when using a good params" do
before(:each) do
@params = ActionController.parameters.new(
good: 'parameters',
provided: 'here'
) # create a set of ActionController parameters
end
it "creates a User" do
expect{FooManager.create(@params)}.to change{User.count}.by(1)
end
it "does other useful stuff" do
# write another post-condition test here
end
it "does even more useful stuff" do
# write another post-condition test here
end
# until you have comprehensive post-condition tests
end
end
end
我认为有一些关于是否应该测试私有方法的讨论。只要你的后置条件测试是全面的,那么在这种情况下你只需要测试一个公共方法FooManager.create
。
如果您遵循此路径,那么您的所有控制器正在调用FooManager.create(params)
,然后重定向。您可以在单独的RSpec测试文件中测试重定向。就个人而言,当我的方法超级瘦,我倾向于跳过RSpec中的控制器测试,并将重定向测试推迟到与Cucumber / Capybara的集成测试。
答案 1 :(得分:0)
也许是这样的? (另)
def create
@result = Braintree::Transaction.sale(
amount: @transaction_total,
payment_method_nonce: params[:payment_method_nonce],
)
@result.success? ? successful_transaction : transaction_error
end
def successful_transaction
setup_user
update_completed_transaction
guest_user.destroy
redirect_to thank_you_path
end
def transaction_error
update_transaction
@error_message = BraintreeErrors::Errors.new.error_message(@result)
flash.now[:alert] = @error_message
flash.keep
redirect_to new_transaction_path
end
private
def setup_user
return unless current_or_guest_user.is_guest?
generated_password = generate_password
@user = create_user(generated_password)
save_user!
RegistrationMailer.welcome(@user, generated_password).deliver_now
end
def generate_password
Devise.friendly_token.first(8)
end
def create_user(generated_password)
User.new(
is_guest: false,
first_name: params[:first_name],
last_name: params[:last_name],
email: params[:email],
password: generated_password,
password_confirmation: generated_password,
braintree_id: @result.transaction.customer_details.id
)
end
def save_user!
@user.save(validate: false)
end
然而,你不应该对私人方法进行真正的测试,除非它们变得复杂(为了便于阅读并将ABC保持在最低水平,仍然可以将它们分解)。