I am trying to write a test case where an id exists in the list of id's
I tried following but it doesnt work.
expect(response['products'][0]['ids'][0]['product_id'])
.to include(product_1.id, product_2.id, product_3.id)
It fails everytime with error the expected id doesnt exist.
e.g expected '123' to include '343' and '543'
but when i step through the code all id's are there so dont get why its looking in only in two ids.
response['products'][0]['ids'][0]['product_id'] is a String: "123".
String#include? will return true here, for the following inputs: "", "1", "2", "3", "12", "23", and "123" -- but that's clearly not what you're trying to test!
You wanted to check that this product_id is in that list; not that it includes the list.
This is a slightly unusual test to run, since your expectation is somewhat fuzzy.
If this is a rails application (i.e. you're using ActiveSupport), then you can make use of Object#in? to write the test as follows:
expect(response['products'][0]['ids'][0]['product_id'])
.to be_in(product_1.id, product_2.id, product_3.id)
Or if we're just using vanilla ruby then perhaps use rspec's satisfy matcher:
expect(response['products'][0]['ids'][0]['product_id'])
.to satisfy { |product_id| [product_1.id, product_2.id, product_3.id].include?(product_id) }
You may also be tempted to simply reverse the order of the arguments -- which technically works, but is a little confusing since the code seems like you're running assertions on the wrong object:
expect([product_1.id, product_2.id, product_3.id])
.to include(response['products'][0]['ids'][0]['product_id'])
But back to the point of this being an "unusual test".
Presumably, you've written it this way because you're not certain which order the ids will be listed in - i.e. which product will actually be response['products'][0].
The test would be even better if you either:
Made the order known (but I cannot really advise how without seeing more detail), so that you don't need a fuzzy matcher in the first place, or
Only have 1 product returned by the response in this test, or
Change the test to read all three product_ids from the response, and then use the match_array matcher.
Related
I'm wondering if this feature exists in RSpec. I can't seem to find any results when looking into it.
What I'd like to do is something that can be done in Jest tests like so:
// This is a Jest expectation
expect(foo).toMatchInlineSnapshot()
// On the first execution of this code, the value of foo will fill in the expectation and result in something like this
expect(foo).toMatchInlineSnapshot('bar')
I'd love to be able to do this with RSpec tests.
# Here's an RSpec expectation
expect(foo).to eq({ bar: 25 })
Say that I make a change to my code that will result in foo[:bar] having a different value, but I don't know what that value will be.
Currently, I need to re-run my tests and see an error saying something along the lines of
Failure/Error: expect(foo).to eq({ bar: 25 })
expected: { bar: 25 }
got: { bar: 100 }
After that, I need to manually update my expectation in order for it to pass.
Is there anyway to tell RSpec to automatically update the expected value?
For Example:
expect(foo).to eq({ bar: 25 }, {update: true})
would change the code after running the test to match the correct value and result in the following code replacing the above expectation:
expect(foo).to eq({ bar: 100 })
Is there any existing way to accomplish this? Some command that I can run with RSpec maybe?
spec UPDATE_EXPECTS=1
I've seen libraries that can match based on snapshots, but haven't been able to find anything that results in the expected answer appearing inline.
Thanks for the help
I don't believe this type of tool exists for RSpec. The closest you could maybe come is using a let or an instance variable at the top of your script.
let(:subtotal) { 25.0 } # can _maybe_ only update this if logic changed
let(:cart) { Cart.new }
before do
cart.add_item(10.0)
cart.add_item(15.0)
end
it do
expect(cart.subtotal).to eq(subtotal)
end
You'd still have to manually curate the values when logic changes that would break the test assertion. There are already a number of matchers in RSpec that will let you do a looser assertion, such as be_within, but nothing to automatically fill in values for you. The include matcher is another powerful matcher that lets you do looser assertions.
I feel like updating values automatically would be equivalent to testing expect(x).to eq(x). I'm not sure there's much value in that kind of test for what I've typically seen RSpec used for.
The reality is, your test should/will often fail when you make a change to your code. It's annoying that you have to go back and fix the test, but it should also reassuring that the test provided some value. I would be more alarmed if I changed code and didn't have a failing test...
I have a simple test in an RSpec controller spec that checks to see if the correct message was passed to a delayed job:
it 'sends a message to NotifyFollowersJob with relevant person and split_time data' do
allow(NotifyFollowersJob).to receive(:perform_later)
post :import, params: request_params
split_time_ids = SplitTime.all.ids
person_id = SplitTime.first.effort.person_id
expect(NotifyFollowersJob).to have_received(:perform_later)
.with(person_id: person_id,
split_time_ids: split_time_ids)
end
The test usually passes, but sometimes it fails because the split_time_ids (an Array) are sometimes reversed. I do not care what order the split_time_ids are passed to NotifyFollowersJob, so the test should pass regardless of the order.
If I were testing the contents of the Array alone, I could write:
expect(split_time_ids).to match_array(SplitTime.all.ids)
But I can't figure out how to get similar functionality where the Array is a value of one of several arguments.
Any RSpec masters out there care to give me some guidance?
One idea is to make a block to check the message and pass it to your allow method.
Like this:
allow(NotifyFollowersJob).to receive(:perform_later) do |arg|
# Handle your args to avoid fails because of its order
expect(...)
end
And you should keep the expect(NotifyFollowersJob).to have_received(:perform_later) (without the with verification) just to make sure it is still being called.
It looks like your controller action runs SplitTime.all.ids query and passes them to the worker as args.
If you don't care about the order, maybe you can stub this query, to get the same result all the time?
In addition, it'll make your test faster.
I'll be brief with the code samples, as all of my tests pass except the one below. I got it to pass by changing things up a bit, but I'm not sure why version 1 fails and version 2 works.
My model:
# app/models/person.rb
class Person
validates :contact_number, uniqueness: true
end
Model spec
# spec/models/person_spec.rb
require 'spec_helper'
describe Person do
it 'is a valid factory' do
create(:person).should be_valid # passes
end
it 'has a unique phone number' do
create(:person)
build(:person).should_not be_valid # fails
end
it 'also has a unique phone number' do
person1 = create(:person)
person2 = person1.dup
person2.should_not be_valid # passes
end
end
As far as I can tell, the two uniqueness tests should be doing the same thing, however one passes and one fails.
If it matters, I am using mongoid, though I don't think that should have any effect. I'm also not doing anything with nested contexts or describes in my test, so I think the scope is correct. Any insight is appreciated.
UPDATE 1: I realized in my factory I am adding an initialize_with block like this:
initialize_with { Person.find_or_create_by(contact_number: contact_number) }
I realized that this may be the reason the validation was failing -- I was just getting the same person back. However, commenting out that line gives the following error:
Mongoid::Errors::Validations:
Problem:
Validation of Person failed.
Summary:
The following errors were found: Contact number is already taken
Resolution:
Try persisting the document with valid data or remove the validations.
Which, in theory is good, I suppose, since it won't let me save a second person with the same contact number, but I'd prefer my test to pass.
Probably your person factory has a sequence in contact_number making a diferent contact_number in each person.
Just realize that the build(:person) doesnt validate. The validation occurs only in create.
I strongly suggest use of shoulda-matchers for this kind of validations.
It is possible that your database is being cleaned (do you have database-cleaner in your Gemfile), or your tests are not being run in the order you think they are. (Check for :random in your spec_helper.rb)
While the above answer regarding using shoulda-matchers will help you run this particular test in RSpec more concisely, you probably want to have your unique phone number test be able to be run completely on its own without relying on another spec having executed. Your second test is an example of Obscure Test (and also a little bit of Mystery Guest http://robots.thoughtbot.com/mystery-guest), where it's not clear from the test code what is actually being tested. Your phone number parameter is defined in another file (the factory), and the prior data setup is being run in another spec somewhere else in the file.
Your second test is already better because it is more explicitly showing what you're testing, and doesn't rely on another spec having been run. I would actually write it like this to make it more explicit:
it 'has a unique phone number' do
person1 = create(:person, phone_number: '555-123-4567')
person2 = create(:person, phone_number: '555-123-4567')
# can use 'should' here instead
expect(person2).not_to be_valid
end
If you don't explicitly make it about the phone number, then if you change your factory this test might start failing even though your code is still sound. In addition, if you have other attributes for which you are validating uniqueness, your previous test might pass even though the phone number validation is missing.
I figured it out! On a whim, I checked out the test database and noticed that a Person object was lingering around. So, it actually wasn't the
build(:person).should_not be_valid that was raising the Mongoid exception. It was the create call on the line before. Clearing out the DB and running the spec again passed, but again the data was persisting. I double checked my spec_helper.rb file and noticed I wasn't calling start on the DatabaseCleaner. My updated spec_helper.rb file looks like this, and now everything works:
# Clean up the database
require 'database_cleaner'
config.mock_with :rspec
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.orm = "mongoid"
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
Unfortunately I'm needing to interact with a Soap API. If that's not bad enough, the API has parameter ordering turned on which means, regardless of the fact it's XML, it must be structured in the correct element order.
I'm using Savon so I'm building an ordered hash. However after some refactoring, the real calls stopped working whilst all my tests continued to pass. A typical test looks like this:
it 'should receive correctly ordered hash' do
example_id = 12345789
our_api = Example::ApiGateway.new()
params = {:message=>{'ApiKey' => api_key, 'ExampleId' => example_id}}
Savon::Client.any_instance.should_receive(:call).with(:get_user_details, params).and_return(mocked_response())
our_api.get_user(example_id: example_id)
end
The hash compare totally does not care about the order of the keys so this test passes regardless of the actual hash order received. I'd like the just grab the parameters that the call method receives and then I could compare the ordered keys of each hash but I con't find how to do this.
How do I make sure that the Savon call receives the message hash in the correct order?
So in the next google I found the answer. should_receive can take a block, so I can rebuild my test as
it 'should receive correctly ordered hash' do
example_id = 12345789
our_api = Example::ApiGateway.new()
params = {:message=>{'ApiKey' => api_key, 'ExampleId' => example_id}}
Savon::Client.any_instance.should_receive(:call){ |arg1, arg2|
arg1.should eq(:get_user_details)
#Ensure order here manually
arg2[:message].keys.should eq(params[:message].keys)
mocked_response()
}
our_api.get_user(example_id: example_id)
end
Now my tests will break as expected when the keys get messed with and I can rack up yet more hours to working around other peoples's fragile code...
Each time I add in the correct code, it gives me the same error due to AboutMethods:0x00000101841a28 number changing each time. It's like its stuck and I don't know how to get out this loop. It worked once, then I went on to the next step, but then it triggered an error after that.
I must not be inputting the correct line of code given from the console?
def test_calling_private_methods_with_an_explicit_receiver
exception = assert_raise(NoMethodError) do
self.my_private_method
end
assert_match "private method `my_private_method' called for #<AboutMethods:0x000001008debf8>", exception.message
end
The AboutMethods:0x000001008debf8 changes each time, not sure how to approach this problem?
AboutMethods:0x... is the output of the inspect method, which usually (and in this case) includes the class name (AboutMethods) and the object id (0x...). The object id is related to the objects location in memory, so it will change every time.
In my experience, there is very little value to checking the string from an exception (it's brittle). However, if you feel the need, use a regex:
assert_match /private method `my_private_method' called for \#\<AboutMethods:.*/