编写有价值的控制器测试 - Rspec [英] Writing valuable controller tests - Rspec

查看:53
本文介绍了编写有价值的控制器测试 - Rspec的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望在进入下一部分代码之前彻底测试我的控制器方法,但想知道如何将以下方法分解为可测试的块

I am looking to thoroughly test my controller method before moving onto my next part of code but would like to know how to break this following method down into testable chunks

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 

在网站用户完成交易后调用

This is called after a user of the site has completed a transaction

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

如您所见,有一些方法调用,但我想单独测试它们.

As you can see there are a few method calls but i would like to test them individually.

我将如何使用 rspec 进行设置

How would i go about setting this up with rspec

谢谢

推荐答案

我认为 @Nabeel 在将代码分解为可测试的块(问题的第一部分)方面走在正确的轨道上.稍后我将介绍您的第二部分(如何在 RSpec 中进行设置).

I think @Nabeel is on the right track in terms of breaking your code down into testable chunks (first part of your question). I'll get to your second part (how to set up in RSpec) in a bit.

我建议进一步重构.就我个人而言,我的控制器中几乎没有逻辑.我更喜欢将逻辑移到管理器"中(只是一个与控制器具有并行名称的 PORO).我喜欢这个,因为我发现测试 PORO 比测试控制器容易得多.我不知道上面的控制器叫什么名字,所以就叫它FooController.经理将是 FooManager.

I suggest further refactoring. Personally, I want almost no logic in my controllers. I prefer to move logic into a 'manager' (just a PORO that has a parallel name to the controller). I like this because I find testing POROs a lot easier than testing controllers. I don't know what the name of the above controller is, so let's just call it FooController. The manager would be 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

现在,对于你的测试,你可以这样做(我喜欢按方法分解我的测试):

Now, for your test, you could do (I like to break my tests up by method):

#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.

I think there is some discussion about whether the private methods should be tested. As long as your post-condition tests are comprehensive, then you should only have to test the one public method, FooManager.create in this case.

如果你遵循这个路径,那么你的控制器所做的就是调用 FooManager.create(params) 然后重定向.您可以在单独的 RSpec 测试文件中测试您的重定向.就我个人而言,当我的方法非常简单时,我倾向于跳过 RSpec 中的控制器测试,并将重定向测试推迟到使用 Cucumber/Capybara 进行的集成测试.

If you follow this path, then all your controller is doing is calling FooManager.create(params) and then redirecting. You could test your redirects in a separate RSpec test file. Personally, I tend to skip controller testing in RSpec when my methods are super skinny and defer redirect testing to integration tests with Cucumber/Capybara.

这篇关于编写有价值的控制器测试 - Rspec的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