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.
Related
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'))
I have this rspec code:
let(:valid_attributes) {
{name: "Sample Product"}
}
describe "#index" do
it "should give a collection of products" do
product = Product.create! valid_attributes
get :index, :format => :json
expect(response.status).to eq(200)
expect(response).to render_template("api/products/index")
expect(assigns(:products)).to eq([product])
end
end
And it controller:
def index
#products = Product.all
end
But the controller code still doesn't satisfy the spec. What is wrong here.
Here is the failure message:
Failures:
1) Api::ProductsController#index should give a collection of
products
Failure/Error: expect(assigns(:products)).to eq([product])
expected: [#<Product id: 3, name: "Sample Product", created_at: "2016-06-15 05:10:50", updated_at: "2016-06-15 05:10:50">]
got: nil
(compared using ==)
# ./spec/controllers/api/products_controller_spec.rb:53:in `block (3 levels) in <top (required)>'
Finished in 0.05106 seconds (files took 1.89 seconds to load) 1
example, 1 failure
You have:
get :index, :format => :json
I assume you should have:
get :index, :format => :html
By default, rails returns html, and you didn't specify otherwise in your index action.
assign checks that an instance variable was set
and products should be created in database (use let!(with bang) for it), then:
in your controller:
def index
#products = Product.all # `#products` should be present
end
in rspec:
let(:valid_attributes) {{ name: 'Sample Product' }}
let!(:product) { Product.create!(valid_attributes) } # use `!` here
describe "#index" do
before { get :index, format :json }
it 'should give a collection of products' do
expect(assigns(:products)).to eq([product])
end
end
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
I am receiving the following errors for my Rspec test in rails
1) MoviesController searching for similar movies should find the similar movies by director
Failure/Error: get :similar, {:id => "1"}
ActiveRecord::RecordNotFound:
Couldn't find Movie with id=1
# ./app/controllers/movies_controller.rb:63:in `similar'
# ./spec/controllers/movies_controller_spec.rb:16: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"}
ActiveRecord::RecordNotFound:
Couldn't find Movie with id=1
# ./app/controllers/movies_controller.rb:63:in `similar'
# ./spec/controllers/movies_controller_spec.rb:22: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"}
ActiveRecord::RecordNotFound:
Couldn't find Movie with id=1
# ./app/controllers/movies_controller.rb:63:in `similar'
# ./spec/controllers/movies_controller_spec.rb:29:in `block (3 levels) in <top (required)>'
And this is my controller method that is causing it to fail:
def similar
#movie = Movie.find(params[:id])
#movies = Movie.find_all_by_director(Movie.find_by_id(params[:id])[:director])
if #movies.count <= 1
redirect_to movies_path
flash[:notice] = "'#{#movie.title}' has no director info"
end
I can't understand why this test keeps failing with the same error. Any help would be much appreciated
Below are the tests
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_all_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_all_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_all_by_director).with(#fake_movie.director).and_return(#fake_movies)
get :similar, {:id => "1"}
assigns(:movies).should == #fake_results
end
end
end
FactoryGirl is setup as follows:
FactoryGirl.define do
factory :movie do
title 'Star Wars'
director 'George Lucas'
end
end
The spec calls Factory.build. That does not persist the object to the database. You'll want to use Factory.create to allow Movie.find to work.
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".