How to do an RSpec oneliner to test a :count change - ruby-on-rails

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.

Related

rspec if Rails.env.production? return string how to implemented it?

And it does not work. Can you help me how to write the right test for its case? Thanks very much.
#model.rb
def driver_iq_api
if Rails.env.production?
'https://admin.sss/xmlpost.cfm'
else
'https://eeem/ws/xmlpost.cfm'
end
end
model_spec.rb
describe 'private methods' do
context '.driver_iq_api' do
it 'production true' do
allow(Rails.env).to receive(:production?) {true}.and_return('https://admin.sss/xmlpost.cfm')
end
it 'production false' do
allow(Rails.env).to receive(:production?) {false}.and_return('https://eeem/ws/xmlpost.cfm')
end
end
end
Setting Rails.env to something other than test, inside a test, is a bad idea. Whilst you may "get away with it" in this case, it could cause all sorts of weird side-effects in general, such as writing data to a non-test database.
In addition, it seems you're writing unit tests for private methods, which is typically a bad idea. You should only normally test the public interface of a class.
As stated above, this sort of config should ideally live in a configuration file, such as e.g. application.yml.
The other answer already shows how you could stub the behaviour, but as yet another alternative, you could consider injecting the environment as a method dependency:
def driver_iq_api(env: Rails.env)
if env.production?
'https://admin.sss/xmlpost.cfm'
else
'https://eeem/ws/xmlpost.cfm'
end
end
describe '#driver_iq_api' do
it 'production env' do
expect(model.driver_iq_api(env: 'production'.inquiry)).to eq 'https://admin.sss/xmlpost.cfm'
end
it 'test env' do
expect(model.driver_iq_api(env: 'test'.inquiry)).to eq 'https://eeem/ws/xmlpost.cfm'
end
end
Note that for example, 'test'.inquiry returns a ActiveSupport::StringInquirer instance - which is the same behaviour as calling Rails.env.
...But to reiterate my original point, I wouldn't bother testing this method at all.
As for you test, I'm not familiar with the receive...{block} syntax you use, and I doubt seriously that tests what you think it is testing.
Here is a test suite that tests much more succinctly, and is super easy for anyone to read:
describe '.driver_iq_api' do
subject { MyModel.driver_iq_api }
context 'when in production' do
allow(Rails.env).to receive(:production?).and_return(true)
it { is_expected.to eq 'https://admin.sss/xmlpost.cfm' }
end
context 'when not in production' do
it { is_expected.to eq 'https://eeem/ws/xmlpost.cfm' }
end
end
And to reinforce what #Tom Lord said, this approach is dangerous for methods that actually do things like write to databases and such. I'd use this only for the type of method you have in your example...returning a resource name, boolean, etc. based on env. If your platform-sensitive code is buried deep in a method and can't be tested in isolation, then refactor it out of the big method into an atomic method that can easily be tested (and mocked!).
I agree you should pull this out into config since it is static, but to answer your question:
it "when production" do
allow(Rails.env).to receive(:production?).and_return(true)
expect(my_class.send(:driver_iq_api)).to eq('https://admin.sss/xmlpost.cfm')
end

Rails 5 Rspec receive with ActionController::Params

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

Clean Rspec matcher for change(Model, :count).by(1)

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

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

"nil is not a symbol" for model count in rspec matcher

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.

Resources