Rspec failed with update in controller - ruby-on-rails

Here is the rspec error for update in customers controller:
5) CustomersController GET customer page 'update' should be successful
Failure/Error: put 'update', id => 1, :customer => {:name => 'name changed'}
<Customer(id: integer, name: string, short_name: string, contact: string, address: string, country: string, phone: string, fax: string, email: string, cell:
string, sales_id: integer, test_eng_id: integer, safety_eng_id: integer, web: string, category1_id: integer, category2_id: integer, active: boolean, biz_status: str
ing, input_by_id: integer, quality_system: string, employee_num: string, revenue: string, note: text, user_id: integer, created_at: datetime, updated_at: datetime)
(class)> received :find with unexpected arguments
expected: (1)
got: ("1")
# ./app/controllers/customers_controller.rb:44:in `update'
# ./spec/controllers/customers_controller_spec.rb:41:in `block (3 levels) in <top (required)>'
Here is the rspec code:
it "'update' should be successful" do
customer = mock_model(Customer)
Customer.should_receive(:find).with(1).and_return(customer)
customer.stub(:update_attributes).and_return(true)
put 'update', :id => 1, :customer => {:name => 'name changed'}
response.status.should == 302 #redirect()
end
Here is the update in controller:
def update
#customer = Customer.find(params[:id])
if #customer.update_attributes(params[:customer], :as => :roles_new_update)
if #customer.changed
#message = 'The following info have been changed\n' + #customer.changes.to_s
#subject ='Customer info was changed BY' + session[:user_name]
notify_all_in_sales_eng(#message,#subject)
end
redirect_to session[('page'+session[:page_step].to_s).to_sym], :notice => 'Customer was updated successfaully!'
else
render 'edit', :notice => 'Customer was not updated!'
end
end
Any thoughts? Thanks.

The values posted (and accessed via the params hash) are strings. The easiest way to correct your test is to have Customer.should_receive(:find).with("1").and_return(customer).
Notice we now have "1" (i.e. a String) as the expected argument instead of 1 (a FixNum).

All params are passed through as strings and I believe the find method is doing an implicit conversation to an integer. Either make it explicit using to_i in the controller or change your spec to expect the string "1".

Related

How to expect a Params hash in RSpec in Rails 5?

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'))

Sessions and user_id in rails

I'm having a lot of trouble understanding sessions and authentication with Rails. So basically, I tried to set a session based on user_id, following a tutorial from a book. Here is the create method in my sessions_controller.rb file:
def create
if user = User.authenticate(params[:email], params[:password])
session[:id] = user.id
redirect_to root_path, :notice => 'Logged in successfully'
else
flash.now[:alert] = "Invalid login/password combination"
render :action => 'new'
end
end
But, when I try to define a current_user method in my application_controller.rb file, it asks me to reference the session based on user_id:
def current_user
return unless session[:user_id]
#current_user = User.find_by_id(session[:user_id])
end
Here is what confuses me - user_id is an attribute of each Recipe (equivalent of articles or posts in my app) that creates a has_one relationship with a user. nowhere else in my application is user_id defined. so user_id shouldn't be an attribute of a User, right?
Just to clarify, I've listed the parameters for User objects and Recipe objects from the rails console:
2.0.0-p598 :019 > Recipe
=> Recipe(id: integer, title: string, body: text, published_at:
datetime, created_at: datetime, updated_at: datetime, user_id:
integer, photo_of_recipe_file_name: string,
photo_of_recipe_content_type: string, photo_of_recipe_file_size:
integer, photo_of_recipe_updated_at: datetime)
2.0.0-p598 :020 > User
=> User(id: integer, email: string, hashed_password: string,
created_at: datetime, updated_at: datetime, username: string)
In your create method it should be
session[:user_id] = user.id
You are setting up a key :user_id in the session hash and storing the authenticated user's id user.id in it for later use
Key name can be anything

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.

Rspec not showing the updates on a User

Here is my Rspec when testing an API end point related to Users:
context "updating a user" do
let(:user) { User.create! }
it "should let me update a user without an email" do
put "/api/v1/users/#{user.id}", {:user => {:first_name => 'Willy'}}.to_json, {'CONTENT_TYPE' => 'application/json', 'HTTP_AUTHORIZATION' => "Token token=\"#{auth_token.access_token}\""}
p user.inspect
end
And the controller action that I am testing looks like this:
def update
begin
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
p #user.inspect
render json: #user, :except => [:created_at, :updated_at]
else
render json: { :errors => #user.errors }, :status => :unprocessable_entity
end
rescue ActiveRecord::RecordNotFound
head :not_found
end
end
Surprisingly, the #user.inspect in the controller shows this:
"#<User id: 2, first_name: \"Willy\", last_name: nil, email: nil, state: nil, created_at: \"2013-06-22 11:21:22\", updated_at: \"2013-06-22 11:21:22\">"
And the user.inspect in the rspec, right after the call to the controller has been done, looks like this:
"#<User id: 2, first_name: nil, last_name: nil, email: nil, state: nil, created_at: \"2013-06-22 11:21:22\", updated_at: \"2013-06-22 11:21:22\">"
Why does the Rspec not catch the updates? I mean, I have tested this manually and the database gets updated correctly.
What am I missing here?
In rspec example you define user method with let, which returns ActiveRecord object. Your controller is creating different object, that points to the same database entry. Change in db is not reflected in user object in rspec example, as there is no callback mechanism that would notify it to change.
Using #reload method on AR object in test should solve your problem, as it forces reloading data from db.

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.

Resources