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
Related
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.
I have just upgraded to Rails 5. In my specs I have the following
expect(model).to receive(:update).with(foo: 'bar')
But, since params no longer extends Hash but is now ActionController::Parameters the specs are failing because with() is expecting a hash but it is actually ActionController::Parameters
Is there a better way of doing the same thing in Rspec such as a different method with_hash?
I can get around the issue using
expect(model).to receive(:update).with(hash_including(foo: 'bar'))
But that is just checking if the params includes that hash, not checking for an exact match.
You could do:
params = ActionController::Parameters.new(foo: 'bar')
expect(model).to receive(:update).with(params)
However it still smells - you should be testing the behaviour of the application - not how it does its job.
expect {
patch model_path(model), params: { foo: 'bar' }
model.reload
}.to change(model, :foo).to('bar')
This is how I would test the integration of a controller:
require 'rails_helper'
RSpec.describe "Things", type: :request do
describe "PATCH /things/:id" do
let!(:thing) { create(:thing) }
let(:action) do
patch things_path(thing), params: { thing: attributes }
end
context "with invalid params" do
let(:attributes) { { name: '' } }
it "does not alter the thing" do
expect do
action
thing.reload
end.to_not change(thing, :name)
expect(response).to have_status :bad_entity
end
end
context "with valid params" do
let(:attributes) { { name: 'Foo' } }
it "updates the thing" do
expect do
action
thing.reload
end.to change(thing, :name).to('Foo')
expect(response).to be_successful
end
end
end
end
Is touching the database in a spec inheritenly bad?
No. When you are testing something like a controller the most accurate way to test it is by driving the full stack. If we in this case had stubbed out #thing.update we could have missed for example that the database driver threw an error because we where using the wrong SQL syntax.
If you are for example testing scopes on a model then a spec that stubs out the DB will give you little to no value.
Stubbing may give you a fast test suite that is extremely brittle due to tight coupling and that lets plenty of bugs slip through the cracks.
I handled this by creating in spec/rails_helper.rb
def strong_params(wimpy_params)
ActionController::Parameters.new(wimpy_params).permit!
end
and then in a specific test, you can say:
expect(model).to receive(:update).with(strong_params foo: 'bar')
It's not much different from what you're already doing, but it makes the awkward necessity of that extra call a little more semantically meaningful.
#max had good suggestions about how to avoid this altogether, and I agree they switched away from a hash to discourage using them with hashes interchangeably.
However, if you still want to use them, as a simple hack for more complex situations (for instance if you expect using a a_hash_including), you can try using something like this:
.with( an_object_satisfying { |o|
o.slice(some_params) == ActionController::Parameters.new(some_params)
})
I'm working hard trying to keep my spec files as clean as possible. Using 'shoulda' gem and writing customized matchers that follow the same pattern.
My question is about creating a custom matcher that would wrap expect{ post :create ... }.to change(Model, :count).by(1) and could be used in the same example groups with other 'shoulda' matchers. Details bellow:
Custom matcher (simplified)
RSpec::Matchers.define :create_a_new do |model|
match do |dummy|
::RSpec::Expectations::ExpectationTarget.new(subject).to change(model, :count).by(1)
end
end
Working example
describe 'POST create:' do
describe '(valid params)' do
subject { -> { post :create, model: agency_attributes } }
it { should create_a_new(Agency) }
end
end
This work OK as long as I use a subject lambda and my matcher is the only one in the example group.
Failing examples
Failing example 1
Adding more examples in the same group makes the other matcher fail because subject is now a lambda instead of an instance of the Controller.
describe 'POST create:' do
describe '(valid params)' do
subject { -> { post :create, model: agency_attributes } }
it { should create_a_new(Agency) }
it { should redirect_to(Agency.last) }
end
end
Failing example 2
The 'shoulda' matcher expect me to define a before block, but this become incompatible with my custom matcher
describe 'POST create:' do
describe '(valid params)' do
before { post :create, agency: agency_attributes }
it { should create_a_new(Agency) }
it { should redirect_to(Agency.last) }
end
end
Expected result
I am looking for a way to write my custom matcher that would fit in the same example group as other matchers, meaning my custom matcher should use the before block to execute the controller action, the "failing example #2" above is the way I would like to write my specs. Is it possible?
Thanks for reading
I do not think there is a way you can get your failing examples passing.
This is because change really needs a lambda, since it needs to perform your count twice (once before, and once after calling it). That's the reason I tend not to use it (or use it in context isolation).
What I usually do, instead of using the count matcher, is checking three things:
The record is persisted. If I assign the model to #model, then I use expect(assigns(:model)).to be_persisted
The record is an instance of the expected model (though might not seem useful, it is
quite descriptive when using an STI). expect(assigns(:model)).to be_a(Model).
Check the last record in DB is the same as the one I just create `expect(assigns(:model)).to eq(Model.last)``
And that's the way I usually test the change matcher without using it. Of course, you can now create your own matcher
RSpec::Matchers.define :create_a_new do |model|
match do |actual|
actual.persisted? &&
actual.instance_of?(Participant) &&
(Participant.last == actual)
end
end
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.
I've ben struggling with this for a couple hours now. I am using Rack::Test to write API tests for my Rails 3.2 app.
Regardless of what I do, last_response has an empty body (well, specifically it has "{}", so 2 characters).
Here are the tests:
describe "updating a product set with JSON" do
def app
ProductSetsController.action(:update)
end
let(:update_json) { ... }
before do
#product_set = FactoryGirl.build(:product_set)
end
it { #failures.should == 0 }
it "should not increment the product set count" do
expect { put :update, update_json }.to_not change(ProductSet, :count).by(1)
end
it "should increment the conditions count" do
expect { put :update, update_json }.to change(#product_set.conditions, :count).by(2)
end
context "response should be valid" do
before do
put :update, update_json
end
subject { last_response }
it { should be_ok }
end
end
All these tests pass. But the body is empty.
The weird thing is that if I run the actual application the response body is definitely not empty. It has JSON about the updated product_set.
So I'm setting up the test incorrectly somehow. I bet I'm overlooking something really silly as this is my first time using Rack::Test. Any thoughts?
Just had a thought that I may not be setting the request headers correctly. I'm also using RABL for generation.
UPDATE:
The problem is indeed with RABL. Haven't figured out a solution yet, but if I use:
respond_with(#product_set.update_attributes(get_properties(params)),
file: 'app/views/product_sets/update.json.rabl', :handlers => [:rabl])
instead of:
respond_with(#product_set.update_attributes(get_properties(params)))
then it works in testing, whereas both work in development or production. Also I've confirmed that it's not a Gemfile problem.
Found the answer. In short, make sure to use the keyword "render_views" at the top of the root describe block in the rspec document to ensure that views are rendered correctly.
This was the helpful article:
https://github.com/nesquena/rabl/wiki/Testing-with-rspec