【问题标题】:Rspec how to create an method to "DRY" only some params of a request?Rspec如何创建一个方法来“干燥”请求的一些参数?
【发布时间】:2018-08-23 00:22:08
【问题描述】:

我想测试我的项目的创建方法,但是这个创建方法在我的表单中有 3 个步骤,我想测试所有步骤。要测试每个步骤,我需要发送一个带有相应步骤参数的创建请求。

问题是:我在每一步都重复了很多参数,我想知道如何将常用参数放在一个方法中,然后调用它。

这是我的 rspec 文件

require 'rails_helper'

RSpec.describe Api::MenteeApplicationsController, type: :controller do
    describe "Api Mentee Application controller tests" do
        let(:edition) { create(:edition) }

        it 'should start create a Mentee Application, step 1' do
            edition
            post :create, application: {
                first_name: "Mentee", last_name: "Rspec", email: "mentee@email.com",
                gender: "female", country: "IN", program_country: "IN",
                time_zone: "5 - Mumbai", communicating_in_english: "true",
                send_to_mentor_confirmed: "true",
                time_availability: 3,
                previous_programming_experience: "false" },
                step: "1", steps: "3"

            expect(response).to have_http_status(200)
        end

        it 'should continue to create a Mentee Application, step 2' do
            post :create, application: {
                first_name: "Mentee", last_name: "Rspec", email: "mentee@email.com",
                gender: "female", country: "IN", program_country: "IN",
                time_zone: "5 - Mumbai", communicating_in_english: "true",
                send_to_mentor_confirmed: "true",
                time_availability: 3,
                motivation: "Motivation",
                background: "Background",
                team_work_experience: "Team Work Experience",
                previous_programming_experience: "false" },
                step: "2", steps: "3"

            expect(response).to have_http_status(200)
        end

        it 'should not create a Mentee Application in api format' do
            applications = MenteeApplication.count
            post :create, application: {
                first_name: "Mentee", last_name: "Rspec", email: "mentee@email.com",
                gender: "female", country: "IN", program_country: "IN",
                time_zone: "5 - Mumbai", communicating_in_english: "true",
                send_to_mentor_confirmed: "true",
                motivation: "Motivation",
                background: "Background",
                team_work_experience: "Team Work Experience",
                previous_programming_experience: "false", experience: "",
                operating_system: "mac_os",
                project_proposal: "Project Proposal",
                roadmap: "Roadmap",
                time_availability: 3,
                engagements: ["master_student", "part_time", "volunteer", "one_project"] },
            step: "3", steps: "3"

            expect(response).to have_http_status(:unprocessable_entity)
            expect(MenteeApplication.count).to be(0)
        end

        it 'should create a Mentee Application in api format (step 3)' do
            applications = MenteeApplication.count
            post :create, application: {
                first_name: "Mentee", last_name: "Rspec", email: "mentee@email.com",
                gender: "female", country: "IN", program_country: "IN",
                time_zone: "5 - Mumbai", communicating_in_english: "true",
                send_to_mentor_confirmed: "true",
                motivation: "Motivation",
                background: "Background",
                programming_language: "ruby",
                team_work_experience: "Team Work Experience",
                previous_programming_experience: "false", experience: "",
                operating_system: "mac_os",
                project_proposal: "Project Proposal",
                roadmap: "Roadmap",
                time_availability: 3,
                engagements: ["master_student", "part_time", "volunteer", "one_project"] },
            step: "3", steps: "3"

            expect(response).to have_http_status(200)
            expect(MenteeApplication.count).to be(applications+1)
            expect(flash[:notice]).to eq("Thank you for your application!")
        end

    end
end

如您所见,第 1 步中的参数在第 2 步和第 3 步中使用,所以我的想法是这样的:

def some_params
    params.require(:application).permit(first_name: "Mentee", last_name: "Rspec", email: "mentee@email.com",
            gender: "female", country: "IN", program_country: "IN",
            time_zone: "5 - Mumbai", communicating_in_english: "true",
            send_to_mentor_confirmed: "true",
            time_availability: 3,
            previous_programming_experience: "false")
end

但是没有用,我该怎么做?

