How to expect a Params hash in RSpec in Rails 5? - ruby-on-rails

I'm upgrading to Rails 5, which has broken my RSpec even though I'm passing the data I should be.
The problem is obviously here:
expected: ({"name"=>"MyString"})
got: (<ActionController::Parameters {"name"=>"MyString"} permitted: true>)
Which means I need to be able to fix my controller assertion so that it expects the latter. This is the line that needs changing.
expect_any_instance_of(Hospital).to receive(:update).with({ "name" => "MyString" })
Probably to something like this
expect_any_instance_of(Hospital).to receive(:update).with(params: { "name" => "MyString" }, permitted: true)
I just don't know what the syntax is, and can't find it anywhere in the scattered documentation for Rails 5, or non existent notes/Stack Overflow questions concerning RSpec on Rails 5.
Full error and controller spec
2) HospitalsController PUT update with valid params updates the requested hospital
Failure/Error: if #hospital.update(hospital_params)
#<Hospital id: 43, name: "MyString", reference_code: "RefCod", image_file_name: nil, image_content_type: nil, image_file_size: nil, image_updated_at: nil, contact_phone: "+61-000-000-000", website_link: "www.example.com", street_number: "01", street: "Somewhere St", suburb: "Suburb", state: "ACT", postcode: "1111", description: "MyText MyText MyText MyText MyText MyText MyText M...", areas_of_specialization: "MyText MyText MyText MyText MyText MyText MyText M...", created_at: "2016-07-24 22:28:24", updated_at: "2016-07-24 22:28:24"> received :update with unexpected arguments
expected: ({"name"=>"MyString"})
got: (<ActionController::Parameters {"name"=>"MyString"} permitted: true>)
Diff:
## -1,2 +1,2 ##
-[{"name"=>"MyString"}]
+[<ActionController::Parameters {"name"=>"MyString"} permitted: true>]
# ./app/controllers/hospitals_controller.rb:54:in `block in update'
Controller spec method
describe "PUT update" do
describe "with valid params" do
it "updates the requested hospital" do
hospital = Hospital.create! valid_attributes
# Assuming there are no other hospitals in the database, this
# specifies that the Hospital created on the previous line
# receives the :update_attributes message with whatever params are
# submitted in the request.
expect_any_instance_of(Hospital).to receive(:update).with({ "name" => "MyString" })
put :update, {:id => hospital.to_param, :hospital => { "name" => "MyString" }}, valid_session
end
it "assigns the requested hospital as #hospital" do
hospital = Hospital.create! valid_attributes
put :update, {:id => hospital.to_param, :hospital => valid_attributes}, valid_session
expect(assigns(:hospital)).to eq(hospital)
end
it "redirects to the hospital" do
hospital = Hospital.create! valid_attributes
put :update, {:id => hospital.to_param, :hospital => valid_attributes}, valid_session
expect(response).to redirect_to(hospital)
end
end
...etc

Have you tried just using a ActionController::Parameters object as the value you're expecting?
As in:
expect_any_instance_of(Hospital).to receive(:update).with(ActionController::Parameters.new('name':'MyString'))

Related

In rspec file, I am getting => Error: ActionController::RoutingError: No route matches xxxxxxx despite valid routing

