I have written following test in Rspec:
expect {
...
}.to change( User.where(:profile.exists => true), :count ).by(1)
but this scope is executed only once and it's always the same Array with the same size. How to make rspec execute this scope before and after running the code in expect?
The OPs Solution, Posted As Answer
This may or may not work for anyone else with a similar problem. No corpus was included in the original question, and it was not independently verified.
expect {
# test goes here
}.to change{ User.where(:profile.exists => true).count }.by(1)
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)
})
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
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
I'm trying to expect an error in an rspec test.
lambda {Participant.create!({:user_id => three.id, :match_id => match.id, :team => 1})}.should raise_error StandardError
For now I'm just using StandardError to make sure it's working.
1) StandardError in 'Participant should never allow more participants than players'.
This game is already full. Cannot add another player.
/home/josiah/Projects/Set-Match/app/models/participant.rb:12:in `do_not_exceed_player_count_in_match'
./spec/models/participant_spec.rb:24:
It clearly throws the error, but my test still fails.
Thoughts?
Since some time, but at least in RSpec 2.5, it is possible to use
expect {raise 'boom'}.to raise_error(RuntimeError, /boom/)
Your syntax looks correct. To debug this, simplify to make sure your spec is coded correctly.
it "should raise an error" do
lambda {raise "boom"}.should raise_error
end
And then add more detail until it breaks.
lambda {raise "boom"}.should raise_error(RuntimeError)
lambda {raise StandardError.new("boom")}.should raise_error(StandardError)
I had to fight with the same symptoms:
def boom
raise "boom"
end
boom.should raise_error
The test above fails because raise_error requires should to be called on a Proc (due to technical reasons, I suppose). Thus, wrapping a method call with a lambda works just fine:
lambda { boom }.should raise_error
Unfortunately, the documentation does not say that explicitly and there is no RSpec Exception that reveals this behavior. There is a two year old ticket for that.