1

I want to test a create method of my project, but this create method has 3 steps in my form and I want to test all of them. To test each step I need to send a create request with their respective params of the step.

The problem is: I am repeating many params in each step, I want to know how can I put the common params in a method and then just call it.

Here is my rspec file

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: "[email protected]", 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: "[email protected]", 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: "[email protected]", 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: "[email protected]", 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 

As you can see, the params in step 1 are used in steps 2 and 3, so I was thinking in something like this:

def some_params params.require(:application).permit(first_name: "Mentee", last_name: "Rspec", email: "[email protected]", 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 

But didn't work, how can I do that?

2 Answers 2

2

let blocks allow you to define variables for using within the tests cases (its). Some key points to be aware of:

  • They are lazily evaluated: code within the block is not run until you call the variable (unless you use a bang -- let! -- which forces the evaluation)
  • They might be overridden within inner contexts

Head to RSpec docs to know more about them.


The code you provided could make use of lets just like this:

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 

Additional suggestions

1. Do not implement controller specs

Controllers are meant to be a thin software layer between the user interface and background services. Their tests can hardly be acknowledged as integration (end-to-end) nor unit tests.

I'd suggest you to implement feature specs instead. (capybara is a great match for Rails testing with RSpec)

This blog post might provide more insights on this.

2. Do not use should in your test cases descriptions

See betterspecs.org.

3. Mind the last trailing comma in

let(:application_params) do { first_name: 'Mentee', last_name: 'Rspec', #... previous_programming_experience: false, } end 

It prevents incidental changes.

4. Use a .rspec file

With contents such as

--require rails_helper 

So you don't need require 'rails_helper' on top of each spec file.

5. Use contexts

This is also a guidance from betterspecs.org. You could do something like

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 might also be handy for handling additional params:

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 

Note that the post method call became exactly the same in both contexts. This would allow for reusing it (in a before block or even another let block).

Sign up to request clarification or add additional context in comments.

7 Comments

This however doesn't provide the new application params.
Thanks @JohanWentholt. I've updated the answer. What do you think?
I don't want to be the annoying one, but you've added the step 3 params, but still left out step 2.
@MatheusSantana this works for step 1 but I can't make it work for step 2 and 3. Code of step 2: post :create, application: application_params { motivation: "Motivation", background: "Background", team_work_experience: "Team Work Experience" }, step: "2", steps: "3"
One last thing to keep in mind by having the merge with the previous params at the end, is that it overwrites the hash you're creating. This means if you have two conflicting keys the value of the previous params is chosen. If you don't want this behavior you have to change the order and call #merge on the previous params, supplying the new additions.
|
0

I think I would be tempted to do it something like below. Essentially:

  1. Create a memoized variable called @full_application and wrap it in a method (I've done this at the bottom of the test).

  2. Create constants stipulating the subsets of the values that you want for each test, such as STEP_ONE_PARAMS, STEP_TWO_PARAMS, etc.

  3. In each it block, use .slice and the constants defined above to "grab" the values from full_application that you want to use.

Something like this:

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: "[email protected]", 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 

2 Comments

Why use arr1.dup.concat(arr2) over arr1 + arr2? Or did you not know about the + method on arrays?
Oh, wow! There's a + method on arrays?!? BTW, concat and + are not the same. Here's one Q&A on the topic, although there are a lot of them out there. In this particular instance, the OP could probably go either way without great consequence.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.