Rspec - Compare two json values - ruby-on-rails

I got a response from render json: { success: 'Success' }, I met a problem when I want to test if the response received the content like this. My unit test is:
let(:success) do
{
success: "Success"
}
end
it 'responds with json containing the success message' do
expect(JSON.parse(response.body)).to eq(success)
end
I got a failure from my test, which is:
expected: {:success=>"Success"}
got: {"success"=>"Success"}
I tried to add double quote to success:'Success' so that it changed to 'success':'Success' but still have the same problem. I have two questions, the first is why there's a colon before the success, and the second is how could I remove it?

JSON.parse will have string-y keys by default.
my_hash = JSON.parse(response.body)
p my_hash.keys.first.class # String
If you want it to symbolize the keys,
my_hash = JSON.parse(response.body, symbolize_names: true)
p my_hash.keys.first.class # Symbol
Note: the option is called symbolize_names and not symbolize_keys.
Remember that a symbol is not a string:
p :success == 'success' # false

I guess you are trying to test API response with JSON format. You could try json_spec gem with many other helpful features https://github.com/collectiveidea/json_spec

Related

I'm having trouble understanding how to use webmock with a test for fetching a message from an api

I'm currently trying to write a test for this method:
#fetch message from api
def message_instance
project_id = ENV['SIGNALWIRE_PROJECT_KEY']
project_tkn = ENV['SIGNALWIRE_TOKEN']
host_url = ENV['SIGNALWIRE_HOST']
#client = Signalwire::REST::Client.new project_id, project_tkn, signalwire_space_url: host_url
message = #client.messages(sid).fetch
end
and am using FactoryBot to simulate a received message
#message = build :message, status: :received
But I can't get my head around how to test every line of the method. Hoping someone can break down how I can properly stub a request for this?
Edit: I so far I've tried this:
describe 'message_instance' do
it 'returns message instance' do
allow(ENV).to receive(:[]).with('SIGNALWIRE_PROJECT_KEY').and_return('AC123')
#message = build :message, status: 'received', sid: '123456789'
#message.message_instance.should eq #client
end
end
which returns this error:
"BUNDLER_ORIG_RUBYLIB"=>"BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL", "BUNDLER_ORIG_RUBYOPT"=>"BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL", "BUNDLE_GEMFILE"=>"/vagrant/oyetext-backend/Gemfile", "BUNDLE_BIN_PATH"=>"/usr/share/rvm/rubies/ruby-2.7.2/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/libexec/bundle", "BUNDLER_VERSION"=>"2.1.4", "RUBYOPT"=>"-r/usr/share/rvm/rubies/ruby-2.7.2/lib/ruby/2.7.0/bundler/setup", "RUBYLIB"=>"", "RAILS_ENV"=>"test", "SIGNALWIRE_PROJECT_KEY"=>"test", "SIGNALWIRE_TOKEN"=>"test", "SIGNALWIRE_NUMBER"=>"+19999999999"} received :[] with unexpected arguments
expected: ("SIGNALWIRE_PROJECT_KEY")
got: ("SIGNALWIRE_TOKEN")
Please stub a default value first if message might be received with other args as well.
I don't really think stubbing ENV is a good idea, as you could see that is used for way more stuff than your code logic, like bundler or ruby itself.
Instead of calling allow(ENV).to..., I'd try just with:
ENV['SIGNALWIRE_PROJECT_KEY'] = 'AC123'

Rspec rails api controller index test

I'm trying to test an api controller action with rspec, but since i am quite new to rspec i can't make it work.
I have the following index action: render json: #website.jobs
I have to find all associated jobs to a website by name. Here is my spec:
let(:endpoint) { FactoryGirl.create(:website) }
let(:job) { FactoryGirl.create(:job) }
subject do
get :index, params: { format: :json,
name: endpoint.name }
end
expect(subject.body).to include(:job)
Am i doing something wrong, or am i missing something ?
You cannot check the object directly with the JSON response. You can do something like this:
it "response with JSON body containing expected jobs" do
hash_body = JSON.parse(response.body)
expect(hash_body).first.to match({
id: job.id,
title: job.title
})
end
You need to change the attributes according to your Job model.
A good tutorial for testing JSON using Rspec: Pure RSpec JSON API testing
Hope this helps.

Rails JSON API tests don't return json when exception is thrown