I am very very new to testing and new to rails (less than 1 year of experience). Please keep this in view before answering.
I have a model recipe which belongs to a source and source belongs to client.
Route is:
client_source_recipes GET /clients/:client_id/sources/:source_id/recipes(.:format) recipes#index
I am trying to test this:
RSpec.describe RecipesController, :type => :controller do
# Prerequisites go here (mentioned at the end of the question)
describe "GET #index" do
it "assigns all recipes as #recipes" do
recipe = Recipe.create! valid_attributes
get :index, params: { client_id: client.id, source_id: source.id, locale: 'cs' }, session: valid_session
expect(assigns(:recipes)).to eq([recipe])
# Commented below are different ways I tried but failed:
# visit client_source_recipes_path(client.id, source.id, locale: 'cs')
# visit "/client/#{client.id}/source/#{source.id}/recipes?locale=cs"
# get client_source_recipes_path, params: { client_id: client.id, source_id: source.id, locale: 'cs' }, session: valid_session
# get client_source_recipes_path(client.id, source.id, locale: 'cs')
end
end
end
Prerequisites for test:
login_user # defined - works well
let(:client) { create :client } # defined in factories
let(:source) { create :source, warehouse_id: 1, client_id: client.id } # defined in factories
let(:valid_attributes) {
{ name: "name", source_id: source.id }
}
let(:valid_session) { {"warden.user.user.key" => session["warden.user.user.key"]} } # works well in other tests
Why do I get the error of route when same route is being used everywhere else?
Errors:
Error: ActionController::RoutingError: No route matches {:controller=>"recipes", :params=>{:client_id=>"1", :source_id=>"1", :locale=>"cs"}, :session=>{"warden.user.user.key"=>[["1"], "$2a$04$bobnhLAlKEsxZtlJheY64."]}}
Error: ActionController::RoutingError: No route matches {:controller=>"recipes", :action=>"/clients/1/sources/1/recipes?locale=cs"}
Error: ActionController::RoutingError: No route matches {:controller=>"recipes", :action=>"/clients/1/sources/1/recipes"}
# etc. etc. i.e. errors are more or less the same
Thanks in advance.
OK!
I have 'spent' like 5 hours pondering on this but could not do it in any way and now I found out that the only issue was syntax:
Wrong (old) syntax (expects params and session as arguments explicitly):
get :index, { client_id: client.id, source_id: source.id, locale: 'cs' }, session: valid_session
#or
get :index, params: { client_id: client.id, source_id: source.id, locale: 'cs' }, session: valid_session
Right (newer) syntax (accepts params and session as arguments implicitly):
get :index, { client_id: client.id, source_id: source.id, locale: 'cs' }, valid_session
I hope it saves precious time of other people, unlike me ):

Strange missing required key in RSpec controller test for nested resource

