rspec passing instance as argument causes result with different object id - ruby

I am trying to test this example code:
SomeJob.schedule_job(title: title, body: BodyGenerator.new(recipient: user))
So the body of my rspec test is:
expect(SomeJob).to receive(:schedule_job).with(
title: 'Some title',
body: BodyGenerator.new(recipient: test_user)
).and_call_original
...
But I am getting the error after running the tests
and the only difference between expected result and actual result is the object id of the BodyGenerator
- #<BodyGenerator:0x00007fb188b50f68 ...
+ #<BodyGenerator:0x00007fb188bebe50 ...
Without and_call_original we can use be_an_instance_of(BodyGenerator)
but here it's not the case
For more context. BodyGenerator.new(recipient: user) will return specific message depending on the user type and other user's properties.

BodyGenerator.new is returning a different instance of BodyGenerator in your test than in your implementation. As such, the two do not match.
You should instead mock BodyGenerator.new such that the returned instance in your implementation matches your test:
let(:body_double) { instance_double(BodyGenerator) }
before do
allow(BodyGenerator).to receive(:new).and_return(body_double)
end
it "passes the test" do
expect(SomeJob).to receive(:schedule_job).with(
title: "Some title",
body: body_double
)
end
If necessary, you can then also set expectations against the params passed to BodyGenerator.new

Related

Is there a way to properly drill down into a JSON response with random property names

I'm trying to test an API response where key values are randomized alphanumerics. This is making it difficult for me to drill down into the JSON response to get the data I want to test.
I am using SuperTest/Mocha/Chai. At this point I'm just trying to test to see if the property 'id', 'name', and 'pattern' exist, and to verify the values of those properties.
Unfortunately since the parent of those properties is a randomized value, i've been unable to access it.
I'm new to API testing in general, so I apologize if I'm not including some important information. Normally I would do something like this:
Example of expects I normally write:
end(function(err, res) {
expect(res.body).to.have.property('id');
expect(res.body.id).to.equal(0);
}
So far, the only way I've found to do it is to put response.text into a variable, then use split and splice to separate out the data I want. This is ugly and probably inefficient.
Example JSON I'm working with:
{ idTag1: 'randomValue',
idTag2:
{ 'randomValue':
{ id: 'an integer',
name: 'a basic string',
pattern: 'a basic string'
}
}
}

Rspec - Using variable defined in a let statement in another let statement

In my RSpec test, I have defined multiple methods using let() syntax. I have encountered a scenario where I need to use variable defined inside a let statement into another let statement inside same describe block.
context 'Reuse the name of first car in the second car' do
it_behaves_like 'some car test' do
let(:car1) do {
name1: "#{testcase['Car']}_#{Time.now.round}",
car_model: testcase['Toyota']
}
end
let(:car2) do {
name2: name1
}
end
end
end
As you can see I want to use the exact name value I defined name1 inside :car1 for name2 inside :car2. The above syntax throws me following error
NameError:
undefined local variable or method `name1' for #<RSpec::
How do I use exact value of name1 in :car2? Any ideas..
let(:name1) { "#{testcase['Car']}_#{Time.now.round}" }
let(:car1) { { name1: name1, car_model: testcase['Toyota'] } }
let(:car2) { { name2: name1 } }
So name1 is also now a lazy variable to initialized when is called, if not for car1, then for car2.
If the Time.now is a problem, you can leave the value of name1 as testcase['Car'] and then just interpolate the value of Time.now.
name1 is a key in the hash defined as car1, therefore you need to use the hash syntax to get its value:
let(:car1) do
{
name1: "#{testcase['Car']}_#{Time.now.round}",
car_model: testcase['Toyota']
}
end
let(:car2) do
{
name2: car[:name1]
}
end
Please note that this only answers your question on how to extract the value, I do not suggest writing specs like that. If both cars should share the same name than Sebastian's answer is probably clearer and easier to understand.
let definitions work just fine with each other.
You defined two things: car1 and car2.
Code is throwing an error about name1. You just didnt define it.
I guess you need to read more about ruby Hash and Symbol.

Rspec error: location has already been taken

I have following test case in rspec
let(:detail) { FactoryGirl.create(:detail) }
let(:url) do
"/detail/#{detail.detail_id}"\
"/user/#{detail.user.id}/details"
end
let(:location_header) do
detail_url(detail_id: detail.detail_id,
user: detail.user.id, location: detail.location)
end
before do
post url, params: params
end
context 'when valid location and value are passed, detail is created successfully.' do
let(:params) {{detail_app_id: detail.detail_app_id, location: detail.location, value: 'value'}}
it { expect(response.status).to eq(201) }
end
I am learning ruby rspec and i am getting an error "{\"error\":\"Validation failed: location has already been taken\"}"
when i change location value in params it passes but since i am creating factory girl for it i want to use that value rather passing different one.
Any idea why i am getting that error?
You need to go into one of your factories (I'm assuming detail) and assign its location properly (factories/detail.rb).
What's happening here is you're trying to create a new detail, which it attempts to save, but it fails the validation (I'm assuming you have a unique location validation on the detail model)
Check out http://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md, the sequences section, for how to give a unique attribute where required.

RSpec - trying to stub a method that returns its own argument

I have a method which I'm trying to stub out in my unit test. The real method gets called with one argument (a string) and then sends out a text message. I need to stub out the method but return the string that gets passed in as an argument.
The code I have in my RSpec test is this:
allow(taxi_driver).to receive(:send_text).with(:string).and_return(string)
This returns:
NameError: undefined local variable or method 'string'
If I change the return argument to :string, I get the following error:
Please stub a default value first if message might be received with other args as well
I've tried googling and checking the relishapp.com site, but can't find the answer to something which appears quite simple and straightforward.
You can pass a block:
allow(taxi_driver).to receive(:send_text).with(kind_of(String)){|string| string }
expect(taxi_driver.send_text("123")).to eq("123")
My method is being called like this: send_text("the time now is #{Time.now}"). The string varies according to the time, thats why I need the mock to return the varying string. Perhaps its not within the scope of a mock to do this?
In such a case, I usually use Timecop gem in order to freeze system time. Here is a sample use case:
describe "#send_text" do
let(:taxi_driver) { TaxiDriver.new }
before do
Timecop.freeze(Time.local(2016, 1, 30, 12, 0, 0))
end
after do
Timecop.return
end
example do
expect(taxi_driver.send_text("the time now is #{Time.now}")).to eq \
"the time now is 2016-01-30 12:00:00 +0900"
end
end

How to make an optional strong parameters key but filter nested params?

I have this in my controller:
params.require(:item).permit!
Let's assume this rspec spec, which works as expected:
put :update, id: #item.id, item: { name: "new name" }
However, the following causes ActionController::ParameterMissing:
put :update, id: #item.id, item: nil
It has to do with controller macros that I use for other actions and through which I cannot control the params being sent (the macros checks for user credentials, so I don't really care about actually testing an #update action, rather I just test before_filters for it).
So my question is: How do I make params[:item] optional, yet still filter attributes within it if it's present?
What about:
params.require(:item).permit! if params[:item]
You cannot require an optional parameter. That is contradictory.
Edit: as mtjhax mentioned in his comment, there is advice from here to use fetch instead: params.fetch(:item, {}).permit!

Resources