"nil is not a symbol" for model count in rspec matcher - ruby-on-rails

I am trying to write an integration test where if a user clicks on a button, it creates a new record in the database (CheckPrice model).
I am running into the error nil is not a symbol when I try to run my test.
require 'spec_helper'
describe 'CheckPrice', type: :request, js: true do
it "should create a new CheckPrice record when user clicks Check Price on topic page" do
city = create :city
hotel = create :hotel
affiliate_link = create :affiliate_link
visit '/hotel-bilboa-hotel'
sleep 2
click_button "Check Prices"
response.should change(CheckPrice.count).by(1)
end
end
When "Check Prices" is clicked, there is an event listener that triggers the new method in the checkprices_controller.
The error seems to occur on the last line response.should change(CheckPrice.count).by(1). It looks like the method does not recognize the model CheckPrice. How do I reference the CheckPrice table?
Thanks.

I don't think you can use the change matcher like this on the response object. Try this:
expect {
click_button "Check Prices"
}.to change{ CheckPrice.count }.by(1)
This makes more semantic sense, too, IMO.
See this cheat sheet for more examples.

Semantic aside, to answer the original question (getting "nil is not a symbol") and help other people who might land here like I did: make sure to use curly brackets {} instead of parentheses ().
So (correct)
response.should change{CheckPrice.count}.by(1)
response.should change(CheckPrice, :count).by(1)
instead of (won't work, a mix of the 2 above)
response.should change(CheckPrice.count).by(1)
Edit:
Same answer with recommended expect syntax
So (correct)
expect{response}.to change{CheckPrice.count}.by(1)
expect{response}.to change(CheckPrice, :count).by(1)
instead of (won't work, a mix of the 2 above)
expect{response}.to change(CheckPrice.count).by(1)

Another way to do this would be:
expect do
click_button "Check Prices"
end.to change(CheckPrice, :count).by(1)
Which indicates that the output of the count method on CheckPrice is what is supposed to be changing. When two parameters are passed to change, one is assumed to be a receiver, the other a symbol to send.

I ran into the same problem, the case is, as the other answers says, that both the methods expect and change, in this case, expect a block as parameter.
So, in rails, you can use either { } or do end syntaxes.

Related

How to do an RSpec oneliner to test a :count change

we all love oneliners, they are so cool
but they are also so undocumented
how can I do an oneliner of this code (that currently works brilliantly)?
it "deletes the user" do
expect { destroy_user }.to change(User, :count).by(1)
end
how can I do an oneliner of this code
You can't. The one-liner syntax is defined in terms of subject and this test doesn't use one at all. Furthermore, to quote the documentation:
The one-liner syntax only works with non-block expectations (e.g.
expect(obj).to eq, etc) and it cannot be used with block expectations
(e.g. expect { object }).
I think your example is the only way to perform what you want.
Rspec does have one-liner syntax but it's only for when your block has a subject and doesn't seem to support actions like destroy_user.

Rspec / CapyBara how to expect record not to be saved

I am testing a scenario with CapyBara where I expect the record NOT to be valid.
Given "the customer is not an athlete" do
#customer = create(:athlete, :shop=> #shop, :first_name=>"Birglend", :last_name=>"Firglingham", :email=>"birglendfirglingham#gmail.com")
end
Then "I should not see that the customer is an athlete" do
expect(page).not_to have_css('.athlete-row')
end
But before it can get to the "Then", I get an "ActiveRecord::RecordNotSaved" exception. This is caused by a before_save callback that I have to check if the customer is an athlete. If they're not an athlete, the method returns false and I do not want the record to be saved.e.g.
before_save :check_athlete_status
def check_athlete_status
#return false unless self is an athlete
end
But I have a feeling this is not the correct way because this is an expected scenario and should not be throwing exceptions. How do I handle this?
You're trying to mix what a model test and a feature test do here.
There are a couple ways to test this.
I am going to drop the Given/Then syntax because I don't have any experience using that gem.
1) A model spec:
RSpec.describe Athlete, type: :model do
context 'with invalid status' do
let(:athlete) { FactoryGirl.build(:athlete, :shop=> #shop, :first_name=>"Birglend", :last_name=>"Firglingham", :email=>"birglendfirglingham#gmail.com") }
it "raises an exception" do
expect { athlete.save }.to raise_exception(ActiveRecord::RecordNotSaved)
end
end
end
2) You can also change the feature test to do whatever your Given is doing via the interface:
feature 'Admin creates athlete' do
given(:athlete) { FactoryGirl.build(:athlete, :shop=> #shop, :first_name=>"Birglend", :last_name=>"Firglingham", :email=>"birglendfirglingham#gmail.com") }
scenario 'with invalid status' do
# We're testing that your custom callback stops saving the record.
visit '/athletes/new'
select #shop.title, from: 'Shop'
fill_in 'First name', with: 'Birglend'
fill_in 'Last name', with: 'Firglingham'
fill_in 'Email', with: 'birglendfirglingham#gmail.com'
select 'Not an athlete' from: 'Status'
click_button 'Create Athlete'
expect(page).to have_content 'There was an error creating the athlete'
# I'd argue that these next lines, what you're trying to test, really shouldn't be tested here. What you should be testing is that the form provides feedback that the athlete record wasn't created.
click_link 'Athletes'
expect(page).not_to have_css('.athlete-row')
end
end
I don't know what your interface is supposed to be doing, so I just made something up above. Hope that helps you go down the right path.
I think you should just use validations rather than a before_save. You generally want to avoid before and after create/save callbacks unless you really need them. That said you can expect the call to raise an exception if that is the desired behavior.

rspec expect redirect_to fail

Sometimes new (very DRY) rspec syntax makes me crazy...
Rspec v 2.14.1
describe "POST create" do
subject { post :create, contractor: valid_params }
context "by user" do
before { sign_in #legal.user }
it "contractor successful created" do
expect { subject }.to redirect_to(contractor_path(assigns(:contractor).id))
I have error & question here:
NoMethodError: # :contractor variable not defined
undefined method `id' for nil:NilClass
It seems that expect take an operator before controller method post executes, because I try to raise this method.
My code:
def create
#contractor = Contractor.restrict!(current_accreditation).new(permitted_params) # TODO move to the IR::Base
if #contractor.save
current_accreditation = #contractor.create_legal!(user: current_user) # TODO legal create
redirect_to(#contractor)
else
render(:new)
end
end
Secondly, why I have an error when try
expect(subject).to ...
Why {} works, but () no? In relish docs this method work great: https://www.relishapp.com/rspec/rspec-rails/docs/matchers/redirect-to-matcher
Kinda unrelated but I've found the following helpful:
Use expect {} when you want to test before/after of whatever's in the block. eg. expect { subject } to change(User, :count) - you want to check the count before, then after, to work out the difference and see if it actually changed.
Use expect () to verify the outcome of a block, eg. that a redirect occurred, or something got assigned, etc.
Your test with the assigns(:contractor) doesn't work because you're using the {} notation - so it's trying to work out the assigns(:contractor).id both before and after evaluating the subject (and of course, before the subject, it doesn't exist).

Rails RSpec: Controller Testing, checking if errors Array of model is filled with entries if new record cannot be created due to validation error

I have a still pretty simple Rails application that I want to develop using BDD with Cucumber and TDD with RSpec. Currently, I am hanging at a test where I want to check that if a new instance of an Organizer (that's the model I have) cannot be created due to a validation error. I would like to check that the errors Array of the object to be created is not empty so that I can be sure that error messages are available for showing them in the view.
require 'spec_helper'
describe OrganizersController do
render_views
describe "POST 'create'" do
describe "with invalid arguments" do
before(:each) do
request.env["HTTP_REFERER"] = organizers_new_path
#organizer_args = { :name => "" }
end
it "should return a non-empty list of errors" do
post 'create', :organizer => #organizer_args
#organizer.errors.empty?.should_not be_true
end
end
end
end
I am developing based on Rails 3.2.9 with RSpec 2 and cucumber-rails.
Any suggestions are appreciated. Thanks!
You should use assigns method to get instance variable from controller action:
assigns(:organizer).errors.empty?.should_not be_true
The latest preferred syntax is:
expect(assigns(:organizer).errors.empty?).to_not be_true
thanks for the answer guys but I'd like to suggest a slightly nicer syntax:
expect(assigns(:organizer).errors).to_not be_empty
(unrelated to the question 👇)
Basically whenever you have a method that ends with ? you'll have the corresponding rspec matcher that starts with be_ e.g.
1.odd? #=> true
expect(1).to be_odd

RSpec, expect to change with multiple values [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Is it possible for RSpec to expect change in two tables?
it "should create a new Recipe" do
expect { click_button submit }.to change(Recipe, :count).by(1)
end
This allows me to check that the 'Recipe' model has one more entry, but I'd like to also check that the 'Ingredient' model has one more entry. The expect block can only be executed once, since the form is submitted already.
I know I could just make another 'it' block, but I feel that there must be a DRYer way.
I would propose DRYing it up by redefining the test subject (and using stabby lambdas for fun):
describe "recipe creation" do
subject { -> { click_button submit } }
it { should change(Recipe, :count).by(1) }
it { should change(Ingredient, :count).by(1) }
end
Update: Although it may look less DRY, these days I would probably still keep on using the expect syntax, since it's recommended and I'm generally moving away from should, but perhaps make some minor changes for spec readability:
describe "recipe creation" do
let(:creating_a_recipe) { -> { click_button submit } }
it "changes the Recipe count" do
expect(creating_a_recipe).to change(Recipe, :count).by(1)
end
it "changes the Ingredient count" do
expect(creating_a_recipe).to change(Ingredient, :count).by(1)
end
end
Note: you may see in the RSpec documentation for the change matcher that expect uses curly brackets. This is, of course, correct, but the reason that standard parenthesis work in this example is that the code that changes mutable state (contained in creating_a_recipe) is in a lambda that gets called when passed into expect as a parameter.
Regardless, in this case either expect(creating_a_recipe) or expect { creating_a_recipe } can be used successfully, and whichever one you use is up to personal preference.
You could abstract all of that into a helper method
def test_creation_of(model)
it "should create a new #{model}" do
expect { click_button submit }.to change(model.constantize, :count).by(1)
end
end
But I would only recommend it if you will do this for many models. Else it will just make the code harder to read. If you do, best to put it into a spec helper.
Also, based on other precedents in your tests, you could pass a Const object instead of a string (as I used in the example above).
then
it "should create a new Recipe" do
test_creation_of( 'Recipe' )
test_creation_of( 'Ingredient' )
end

Resources