Rspec / Rails matcher that excepts a model to_be_saved - ruby-on-rails

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?

Related

Testing if a method returns a `non-null` value

Im testing if my method populate() returns a non-null value (which it does, it returns an integer > 0) but having troubles to correctly write it. I have:
describe House::Room do
describe '.populate' do
let(:info) {
$info = {"people"=>
{"name"=>"Jordan",
"last_name"=>"McClalister"}}
}
it 'should return an integer > 0' do
expect(House::Room.populate(info)).not_to eq(nil)
end
end
end
You'll need to change the let assignment to:
describe House::Room do
describe '.populate' do
let(:info) {"people"=>
{"name"=>"Jordan",
"last_name"=>"McClalister"}
}
it 'should return an integer > 0' do
expect(House::Room.populate(info)).not_to be(nil)
end
end
end
That should make your code work as you expect.
However, you could also use another matcher, like 'be_within' if you wanted to be more specific, or write several expect statements in the same test, like 'expect to be an integer', 'expect to be greater than 0', etc... There is no limit to the number of expect statements you can have in an 'it' block, the test will only pass if all of the expectations are fulfilled. (That said, I believe best practice would be to split it up into individual tests.)

Rails .include? returns true when it shouldn't

I'm testing my ActiveRecord models in RSpec and I'm trying to speed up my tests by just instantiating models instead of creating a FactoryGirl model, but ActiveRecord::Associations::CollectionProxy.include? is returning true when I expect false.
The code I'm testing is this method in the Schedule model, which is passed an argument of an instance of the Location model...
def available_at_location(location)
locations.include?(location)
end
And the test is...
before do
subject.locations = [location_b]
end
it 'returns false' do
result = subject.available_at_location(location_a)
expect(result).to be(false)
end
When I create location_a and location_b with FactoryGirl the test passes and include? returns false, but when I instantiate location_a and location_b with this code, the test fails and include? returns true.
let(:location_a) { Location.new(name: 'London') }
let(:location_b) { Location.new(name: 'York') }
Printing locations shows that it is a CollectionProxy containing location_b, and printing the location argument shows that it is location_a, both with the correct properties. And, the include? returns true whether the location argument is passed or not.
EDIT: Some additional code.
This is the code to define the subject...
subject { described_class.new(locations: [location_a]) }
And this is the code for the FactoryGirl Location factory...
require 'faker'
FactoryGirl.define do
factory :location do
name { Faker::Company.name }
address { Faker::Address.street_address }
city { Faker::Address.city }
zipcode { Faker::Address.zip_code }
landline { Faker::PhoneNumber.phone_number }
association :organization
end
end
N.B. I've tried providing an Organization to the constructor (Location.new(name: 'London', organization: Organization.new)) but it doesn't change the output.
CollectionProxy compares models by their ids, since both records are new and have id == nil - include? returns true.
To have your test working you have to save models to db (use create! and so on)
I've worked out the solution. I'll document this here in case anyone else has a similar issue. These lines are the problem...
subject { described_class.new(locations: [location_a]) }
before do
subject.locations = [location_b]
end
When using instances of your model instead of full blown DB models made by FactoryGirl, ActiveRecord doesn't update changes to the items in CollectionProxies. As far as it's concerned locations is always [location_a] even if you change the items to [location_b].
The solution is to only assign the properties once. I.e.
subject { described_class.new(locations: []) }
before do
subject.locations = [location_b]
end
Set the location in each context's before block to the required location.
I'm sure this is something to do with references to the Collection/CollectionProxy, but it's beyond me.

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

ruby rspec conversion from "should" to "expect" with block

I'm handling a set of rspec programs and pc seems to be forcing me to convert "should" questions to "expect".
Have been able to handle most, but having problems with the following rspec setup.
Most of the other 'should' formatting involves an answer should == something and is easily converted to expect(passed_in_value).to eql(returned_value).
In this case though, I believe it is passing in a block to add to a given number, however, i and unable to just convert it to
expect(end).to eql(6) or whatever the returned value should be.
Take a look and if you have any thoughts, please pass them on
it "adds one to the value returned by the default block" do
adder do
5
end.should == 6
end
it "adds 3 to the value returned by the default block" do
adder(3) do
5
end.should == 8
end
There're several methods to do that.
result = adder(3) do
5
end
expect(result).to eq(8)
expect do
adder(3) do
5
end
end.to eq(8)
block = -> do
5
end
expect(adder 3, &block).to eq(8)
Example from comments with respond_to:
it "has a #sum method" do
[].should respond_to(:sum) #old syntax
expect([]).to respond_to(:sum) #new syntax
end

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.

Resources