【问题标题】:Rspec: Stub method that is in the controllerRspec:控制器中的存根方法
【发布时间】:2013-07-28 21:53:21
【问题描述】:

我可以知道如何存根控制器创建方法中的方法吗?我需要为此编写规范,但我收到了这些错误。我需要检查控制器中的创建方法必须在模型中创建新的公司记录之前执行 validate_fbid 方法。

错误:

1) Companies new company create with valid information#validate_fbid should have correct parameters and return value
 Failure/Error: CompaniesController.create.should_receive(:validates_fbid).with(company)
 NoMethodError:
   undefined method `create' for CompaniesController:Class
 # ./spec/requests/companies_spec.rb:38:in `block (5 levels) in <top (required)>'

  2) Companies new company create with valid information#validate_fbid should fbid validation passed
 Failure/Error: CompaniesController.create.stub(:validates_fbid).and_return('companyid')
 NoMethodError:
   undefined method `create' for CompaniesController:Class
 # ./spec/requests/companies_spec.rb:43:in `block (5 levels) in <top (required)>'

公司控制器

def create 
company = Company.new(params[:company])
verifyfbid = validate_fbid(company)

if verifyfbid != false
    if company.fbid.downcase == verifyfbid.downcase
        if company.save 
            @message = "New company created."
            redirect_to root_path
        else 
            @message = "Company create attempt failed. Please try again."
            render 'new' 
        end 
    else 
        @message = "Company create attempt failed. Invalid facebook id."
        render 'new' 
    end
else  
    @message = "Company create attempt failed. No such facebook id."
    render 'new'            
    end             
 end 

  private  
  def validate_fbid(company)
   uri = URI("http://graph.facebook.com/" + company.fbid)
   data = Net::HTTP.get(uri)
   username = JSON.parse(data)['username']      
   if username.nil?
    return false 
   else
    "#{username}"
   end
 end

请求/companies_spec.rb

context "#validate_fbid" do               
        #validate fbid
        let(:company){ Company.new(name:'Example Company', url: 'www.company.com', fbid: 'companyid', desc: 'Company desc' )}

        it "should have correct parameters and return value" do
            CompaniesController.create.should_receive(:validates_fbid).with(company)
                                .and_return('companyid')
        end

        it "should fbid validation passed" do               
            CompaniesController.create.stub(:validates_fbid).and_return('companyid')
            company.fbid.should_not be_nil
            company.fbid.should == 'companyid'
            company.save
            expect { click_button submit }.to change(Company, :count).by(1)
        end                                             
    end    

【问题讨论】:

    标签: ruby-on-rails rspec ruby-on-rails-3.2 mocking stub


    【解决方案1】:

    您可以存根 controller 方法:

    allow(controller).to receive(...).with(...).and_return(...)
    

    【讨论】:

      【解决方案2】:

      这是 Rspec 3 (3.3) 的推荐语法:

      • allow_any_instance_of(CompaniesController).to receive(:validates_fbid).and_return("companyid")

      • expect_any_instance_of(CompaniesController).to receive(:validates_fbid).and_return("companyid")

      来源:https://relishapp.com/rspec/rspec-mocks/docs/working-with-legacy-code/any-instance

      【讨论】:

        【解决方案3】:

        当代码难以测试时,通常是因为它很复杂。

        你应该这样重构这段代码:

        • 将验证逻辑移动到新的“服务类”中,该服务类仅负责在 facebook 上进行公司验证
        • 这将使验证功能独立于 Web 层并且大大更容易测试
        • 为服务类制定规范,它将单独测试此代码(无控制器)
        • 清理逻辑控制器 - 您不希望控制器内部有逻辑(经验法则:最多嵌套一级)
        • 控制器规范也将更容易

        控制器代码可能如下所示:

        def create
          company = Company.new(params[:company])
          verified = FbCompanyVerifier.new.verify(company)
        
          if verified and company.save
            # success logic
          else
            # fail logic
          end
        end
        

        【讨论】:

        • 您好,感谢您的回复。这是否意味着我需要创建一个具有“新”方法和“验证”方法的类 FbCompanyVerifier?我想我不允许在控制器页面中创建一个类,所以我应该创建哪个文件夹“FbCompanyVerifier”?感谢您的澄清。我对此很陌生。
        • 正确 - 具有实例方法“验证”的完整类,如果它不执行任何操作,您可以跳过“新”(初始化)方法。通常这是一个最佳实践,因此您使用一流的对象/普通的 ruby​​ 对象进行操作。 (有些人可能会使用带有类方法的类,它有自己的优点和缺点)。由于这个类依赖于你的模型公司,你应该把它放在应用程序文件夹“app/services”的某个地方。您可以将小型单一职责类放在其中,其中包含一两个方法,并且独立于 Web 层。
        • 您还可以查看 code_climate 博客文章,其中描述了类似的重构 - 将逻辑代码提取到具有单一职责的新类中,编写简单的规范,然后将其用作 PORO(plain-old-ruby-object ) 在代码中...:blog.codeclimate.com/blog/2013/07/23/…
        • 如果FbCompanyVerifier#verify 执行了一个HTTP 请求(或者你想在测试期间避免的任何其他副作用),你如何测试来防止它?在FbCompanyVerifier上使用allow_any_instance_of
        【解决方案4】:

        如果你正在测试控制器,你可以直接访问控制器:

        controller.stub(:message) { 'this is the value to return' }
        

        【讨论】:

        • 这个答案对我有用,因为当我存根方法时,我知道它需要一个参数,但我不知道参数是什么。我只是想传递参数,所以我使用带有参数的块来覆盖(存根)方法。
        【解决方案5】:

        当它是测试用例的主题时,您不想存根方法

        context "#validate_fbid" do
          #test the function here
          #don't stub
        end
        

        当您在控制器中测试创建操作时,您可以存根“validate_fbid”

        describe "post create" do
           ...
           CompaniesController.any_instance.stub(:validates_fbid).and_return('companyid')
           ...
        end
        

        希望对你有帮助。

        【讨论】:

        • allow_any_instance_of(CompaniesController).to receive(:validates_fbid).and_return('companyid') 用于 Rspec3
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-31
        相关资源
        最近更新 更多