I'm writing a test to check for invalid DELETE requests to a Rails API using Rspec.
This is what I have:
context 'invalid id number' do
it 'returns success: false' do
xhr :delete, :destroy, id: 999999999999999999
expect(JSON.parse(response.body)['success']).to be_false
end
end
Postgres throws some kind of integer overflow exception (as it should), but in my spec I can't look at the JSON object because it's never formed. How can I make it return { success : false } instead of a blank string? How do I force the JSON object to return despite the exception?
When I use pry to look at the json object, I get this error: JSON::ParserError: A JSON text must at least contain two octets! because response.body evaluates to the empty string ""
Whoa, almost forgot to include the controller code.
def destroy
if (site == ::MyModel.find(params[:id]))
site.destroy
render :json => {success: true}
else
render :json => {success: false}
end
There are two issues here:
Depending on your database an id of "999999999999999999" is probably outside of an integer type. I recommend reducing it to below signed integer limit, like 9999.
You are trying to find a non-existent record and its raising a record_not_found exception. I recommend changing your destroy method to:
def destroy
site = ::MyModel.find_by(id:params[:id])
if (site.present?)
render :json => {success: false}
else
site.first.destroy
render :json => {success: true}
end
end
EDIT
#rafb3 is correct find_by and present? is a better choice.
By the sounds of it you will need some kind of rescue statement for the exception:
rescue ArithmeticException => ex
# We need to complete the contract and return json here
#response = { success: false }
end
If you want to learn more about contracts check out this link
Remember to stay away from returning objects in failure responses, as if you send back something like site in this case and site does not exist or the database connection is not there your exception response code may have its own exception!
Also try and stay away from rescue Exception => e explanation here: Why is it a bad style to `rescue Exception => e` in Ruby?
TLDR: Your request always expects a json response so in all places even failure it should return one.

Rails: validation error codes in JSON

So, I am writing Rails web application which has JSON API for mobile apps. For example, it sends POST JSON request to example.com/api/orders to create order.
{id: 1, order: { product_name: "Pizza", price: 10000}}
In case of validation errors I can response with HTTP 422 error code and order.errors.full_messages in json. But it seems better for me to have specific error code in JSON response. Unfortunately, it seems like Rails does not provide ability to set error code for validation error. How to solve this problem?
You can pass a custom status code by using the status option when rendering the response.
def create
#order = ...
if #order.save
render json: #order
else
render json: { message: "Validation failed", errors: #order.errors }, status: 400
end
end
I usually tend to return HTTP 400 on validation errors. The message is a readable status response, the errors are also attached.
This is a respons example
{
message: "Validation failed",
errors: [
...
]
}
You can also embed additional attributes.
I was after something similar, so what I did was extend String eg
class ErrorCodeString < String
def init(value, error_code)
#error_code = error_code
super(value)
end
def error_code
#error_code
end
end
Then in a custom validation (this won't work on standard validation) I'd do
errors.add(:email, ErrorCodeString.new('cannot be blank', 50)
Now when you return your JSON you can check to see if the error value is an ErrorCodeString and add the error_code value to the output. As ErrorString inherits String, you shouldn't be breaking anything else along the way.
Rails 5 has error.details that can be used for exactly that.
In the model
errors.add(:price, 1023, message: "Transaction value #{price} is above limit (#{maximum_price}).")
In the controller
format.json { render json: #order.errors.details, status: :unprocessable_entity }
error details can be anything, eg. you could also use :above_limit instead of 1023.
The API response body will then look like
pp JSON.parse(response)
{"price"=>[{"error"=>1023}]}
This feature has been backported to Rails 4, see also http://blog.bigbinary.com/2016/05/03/rails-5-adds-a-way-to-get-information-about-types-of-failed-validations.html
Also: Is there a way to return error code in addition to error message in rails active record validation?

Getting data out of a JSON Response in Rails 3

So I am trying to pull tweets off of Twitter at put them into a rails app (Note because this is an assignment I can't use the Twitter Gem) and I am pretty confused.
I can get the tweets I need in the form of a JSON string but I'm not sure where to go from there. I know that the Twitter API call I'm making returns a JSON array with a bunch of Tweet objects but I don't know how to get at the tweet objects. I tried JSON.parse but was still unable to get the required data out (I'm not sure what that returns). Here's the code I have so far, I've made it pretty clear with comments/strings what I'm trying for. I'm super new to Rails, so this may be way off for what I'm trying to do.
def get_tweets
require 'net/http'
uri = URI("http://search.twitter.com/search.json?q=%23bieber&src=typd")
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
case response
when Net::HTTPSuccess then #to get: text -> "text", date: "created_at", tweeted by: "from_user", profile img url: "profile_img_url"
JSON.parse(response.body)
# Here I need to loop through the JSON array and make n tweet objects with the indicated fields
t = Tweet.new(:name => "JSON array item i with field from_user", :text "JSON array item i with field text", :date => "as before" )
t.save
when Net::HTTPRedirection then
location = response['location']
warn "redirected to #{location}"
fetch(location, limit - 1)
else
response.value
end
end
Thanks!
The JSON.parse method returns a ruby hash or array representing the json object.
In your case, the Json is parsed as a hash, with the "results" key (inside that you have your tweets), and some meta data: "max_id", "since_id", "refresh_url", etc. Refer to twitter documentation for a description on the fields returned.
So again with your example it would be:
parsed_response = JSON.parse(response.body)
parsed_response["results"].each do |tweet|
t = Tweet.new(:name => tweet["from_user_name"], :text => tweet["text"], :date => tweet["created_at"])
t.save
end

Resources