Testing an Rails Controller (for an API) in RSpec for a nested resource (orders has_many order_actions):
it "returns status OK" do
order_action = create(:order_action)
order_action.update_attribute(:order_id, #order.id)
put :update, {:id => order_action.id, :order_id => #order.id, :order_action => valid_attributes}.merge(valid_session)
expect(response.code).to eql("200")
end
(valid_attributes is just build(:order_action).attributes. valid_session is {:user_email => #user.email, :user_token => #user.authentication_token, :format => 'json'}.)
I get an annoying error missing required keys: [:order_id]. Annoying because it is, as you can see, explicitly defined above. Twice. Here's the error text:
ActionController::UrlGenerationError:
No route matches {:action=>"show", :controller=>"api/v1/order_actions", :format=>nil, :id=>#<OrderAction id: 19, order_id: 3, created_at: "2015-03-13 15:52:13", updated_at: "2015-03-13 15:52:13">, :order_id=>nil} missing required keys: [:order_id]
And the relevant failed test report
rspec ./spec/controllers/order_actions_controller_spec.rb:151 # Api::V1::OrderActionsController PUT #update with valid params returns status OK
Notice two errors: :order_id and :format are nil.
Now, things get even weirder if you run the exact same test alone:
$ rspec spec/controllers/order_actions_controller_spec.rb:151
Run options: include {:locations=>{"./spec/controllers/order_actions_controller_spec.rb"=>[151]}}
.
Finished in 0.16575 seconds (files took 3.62 seconds to load)
1 example, 0 failures
The test passes! I'm losing my mind. Help?
Additional Info
#order and #user creation at the top of the order_actions_controller_spec.rb:
before(:each) do
#user = create(:user)
#order = create(:order)
end
Order and User factories:
a_user = User.new({:email => "#{SecureRandom.hex(3)}#rakuten.com", :password => "!abc123%##", :password_confirmation => "!abc123%##"})
FactoryGirl.define do
factory :order do
user a_user
class_code "MyText"
class_name "MyString"
complete false
end
end
and
FactoryGirl.define do
factory :user do
email "#{SecureRandom.hex(3)}#email.com"
password "!abc123%##"
password_confirmation { "!abc123%##" }
end
end

RSpec generated test what does "assigns the requested experience_level as #experience_level" mean?

I'm trying get this spec to pass, but I don't know what it means. Here is the full spec. The second example is the one that is failing.
describe "PUT update" do
describe "with valid params" do
it "updates the requested experience_level" do
experience_level = ExperienceLevel.create! valid_attributes
# Assuming there are no other experience_levels in the database, this
# specifies that the ExperienceLevel created on the previous line
# receives the :update_attributes message with whatever params are
# submitted in the request.
ExperienceLevel.any_instance.should_receive(:update_attributes).with({ "name" => "MyString" })
put :update, {:id => experience_level.to_param, :experience_level => { "name" => "MyString" }}
end
it "assigns the requested experience_level as #experience_level" do
experience_level = ExperienceLevel.create!(name: 'test'), valid_attributes
put :update, {:id => experience_level.to_param, :experience_level => valid_attributes}
assigns(:experience_level).should eq(experience_level)
end
it "redirects to the experience_level" do
experience_level = ExperienceLevel.create! valid_attributes
put :update, {:id => experience_level.to_param, :experience_level => valid_attributes}
response.should redirect_to(experience_level)
end
end
Here is the message in the terminal:
1) ExperienceLevelsController PUT update with valid params assigns the requested experience_level as #experience_level
Failure/Error: assigns(:experience_level).should eq(experience_level)
expected: [#<ExperienceLevel id: 1, name: "test", description: nil, created_at: "2013-10-10 20:40:05", updated_at: "2013-10-10 20:40:05">, {"name"=>"MyString"}]
got: #<ExperienceLevel id: 1, name: "MyString", description: nil, created_at: "2013-10-10 20:40:05", updated_at: "2013-10-10 20:40:05">
(compared using ==)
Diff:
## -1,3 +1,2 ##
-[#<ExperienceLevel id: 1, name: "test", description: nil, created_at: "2013-10-10 20:40:05", updated_at: "2013-10-10 20:40:05">,
- {"name"=>"MyString"}]
+#<ExperienceLevel id: 1, name: "MyString", description: nil, created_at: "2013-10-10 20:40:05", updated_at: "2013-10-10 20:40:05">
# ./spec/controllers/experience_levels_controller_spec.rb:100:in `block (4 levels) in <top (required)>'
The following statement in your second example:
experience_level = ExperienceLevel.create!(name: 'test'), valid_attributes
is the same as:
experience_level = [ExperienceLevel.create!(name: 'test'), valid_attributes]
In other words, it's creating an array from the two comma separated values on the right hand side of the assignment operator and assigning that array to experience_level. This is at least one reason why your test is failing.

RoR - rspec tests failing and shouldn't

I'm running these rspec tests for my controller:
require 'spec_helper'
describe MoviesController do
describe 'searching for similar movies' do
before :each do
#fake_movies = [mock('Movie'), mock('Movie')]
#fake_movie = FactoryGirl.build(:movie, :id => "1", :title => "Star Wars", :director => "George Lucas")
end
it 'should follow the route to the similar movies by director page' do
assert_routing('movies/1/similar', {:controller => 'movies', :action => 'similar', :id => '1'})
end
it 'should find the similar movies by director' do
Movie.should_receive(:find_by_id).with("1").and_return(#fake_movie)
Movie.should_receive(:find_by_director).with(#fake_movie.director).and_return(#fake_movies)
get :similar, {:id => "1"}
end
it 'should select the Similiar Movies template for rendering' do
Movie.should_receive(:find_by_id).with("1").and_return(#fake_movie)
Movie.should_receive(:find_by_director).with(#fake_movie.director).and_return(#fake_movies)
get :similar, {:id => "1"}
response.should render_template('similar')
end
it 'it should make the results available to the template' do
Movie.should_receive(:find_by_id).with("1").and_return(#fake_movie)
Movie.should_receive(:find_by_director).with(#fake_movie.director).and_return(#fake_movies)
get :similar, {:id => "1"}
assigns(:movies).should == #fake_results
end
end
end
Buy they are failing with this output:
Failures:
1) MoviesController searching for similar movies should find the similar movies by director
Failure/Error: get :similar, {:id => "1"}
<Movie(id: integer, title: string, rating: string, description: text, release_date: datetime, created_at: datetime, updated_at: datetime, director: string) (class)> received :find_by_director with unexpected arguments
expected: ("George Lucas")
got: ()
# ./app/controllers/movies_controller.rb:62:in `similar'
# ./spec/controllers/movies_controller_spec.rb:17:in `block (3 levels) in <top (required)>'
2) MoviesController searching for similar movies should select the Similiar Movies template for rendering
Failure/Error: get :similar, {:id => "1"}
<Movie(id: integer, title: string, rating: string, description: text, release_date: datetime, created_at: datetime, updated_at: datetime, director: string) (class)> received :find_by_director with unexpected arguments
expected: ("George Lucas")
got: ()
# ./app/controllers/movies_controller.rb:62:in `similar'
# ./spec/controllers/movies_controller_spec.rb:23:in `block (3 levels) in <top (required)>'
3) MoviesController searching for similar movies it should make the results available to the template
Failure/Error: get :similar, {:id => "1"}
<Movie(id: integer, title: string, rating: string, description: text, release_date: datetime, created_at: datetime, updated_at: datetime, director: string) (class)> received :find_by_director with unexpected arguments
expected: ("George Lucas")
got: ()
# ./app/controllers/movies_controller.rb:62:in `similar'
# ./spec/controllers/movies_controller_spec.rb:30:in `block (3 levels) in <top (required)>'
Finished in 0.15517 seconds
4 examples, 3 failures
Failed examples:
rspec ./spec/controllers/movies_controller_spec.rb:14 # MoviesController searching for similar movies should find the similar movies by director
rspec ./spec/controllers/movies_controller_spec.rb:20 # MoviesController searching for similar movies should select the Similiar Movies template for rendering
rspec ./spec/controllers/movies_controller_spec.rb:27 # MoviesController searching for similar movies it should make the results available to the template
When this is my controller method:
def similar
#movies = Movie.find_by_director(Movie.find_by_id(params[:id]))
end
I do not understand why these tests are failing.
The problem was I was calling the wrong method. find_by_director isn't the method that was supposed to be used, but find_all_by_director, hence what was going to the method was wrong.
You need to first give the request with proper method and then test for the expected result.
Place the below line in the beginning of the failed examples.
get :similar, {:id => "1"}
Like below.
it 'should find the similar movies by director' do
get :similar, {:id => "1"}
Movie.should_receive(:find_by_id).with("1").and_return(#fake_movie)
Movie.should_receive(:find_by_director).with(#fake_movie.director).and_return(#fake_movies)
end
I suggest you to get rid of mocking and work with "real" objects that persist in DB:
describe MoviesController do
describe 'searching for similar movies' do
before do
#movie = FactoryGirl.create(:movie,
:title => "Star Wars",
:director => "George Lucas")
#another_lucas_movie = FactoryGirl.create(:movie,
:title => "Indiana Jones and the Last Crusade",
:director => "George Lucas")
end
it 'should find the similar movies by director' do
get :similar, {:id => #movie.id}
assigns(:movies).should include #another_lucas_movie
end
end
end
See RSpec docs for more details.
Use FactoryGirl.create instead of FactoryGirl.build
.build acts as Model.new You have to save your test records to database.

How to post to create with spec helper method in controller spec?

I'm relatively new to programming, Rails, Ruby, Rspec, and the like, so thanks for your help!
My specs were very repetitive, so I wrote some spec helper methods. I can't figure out how to properly use them in my specs. Specifically, I have a users controller with create:
def create
#user = User.new(params[:user])
if #user.save
redirect_to user_path(#user)
else
render :action => :new
end
end
A bit in the spec helper that creates a valid user:
def valid_user_eilif
#test_image = Rails.root + "spec/fixtures/images/seagull.jpg"
#file = Rack::Test::UploadedFile.new(#test_image, "image/jpeg")
user = User.create!(:username => "eilif", :email => "eilif#email.org",
:image => #file, :bio => "Lots of text that I don't want to write",
:signature_quote => "Yet more text.")
user.save!
user
end
And then in my user controller spec:
before (:each) do
post :create, :user => valid_user_eilif
end
it 'should assign user to #user' do
assigns(:user).should eq(User.last)
end
When I run the spec I get the error:
Failure/Error: assigns(:user).should eq(User.last)
expected #<User id: 1, username: "eilif", email: "eilif#email.org", bio: "Lots of text that I don't want to write", signature_quote: "I feel empty.", image_file_name: "seagull.jpg", image_content_type: "image/jpeg", image_file_size: 10475, image_updated_at: "2011-05-10 23:35:55", created_at: "2011-05-10 23:35:56", updated_at: "2011-05-10 23:35:56">
got #<User id: nil, username: nil, email: nil, bio: nil, signature_quote: nil, image_file_name: nil, image_content_type: nil, image_file_size: nil, image_updated_at: nil, created_at: nil, updated_at: nil>
So, I assume I'm incorrectly posting to create, since nothing is created? What's the proper way to do this?
Ideally controller specs shouldn't depend on the model being able to create a row in the database. With such a simple action you can mock out the dependencies:
describe UsersController do
context "on success" do
before(:each) do
#user = mock_model(User,:save=>true)
User.stub(:new) {#user}
post :create, :user => {}
end
it "redirects" do
response.should redirect_to(user_path(#user))
end
it "assigns" do
assigns[:user].should == #user
end
end
context "on failure" do
it "renders 'new'" do
#user = mock_model(User,:save=>false)
User.stub(:new) {#user}
post :create, :user => {}
response.should render_template "users/new"
end
end
end
Notice that the specs don't pass anything in params[:user]. This helps enforce the MVC separation of concerns, whereby the model is responsible for handling the attributes, ie. validating, setting up associations, etc. You can't always keep controllers this 'skinny', but it's a good idea to try.
It looks like the problem is that #user doesn't get refreshed after the save. Try assigns(:user).reload.should eql(User.last).
But there's another slight problem, and that's probably still going to fail. You shouldn't be calling post with :user => valid_user_eilif; you want the attributes from your user record, not the actual user object itself. And you're essentially creating a new user in valid_user_eilif and then making your controller create that object again -- if you have any kind of unique constraints, you're going to get a conflict.
This is a good place to use something like factory_girl and mocks. For an example, take a look at how one of my projects handles controller specs. This example uses factory_girl, Mocha and shoulda. I'll annotate it with comments below:
describe MembersController, "POST create" do
before do
# Factory Girl - builds a record but doesn't save it
#resource = Factory.build(:member)
# Mocha expectation - overrides the default "new" behavior and makes it
# return our resource from above
Member.expects(:new).with({}).returns(#resource)
# Note how we expect it to be called with an empty hash; that's from the
# `:member` parameter to `post` below.
end
context "success" do
before do
post :create, :member => {}
end
# shoulda matchers - check for a flash message and a redirect
it { should set_the_flash.to(/successfully created/) }
it { should redirect_to(member_path(#resource)) }
end
context "failure" do
before do
# Mocha - To test a failing example in the controller, we override the
# default `save` behavior and make it return false, otherwise it would
# be true
#resource.expects(:save).returns(false)
post :create, :member => {}
end
# shoulda matchers - check for no flash message and re-render the form
it { should_not set_the_flash }
it { should render_template(:new) }
end
end

Resources