编写有价值的控制器测试 - Rspec

时间:2016-02-16 18:19:31

标签: ruby-on-rails ruby rspec

我希望彻底测试我的控制器方法,然后转到我的下一部分代码,但想知道如何将这个方法分解为可测试的块

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

进行设置

由于

2 个答案:

答案 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保持在最低水平,仍然可以将它们分解)。