I have a test like that:
lambda { post("/api/users", parameters) }.should change(User,:count).by(1)
lambda { post("/api/users", parameters) }.should_not change(ActionMailer::Base, :deliveries)
But I want to do it like that:
lambda { post("/api/users", parameters) }.should change(User,:count).by(1).and_not change(ActionMailer::Base, :deliveries)
Is it possible to do it without the need of two post calls?
Thanks
I have found a solution to test it.
lambda{
lambda { post("/api/users", params) }.should change(User,:count).by(1)
}.should change(ActionMailer::Base.deliveries, :count).by(1)
In my tests I am very strict: I want each test to test only a single thing. So I would always choose the first form, not the second.
Secondly I am not sure it is technically possible. The .should expects a block, which is executed before and after the lambda. Anyway, to my knowledge currently rspec does not support this (and imho with good reason).
I recently came across this issue when migrating some request tests over to feature test format for Capybara 2.1, and switching the testing syntax there from should-based to expect-based. To use the original question as the example, I had code like:
subject { -> { post("/api/users", parameters) } }
it { should change(User,:count).by(1) }
it { should_not change(ActionMailer::Base, :deliveries) }
Bringing this over to expect syntax in a scenario test presented some issues and yielded this kind of (working) clunkiness (sorry, not a big fan of explicitly nested lambdas/expects):
expect(-> { expect(post("/api/users", parameters)).to change(User,:count).by(1) }
).to_not change(ActionMailer::Base, :deliveries)
There are some great solutions to this issue in this StackOverflow thread which I tried and succeeded with, but what I ended up doing was simply follow the original format somewhat and split out each statement into its own scenario; something like:
feature "Add users via API" do
given(:posting_parameters_to_api) { -> { post("/api/users", parameters) } }
scenario "foo" do
expect(posting_parameters_to_api).to change(User,:count).by(1)
end
scenario "bar" do
expect(posting_parameters_to_api).to_not change(ActionMailer::Base,
:deliveries)
end
end
More verbose than the original request spec, but essentially working in the same way. Implementation will likely come down to personal taste.
Related
I want to stub first two calls to HTTParty with raised exception and then, the third call, should return value.
before do
allow(HTTParty).to receive(:get).exactly(2).times.with(url).and_raise(HTTParty::Error)
allow(HTTParty).to receive(:get).with(url).and_return('{}')
end
but one allow override another one. how to set stub to raise errors for first few tries and then let it return a value?
According to info provided in this github issue, you can also do that with the following pure-RSpec approach. It leverages the most general way of defining mock responses using block:
before do
reponse_values = [:raise, :raise, '{}']
allow(HTTParty).to receive(:get).exactly(3).times.with(url) do
v = response_values.shift
v == :raise ? raise(HTTParty::Error) : v
end
end
In this specific case, you could use WebMock:
Multiple responses using chained to_return(), to_raise() or to_timeout declarations.
Something like this should work:
before do
stub_request(:get, url).
to_raise(SomeException).then.
to_raise(SomeException).then.
to_return(body: '{}')
end
SomeException should be an actual network error.
You can use an array of Proc instances to give you complete control:
my_object = double
values = [proc { raise ArgumentError }, proc { 2 }]
allow(my_object).to receive(:my_method).exactly(:twice) { values.shift.call }
expect { my_object.my_method }.to raise_error ArgumentError
expect(my_object.my_method).to eq 2
This way you can raise different exceptions across multiple calls or just return values to suit your needs.
You can use a similar approach to call the original when your stubbed responses have all been used:
allow(my_object).to receive(:my_method).and_wrap_original do |original, *args|
values.empty? ? original.call(*args) : values.shift.call
end
I like #b-f approach, but simplified it with #and_invoke, and for me, I've already specified the return values, so I just need to know they were both used.
allow(my_object).to receive(:my_method).and_invoke(proc { raise ArgumentError }, proc { 2 } )
expect(my_object).to have_received(:my_method).exactly(2).times
:speedis simply a numeric model attribute.
Both of these are passing:
it { is_expected.to allow_value( '1').for(:speed) }
it { is_expected.not_to allow_value( '1', 'fff' ).for(:speed) }
With a minor change, the second one is not passing:
it { is_expected.to allow_value( '1').for(:speed) }
it { is_expected.not_to allow_value( '1' ).for(:speed) }
Apparently, if there's a single passing value in the not_to allow_value expression the whole list of values passes.
I'm not quite understanding if it's working as intended and I'm doing it wrong, or if it's a bug.
It seems like it's a bug according to how the documentation explains it.
In Model
validates :max_panels, :if => :depot?, :numericality => true
I am writing an rspec for the above validation and found something confusing
it { should validate_numericality_of(:max_panels) if :depot? }
When ran this test case got error like
1) Site spec for valid sites - Validations
Failure/Error: it { should validate_numericality_of(:max_panels) if :depot? }
Expected errors to include "is not a number" when max_panels is set to "abcd", got errors: ["format can't be blank (nil)", "illumination can't be blank (nil)", "illumination_period can't be blank (nil)", "vertical_size must be between 0.1 and 30 metres (nil)", "horizontal_size must be between 0.1 and 200 metres (nil)", "site_authorisation_id must have a valid authorisation (nil)"]
But when i added unless in my test case it got passed, Can anybody please explain me regarding it as i am new to Rspec. Also suggest how i can write the correct rspec for above validation.
it { should validate_numericality_of(:max_panels) if :depot? unless true }
Brief looking at the source of validate_numericality_of matcher shows that it doesn't contain explicit support for :if conditions. This may be handled by base matchers, but anyway, here's an alternative idea about the testing: prepare object, attempt validation and check error messages.
Something along the lines of:
describe 'numericality validation' do
subject(:instance) { described_class.new(params) }
before { instance.valid? }
context 'when depot' do
let(:params) { { max_panels: 'abcd', depot: true} }
it { expect(instance.errors.messages[:max_panels]).to eq 'is not a number' }
end
context 'when not depot' do
let(:params) { { max_panels: 'abcd', depot: false} }
it { expect(instance.errors.messages[:max_panels]).to eq nil }
end
end
unless true will never happen, so your it is likely not running the should and looks as though it passed.
unless is not rspec, that is pure ruby. You have told rspec you have a test (it), and that the should will never run. This will show as a passed test since the it did not fail.
Another part of this is to not use conditionals in your tests. They should be predictable. Don't use if or unless to determine whether or not to run assertions.
Lastly: Your error from rspec shows a list of validation errors, not of them say anything about what you're asserting. This test has multiple issues, but is flawed even in the should
I want to check if the model was persisted to the DB by the various means available. It looks like all these things defer to .save but I'm curious if there is a better way, perhaps using what Dirty provides?
One way to check if a new record was created:
expect {
MyModel.do_something_which_should_create_a_record
}.to change(MyModel, :count).by(1)
Or, if you're wanting to check that a value was saved, you could do something like:
my_model.do_something_which_updates_field
my_model.reload.field.should == "expected value"
Or you could use expect and change again:
my_model = MyModel.find(1)
expect {
my_model.do_something
}.to change { my_model.field }.from("old value").to("expected value")
Is that what you were meaning?
What's the best way to test scopes in Rails 3. In rails 2, I would do something like:
Rspec:
it 'should have a top_level scope' do
Category.top_level.proxy_options.should == {:conditions => {:parent_id => nil}}
end
This fails in rails 3 with a "undefined method `proxy_options' for []:ActiveRecord::Relation" error.
How are people testing that a scope is specified with the correct options? I see you could examine the arel object and might be able to make some expectations on that, but I'm not sure what the best way to do it would be.
Leaving the question of 'how-to-test' aside... here's how to achieve similar stuff in Rails3...
In Rails3 named scopes are different in that they just generate Arel relational operators.
But, investigate!
If you go to your console and type:
# All the guts of arel!
Category.top_level.arel.inspect
You'll see internal parts of Arel. It's used to build up the relation, but can also be introspected for current state. You'll notice public methods like #where_clauses and such.
However, the scope itself has a lot of helpful introspection public methods that make it easier than directly accessing #arel:
# Basic stuff:
=> [:table, :primary_key, :to_sql]
# and these to check-out all parts of your relation:
=> [:includes_values, :eager_load_values, :preload_values,
:select_values, :group_values, :order_values, :reorder_flag,
:joins_values, :where_values, :having_values, :limit_value,
:offset_value, :readonly_value, :create_with_value, :from_value]
# With 'where_values' you can see the whole tree of conditions:
Category.top_level.where_values.first.methods - Object.new.methods
=> [:operator, :operand1, :operand2, :left, :left=,
:right, :right=, :not, :or, :and, :to_sql, :each]
# You can see each condition to_sql
Category.top_level.where_values.map(&:to_sql)
=> ["`categories`.`parent_id` IS NULL"]
# More to the point, use #where_values_hash to see rails2-like :conditions hash:
Category.top_level.where_values_hash
=> {"parent_id"=>nil}
Use this last one: #where_values_hash to test scopes in a similar way to #proxy_options in Rails2....
Ideally your unit tests should treat models (classes) and instances thereof as black boxes. After all, it's not really the implementation you care about but the behavior of the interface.
So instead of testing that the scope is implemented in a particular way (i.e. with a particular set of conditions), try testing that it behaves correctly—that it returns instances it should and doesn't return instances it shouldn't.
describe Category do
describe ".top_level" do
it "should return root categories" do
frameworks = Category.create(:name => "Frameworks")
Category.top_level.should include(frameworks)
end
it "should not return child categories" do
frameworks = Category.create(:name => "Frameworks")
rails = Category.create(:name => "Ruby on Rails", :parent => frameworks)
Category.top_level.should_not include(rails)
end
end
end
If you write your tests in this way, you'll be free to re-factor your implementations as you please without needing to modify your tests or, more importantly, without needing to worry about unknowingly breaking your application.
This is how i check them. Think of this scope :
scope :item_type, lambda { |item_type|
where("game_items.item_type = ?", item_type )
}
that gets all the game_items where item_type equals to a value(like 'Weapon') :
it "should get a list of all possible game weapons if called like GameItem.item_type('Weapon'), with no arguments" do
Factory(:game_item, :item_type => 'Weapon')
Factory(:game_item, :item_type => 'Gloves')
weapons = GameItem.item_type('Weapon')
weapons.each { |weapon| weapon.item_type.should == 'Weapon' }
end
I test that the weapons array holds only Weapon item_types and not something else like Gloves that are specified in the spec.
Don't know if this helps or not, but I'm looking for a solution and ran across this question.
I just did this and it works for me
it { User.nickname('hello').should == User.where(:nickname => 'hello') }
FWIW, I agree with your original method (Rails 2). Creating models just for testing them makes your tests way too slow to run in continuous testing, so another approach is needed.
Loving Rails 3, but definitely missing the convenience of proxy_options!
Quickly Check the Clauses of a Scope
I agree with others here that testing the actual results you get back and ensuring they are what you expect is by far the best way to go, but a simple check to ensure that a scope is adding the correct clause can also be useful for faster tests that don't hit the database.
You can use the where_values_hash to test where conditions. Here's an example using Rspec:
it 'should have a top_level scope' do
Category.top_level.where_values_hash.should eq {"parent_id" => nil}
end
Although the documentation is very slim and sometimes non-existent, there are similar methods for other condition-types, such as:
order_values
Category.order(:id).order_values
# => [:id]
select_values
Category.select(:id).select_values
# => [:id]
group_values
Category.group(:id).group_values
# => [:id]
having_values
Category.having(:id).having_values
# => [:id]
etc.
Default Scope
For default scopes, you have to handle them a little differently. Check this answer out for a better explanation.