Two conflicting shoulda matchers allow_value expressions are both passing - ruby-on-rails

: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.

Related

How to use `or` in RSpec equality matchers (Rails)

I'm trying to do something like
expect(body['classification']).to (be == "Apt") || (be == "House")
Background:
This is testing a JSON API response.
Issue:
I want the test to pass if either "Apt" or "House" are returned. But in the test it is only comparing to the first value, "Apt".
Failure/Error: expect(body['classification']).to be == "Apt" or be == "House"
expected: == "Apt"
got: "House"
Previous Solution:
There is a solution here,
(Equality using OR in RSpec 2) but its depreciated now, and I wasn't able to make it work.
Documentation:
Also wasn't able to find examples like this in the documentation (https://www.relishapp.com/rspec/rspec-expectations/v/3-4/docs/built-in-matchers/equality-matchers)
Is this possible to do?
How about this:
expect(body['classification'].in?(['Apt', 'Hourse']).to be_truthy
Or
expect(body['classification']).to eq('Apt').or eq('Hourse')
Or even this:
expect(body['classification']).to satify { |v| v.in?(['Apt', 'Hourse']) }
expect(body['classification']).to eq("Apt").or eq("House")
Based on this link
"Compound Expectations.
Matchers can be composed using and or or to make compound expectation
Use or to chain expectations"
RSpec.describe StopLight, "#color" do
let(:light) { StopLight.new }
it "is green, yellow or red" do
expect(light.color).to eq("green").or eq("yellow").or eq("red")
end

Rspec for Validation

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

Rspec expect assigns shorthand

I currently have something like:
it 'assigns #competition' do
expect(assigns(:competition)).to be_a_new(Competition)
end
Is there a shorter version of this using the it { should ... } type syntax?
I don't know that it's shorter, but you can use:
subject {assigns(:competition)}
it {should be_a_new(Competition)}
or you can use:
it {expect(assigns(:competition)).to be_a_new(Competition)}
In both cases, the shortening is coming from the elimination of the string argument to it, which is independent of the use of should.
By now the RSpec documentation suggests to use is_expected.to, as in:
describe [1, 2, 3] do
it { is_expected.to be_an Array }
it { is_expected.not_to include 4 }
end
cf. http://www.rubydoc.info/gems/rspec-core/RSpec/Core/MemoizedHelpers#is_expected-instance_method

Assert multiple change expectations within a single lambda request

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.

Rspec / Rails matcher that excepts a model to_be_saved

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?

Resources