【问题讨论】:

    标签: ruby-on-rails ruby testing rspec rspec-rails


    【解决方案1】:

    let 块允许您定义在测试用例中使用的变量 (its)。需要注意的一些关键点:

    • 它们被延迟评估:在您调用变量之前,块中的代码不会运行(除非您使用 bang -- let! -- 这会强制评估)
    • 它们可能会在内部contexts 中被覆盖

    前往RSpec docs 了解更多信息。


    您提供的代码可以像这样使用lets:

    require 'rails_helper'
    
    RSpec.describe Api::MenteeApplicationsController, type: :controller do
        describe "Api Mentee Application controller tests" do
            let(:edition) { create(:edition) }
            let(:first_step_params) do
              {
                first_name: 'Mentee',
                last_name: 'Rspec',
                #...
                previous_programming_experience: false,
              }
            end
            let(:second_step_params) do
              {
                motivation: "Motivation",
                background: "Background",
                team_work_experience: "Team Work Experience",
              }.merge(first_step_params)
            end
            let(:third_step_params) do
              {
                operating_system: "mac_os",
                project_proposal: "Project Proposal",
                roadmap: "Roadmap",
                time_availability: 3,
                engagements: ["master_student", "part_time", "volunteer", "one_project"],
              }.merge(third_step_params)
            end
    
            it 'should start create a Mentee Application, step 1' do
                edition                                                          
    
                post :create, application: first_step_params, step: "1", steps: "3"
    
                expect(response).to have_http_status(200)                        
            end                                                                  
    
            it 'should continue to create a Mentee Application, step 2' do       
                post :create, application: second_step_params, step: "2", steps: "3"
    
                expect(response).to have_http_status(200)                        
            end
    
            it 'should not create a Mentee Application in api format' do
                applications = MenteeApplication.count
    
                post :create, application: third_step_params, step: "3", steps: "3"
    
                expect(response).to have_http_status(:unprocessable_entity)
                expect(MenteeApplication.count).to be(0)
            end
        end
    end
    

    其他建议

    1。不要实现控制器规范

    控制器旨在成为用户界面和后台服务之间的薄软件层。他们的测试很难被承认为集成(端到端)或单元测试。

    我建议您改为实施功能规范。 (capybara 非常适合使用 RSpec 进行 Rails 测试)

    这个blog post 可能会提供更多关于此的见解。

    2。不要在你的测试用例描述中使用 should

    betterspecs.org

    3。注意

    中的最后一个逗号
    let(:application_params) do                                                      
      {                                                                  
        first_name: 'Mentee',                                            
        last_name: 'Rspec',                                              
        #...                          
        previous_programming_experience: false,
      }                                                                  
    end
    

    它阻止incidental changes

    4。使用 .rspec 文件

    内容如

    --require rails_helper
    

    因此您不需要在每个规范文件的顶部使用require 'rails_helper'

    5。使用contexts

    这也是来自 betterspecs.org 的指导。你可以做类似的事情

    RSpec.describe Api::MenteeApplicationsController, type: :controller do
        describe "Api Mentee Application controller tests" do
            let(:edition) { create(:edition) }
            let(:application_params) do
              {
                #...
              }
            end
            let(:step) { 1 }
    
            it 'should start create a Mentee Application' do
                edition
    
                post :create, application: application_params, step: step, steps: "3"
    
                expect(response).to have_http_status(200)
            end
    
            context 'in second step' do
              let(:step) { 2 }
    
              it 'should continue to create a Mentee Application' do
                  post :create, application: application_params, step: step, steps: "3"
    
                  expect(response).to have_http_status(200)
              end
            end
        end
    end
    

    contexts 也可以方便地处理额外的参数:

    RSpec.describe Api::MenteeApplicationsController, type: :controller do
      describe "Api Mentee Application controller tests" do
        let(:edition) { create(:edition) }
        let(:application_params) do
          common_params.merge(additional_params)
        end
        let(:commom_params) do
          {
            #...
          }
        end
        let(:additional_params) { {} }
    
        it 'creates an application' do
          post :create, application: application_params
        end
    
        context 'with API params' do
          let(:additional_params) do
            {
              #...
            }
          end
    
          it 'creates an application' do
            post :create, application: application_params
          end
        end
      end
    end
    

    请注意,post 方法调用在两种上下文中变得完全相同。这将允许重用它(在before 块甚至另一个let 块中)。

    【讨论】:

    • 这不提供新的应用程序参数。
    • 谢谢@JohanWentholt。我已经更新了答案。你怎么看?
    • 我不想成为讨厌的人,但是您已经添加了第 3 步参数,但仍然忽略了第 2 步。
    • @MatheusSantana 这适用于第 1 步,但我无法使其适用于第 2 步和第 3 步。第 2 步的代码:post :create, application: application_params { motivation: "Motivation", background: "Background", team_work_experience: "Team Work Experience" }, step: "2", steps: "3"
    • 最后要记住的最后一件事是与前面的参数合并,它会覆盖您正在创建的哈希。这意味着如果您有两个冲突的键,则选择前一个参数的值。如果您不希望这种行为,您必须更改顺序并在以前的参数上调用 #merge,提供新添加的内容。
    【解决方案2】:

    我想我会很想像下面那样做。本质上:

    1. 创建一个名为 @full_application 的记忆变量并将其包装在一个方法中(我已在测试底部完成此操作)。

    2. 创建常量,规定每个测试所需的值的子集,例如 STEP_ONE_PARAMSSTEP_TWO_PARAMS 等。

    3. 在每个it 块中,使用.slice 和上面定义的常量从full_application 中“获取”您要使用的值。

    类似这样的:

    require 'rails_helper'
    
    RSpec.describe Api::MenteeApplicationsController, type: :controller do
    
      STEP_ONE_PARAMS = %w(
        first_name
        last_name
        email
        gender
        country
        communicating_in_english
        send_to_mentor_confirmed
        time_availability
        previous_programming_experience
      ).freeze
    
      STEP_TWO_PARAMS = STEP_ONE_PARAMS.dup.concat(%w(
        motivation
        background
        team_work_experience
      )).freeze
    
      STEP_THREE_PARAMS = STEP_TWO_PARAMS.dup.concat(%w(
        operating_system
        project_proposal
        roadmap
        engagements
      )).freeze
    
        describe "Api Mentee Application controller tests" do
            let(:edition) { create(:edition) }
    
            it 'should start create a Mentee Application, step 1' do
                edition
                post :create, application: full_application.slice(*STEP_ONE_PARAMS),
                    step: "1", steps: "3"
    
                expect(response).to have_http_status(200)
            end
    
            it 'should continue to create a Mentee Application, step 2' do
                post :create, application: full_application.slice(*STEP_TWO_PARAMS),
                    step: "2", steps: "3"
    
                expect(response).to have_http_status(200)
            end
    
            it 'should not create a Mentee Application in api format' do
                applications = MenteeApplication.count
                post :create, application: full_application.slice(*STEP_THREE_PARAMS),
                step: "3", steps: "3"
    
                expect(response).to have_http_status(:unprocessable_entity)
                expect(MenteeApplication.count).to be(0)
            end
    
            it 'should create a Mentee Application in api format (step 3)' do
                applications = MenteeApplication.count
                post :create, application: full_application,
                step: "3", steps: "3"
    
                expect(response).to have_http_status(200)
                expect(MenteeApplication.count).to be(applications+1)
                expect(flash[:notice]).to eq("Thank you for your application!")
            end
    
        end
    end
    
    
    def full_application
      @full_application ||= {
        first_name:                       "Mentee", 
        last_name:                        "Rspec", 
        email:                            "mentee@email.com",
        gender:                           "female", 
        country:                          "IN", 
        program_country:                  "IN",
        time_zone:                        "5 - Mumbai", 
        communicating_in_english:         "true",
        send_to_mentor_confirmed:         "true",
        motivation:                       "Motivation",
        background:                       "Background",
        programming_language:             "ruby",
        team_work_experience:             "Team Work Experience",
        previous_programming_experience:  "false", 
        experience:                       "",
        operating_system:                 "mac_os",
        project_proposal:                 "Project Proposal",
        roadmap:                          "Roadmap",
        time_availability:                3,
        engagements: [
          "master_student", 
          "part_time", 
          "volunteer", 
          "one_project"
        ] 
      }
    end
    

    【讨论】:

    • 为什么使用arr1.dup.concat(arr2) 而不是arr1 + arr2?或者你不知道数组上的+ 方法?
    • 哦,哇!数组上有一个+ 方法?!?顺便说一句,concat+ 不一样。 Here's 一个关于这个话题的问答,虽然那里有很多。在这种特殊情况下,OP 可能会采取任何一种方式而不会产生重大后果。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-22
    相关资源
    最近更新 更多