how to check/test specific field in json using rspec - ruby-on-rails

This is my rspec code:-
it "which has max value" do
get :index, Devise.token_authentication_key => #user.authentication_token, business_id: #business.id, max: '1'
expect(request.flash[:alert]).to eq(nil)
expect(response.body).to eq([#location].to_json(LocationFinder::API_PARAMS.merge(:root => false)))
end
and testing result is-
expected: "[{\"address\":\"1120 Milky Way\",\"business_id\":1,\"city\":\"Cupertino]"
got: "[{\"address\":\"1120 Milky Way\",\"business_id\":1,\"city\":\"Cupertino,\"distance\":260.33452958767384,]"
Here Distance is an extra field , how can i check particular fields or if it is not possible , how to eliminate "distance" field which is not check by rspec.

You could check individual fields using something like:
# get the first entry in the JSON array
json_response = JSON.parse(response.body).first
# compare each field
expect(json_response['address']).to eq(#location.address)
expect(json_response['business_id']).to eq(#location.business_id)
expect(json_response['city']).to eq(#location.city)
Of course you may need to adjust the exact methods you call on #location depending on your implementation, but that's the gist of it.

Related

Don't change string value on insert

I have a Model user with the following method:
def number_with_hyphen
number&.insert(8, "-")
end
When I run it several times in my tests I get the following output:
users(:default).number_with_hyphen
"340909-1234"
(byebug) users(:default).number_with_hyphen
"340909--1234"
(byebug) users(:default).number_with_hyphen
"340909---1234"
(byebug) users(:default).number_with_hyphen
"340909----1234"
It changes the number ?Here are the docs https://apidock.com/ruby/v1_9_3_392/String/insert
When I restructure my method to:
def number_with_hyphen
"#{number}".insert(8, "-") if number
end
If works like expected. The output stays the same!
How would you structure the code, how would you perform the insert?
which method should I use instead. Thanks
If you're using the insert method, which in the documentation explicitly states "modifies str", then you will need to avoid doing this twice, rendering it idempotent, or use another method that doesn't mangle data.
One way is a simple regular expression to extract the components you're interested in, ignoring any dash already present:
def number_with_hyphen
if (m = number.match(/\A(\d{8})\-?(\d+)\z/))
[ m[1], m[2] ].join('-')
else
number
end
end
That ends up being really safe. If modified to accept an argument, you can test this:
number = '123456781234'
number_with_hyphen(number)
# => "12345678-1234"
number
# => "123456781234"
number_with_hyphen(number_with_hyphen(number))
# => "12345678-1234"
number_with_hyphen('1234')
# => "1234"
Calling it twice doesn't mangle anything, and any non-conforming data is sent through as-is.
Do a clone of the string:
"#{number}".clone.insert(8, '-')

RSpec eq matcher returns failure when two things are equal

In my controller test, I am testing the correct value is assigned to an instance variable.
When I do
expect(assigns(:conversations)).to eq #user_inbox
RSpec tells me:
Failure/Error: expect(assigns(:conversations)).to eq #user_inbox
expected: #<ActiveRecord::Relation [#<Mailboxer::Conversation id: 4, subject: "Dude, what up?", created_at: "2014-10-21 08:43:50", updated_at: "2014-10-21 08:43:50">]>
got: #<ActiveRecord::Relation [#<Mailboxer::Conversation id: 4, subject: "Dude, what up?", created_at: "2014-10-21 08:43:50", updated_at: "2014-10-21 08:43:50">]>
(compared using ==)
Diff:
I see that there is no difference between the expected and the actual. I would like to know what is causing this test to fail.
ActiveRecord::Relation compares based on the actual relation, not the result set. For example,
User.where(:id => 123) == User.where(:email => "fred#example.com")
will return false, even the query results are both the same, since the actual queries are different.
I suspect that you care much more about the query results rather than how it was composed, in which case you can use to_a to convert the relation to an array of active record objects. Note that Active Record defines equality based only on the value of the id attribute (with a special case for unsaved objects).
Yes, because this is two ActiveRecord::Relation object. Your instance variable is the first one and you create another one called conversations
You should test the number of rows or other property with something like this:
expect(assigns(:conversations).count).to eq #user_inbox.count
Maybe you should change the test strategy.
When your test is hard to write your code is wrong or your test strategy is wrong. I recommend you no test query result in the controller's test.
you should mock your query result
describe 'GET user conversations' do
before do
your_user.stub(:conversations).and_return "foo bar"
end
it 'assigns the conversations of the user' do
get :user_conversation
expect(assigns(:conversations)).to eq your_user.conversations
end
end
or you should test that some_collaborator.should_receive(:some_methods)
describe 'GET user conversations' do
before do
some_collaborator.stub(:conversations)
end
it 'assigns the conversations of the user' do
some_collaborator.should_receive(:conversations)
get :user_conversation
end
end

Rails query by arbitrary column

In my Rails API / Angular app, I want to be able to search Rails tables using field values. I currently have this code below working, which allows searching the users table by email id, and it returns the users record as JSON.
api/controllers/users_controller.rb
def query # by email
queried_user = User.where(email: params[:email]).first
if !queried_user.nil?
render json: queried_user, root: false
else
render json: {error: 'Does not exist'}, status: :not_found
end
end
config/routes.rb
get 'api/users/:id/query' => 'api/users#query'
Example url
http://0.0.0.0:8080/api/users/1/query?email=testuser1#example.com
Example returned JSON
{"id":14,"title":"Dr.","first_name":"John","last_name":"Smith","email":"testuser1#example.com","job_title":"Head Bioligist","organisation":"NIH","phone_office":null,"city":null,"country":null,"approved":true,"admin":false,"template":false}
This is all working fine at present, but there are two issues I cannot resolve.
I would like the url to not contain an :id I find when I leave the id out of the url, Rails treats the query parameter as the id. I can made it work by hard-coding a fake id, but it doesn't seem like the right answer to me.
I would like to pass an abitary param hash to the query method. It should map the columns based on the hash contents.
if params = {email: 'testuser1#example.com'} then it should work as now, but other desired options might be:
{job_title: 'Manager'}
{city: 'LA', last_name: 'Smith'}
I expect I will change this code, but don't know how to pass arbitrary elements to the where.
queried_user = User.where(email: params[:email])
The where method can accept a hash, therefore you can pass the param hash containing the condition for the query. Just note only equality and range conditions can be used when passing a hash to the where method. Just be sure that in terms of security of your application you are covered. example:
queried_user = User.where(params[:user])
To get rid of the :id in your routes file define a new route similar to this:
match 'api/users/query', to: 'users#query', as 'user_search'
and then use the 'user_search_path' for sending the search to the query action of the users controller.

Trouble comparing time with RSpec

I am using Ruby on Rails 4 and the rspec-rails gem 2.14. For a my object I would like to compare the current time with the updated_at object attribute after a controller action run, but I am in trouble since the spec does not pass. That is, given the following is the spec code:
it "updates updated_at attribute" do
Timecop.freeze
patch :update
#article.reload
expect(#article.updated_at).to eq(Time.now)
end
When I run the above spec I get the following error:
Failure/Error: expect(#article.updated_at).to eq(Time.now)
expected: 2013-12-05 14:42:20 UTC
got: Thu, 05 Dec 2013 08:42:20 CST -06:00
(compared using ==)
How can I make the spec to pass?
Note: I tried also the following (note the utc addition):
it "updates updated_at attribute" do
Timecop.freeze
patch :update
#article.reload
expect(#article.updated_at.utc).to eq(Time.now)
end
but the spec still does not pass (note the "got" value difference):
Failure/Error: expect(#article.updated_at.utc).to eq(Time.now)
expected: 2013-12-05 14:42:20 UTC
got: 2013-12-05 14:42:20 UTC
(compared using ==)
I find using the be_within default rspec matcher more elegant:
expect(#article.updated_at.utc).to be_within(1.second).of Time.now
Ruby Time object maintains greater precision than the database does. When the value is read back from the database, it’s only preserved to microsecond precision, while the in-memory representation is precise to nanoseconds.
If you don't care about millisecond difference, you could do a to_s/to_i on both sides of your expectation
expect(#article.updated_at.utc.to_s).to eq(Time.now.to_s)
or
expect(#article.updated_at.utc.to_i).to eq(Time.now.to_i)
Refer to this for more information about why the times are different
Old post, but I hope it helps anyone who enters here for a solution. I think it's easier and more reliable to just create the date manually:
it "updates updated_at attribute" do
freezed_time = Time.utc(2015, 1, 1, 12, 0, 0) #Put here any time you want
Timecop.freeze(freezed_time) do
patch :update
#article.reload
expect(#article.updated_at).to eq(freezed_time)
end
end
This ensures the stored date is the right one, without doing to_x or worrying about decimals.
yep as Oin is suggesting be_within matcher is the best practice
...and it has some more uscases -> http://www.eq8.eu/blogs/27-rspec-be_within-matcher
But one more way how to deal with this is to use Rails built in midday and middnight attributes.
it do
# ...
stubtime = Time.now.midday
expect(Time).to receive(:now).and_return(stubtime)
patch :update
expect(#article.reload.updated_at).to eq(stubtime)
# ...
end
Now this is just for demonstration !
I wouldn't use this in a controller as you are stubbing all Time.new calls => all time attributes will have same time => may not prove concept you are trying to achive. I usually use it in composed Ruby Objects similar to this:
class MyService
attr_reader :time_evaluator, resource
def initialize(resource:, time_evaluator: ->{Time.now})
#time_evaluator = time_evaluator
#resource = resource
end
def call
# do some complex logic
resource.published_at = time_evaluator.call
end
end
require 'rspec'
require 'active_support/time'
require 'ostruct'
RSpec.describe MyService do
let(:service) { described_class.new(resource: resource, time_evaluator: -> { Time.now.midday } ) }
let(:resource) { OpenStruct.new }
it do
service.call
expect(resource.published_at).to eq(Time.now.midday)
end
end
But honestly I recommend to stick with be_within matcher even when comparing Time.now.midday !
So yes pls stick with be_within matcher ;)
update 2017-02
Question in comment:
what if the times are in a Hash? any way to make expect(hash_1).to eq(hash_2) work when some hash_1 values are pre-db-times and the corresponding values in hash_2 are post-db-times? –
expect({mytime: Time.now}).to match({mytime: be_within(3.seconds).of(Time.now)}) `
you can pass any RSpec matcher to the match matcher
(so e.g. you can even do API testing with pure RSpec)
As for "post-db-times" I guess you mean string that is generated after saving to DB. I would suggest decouple this case to 2 expectations (one ensuring hash structure, second checking the time) So you can do something like:
hash = {mytime: Time.now.to_s(:db)}
expect(hash).to match({mytime: be_kind_of(String))
expect(Time.parse(hash.fetch(:mytime))).to be_within(3.seconds).of(Time.now)
But if this case is too often in your test suite I would suggest writing your own RSpec matcher (e.g. be_near_time_now_db_string) converting db string time to Time object and then use this as a part of the match(hash) :
expect(hash).to match({mytime: be_near_time_now_db_string}) # you need to write your own matcher for this to work.
The easiest way I found around this problem is to create a current_time test helper method like so:
module SpecHelpers
# Database time rounds to the nearest millisecond, so for comparison its
# easiest to use this method instead
def current_time
Time.zone.now.change(usec: 0)
end
end
RSpec.configure do |config|
config.include SpecHelpers
end
Now the time is always rounded to the nearest millisecond to comparisons are straightforward:
it "updates updated_at attribute" do
Timecop.freeze(current_time)
patch :update
#article.reload
expect(#article.updated_at).to eq(current_time)
end
You can convert the date/datetime/time object to a string as it's stored in the database with to_s(:db).
expect(#article.updated_at.to_s(:db)).to eq '2015-01-01 00:00:00'
expect(#article.updated_at.to_s(:db)).to eq Time.current.to_s(:db)
Because I was comparing hashes, most of these solutions did not work for me so I found the easiest solution was to simply grab the data from the hash I was comparing. Since the updated_at times are not actually useful for me to test this works fine.
data = { updated_at: Date.new(2019, 1, 1,), some_other_keys: ...}
expect(data).to eq(
{updated_at: data[:updated_at], some_other_keys: ...}
)
In Rails 4.1+ you can use Time Helpers:
include ActiveSupport::Testing::TimeHelpers
describe "some test" do
around { |example| freeze_time { example.run } }
it "updates updated_at attribute" do
expect { patch :update }.to change { #article.reload.updated_at }.to(Time.current)
end
end

Assert difference of number of children in relationship in Ruby on Rails

My controller is able to create a child book_loan. I am trying to test this behavior in a functional test but am having a hard time using the assert_difference method. I've tried a number of ways of passing the count of book_loans to assert_difference with no luck.
test "should create loan" do
#request.env['HTTP_REFERER'] = 'http://test.com/sessions/new'
assert_difference(books(:ruby_book).book_loans.count, 1) do
post :loan, {:id => books(:ruby_book).to_param,
:book_loan => {:person_id => 1,
:book_id =>
books(:dreaming_book).id}}
end
end
can't convert BookLoan into String
assert_difference(books(:ruby_book).book_loans,:count, 1)
NoMethodError: undefined method 'book_loans' for #
assert_difference('Book.book_loans.count', +1)
can't convert Proc into String
assert_difference( lambda{books(:ruby_book).book_loans.count}, :call, 1 )
It looks like assert_difference expects a string, which it will eval before and after the block. So the following may work for you:
assert_difference('books(:ruby_book).book_loans.count', 1) do
...
end
I was having trouble with this too and just figured out how this works. Like the original post, I too was trying something like this:
# NOTE: this is WRONG, see below for the right way.
assert_difference(account.users.count, +1) do
invite.accept(another_user)
end
This doesn't work because there is no way for assert_difference to perform an action before it runs the block and after it runs the block.
The reason the string works is that the string can be evaluated to determine if the expected difference resulted.
But a string is a string, not code. I believe a better approach is to pass something that can be called. Wrapping the expression in a lambda does just that; it allows assert_difference to call the lambda to verify the difference:
assert_difference(lambda { account.users.count }, +1) do
invite.accept(another_user)
end

Resources