RSpec API controllers testing - ruby-on-rails

At first, sorry for my English :)
I need to realize API controller's tests in Ruby on Rails application (v 4.2.0).
When I do request to GET Advertising Sources I have a json response like this:
{"advertising_sources":[{"id":59,"title":"accusantium"},{"id":60,"title":"assumenda"}]}
JSON response template was defined by front-end developer.
Now I trying to create tests for:
1. JSON size (2 advert sources)
2. included attributes (id, title)
My tests:
it 'returns list of advertising sources' do
expect(response.body).to have_json_size(2)
end
%w(id title).each do |attr|
it "returns json with #{attr} included" do
hash_body = JSON.parse(response.body)
expect(hash_body).to include(attr)
end
end
Failures:
1. Failure/Error: expect(response.body).to have_json_size(2)
expected {"advertising_sources":[{"id":59,"title":"accusantium"},{"id":60,"title":"assumenda"}]} to respond to `has_json_size?`
2. Failure/Error: expect(hash_body).to include(attr)
expected {"advertising_sources" => [{"id" => 71, "title" => "necessitatibus"}, {"id" => 72, "title" => "impedit"}]} to include "id"
Diff:
## -1,2 +1,2 ##
-["id"]
+"advertising_sources" => [{"id"=>71, "title"=>"necessitatibus"}, {"id"=>72, "title"=>"impedit"}],
Can anyone help me to correctify my tests code?
Thanks!

Given the shape of your response and the characteristics you are interested in testing, you can write your tests as follows:
describe 'advertising_sources' do
let(:parsed_response_body) { JSON.parse(response.body) }
let(:advertising_sources) { parsed_response_body['advertising_sources'] }
it 'returns list of advertising sources' do
expect(advertising_sources.size).to eq(2)
end
%w(id title).each do |attr|
it "returns json with #{attr} included" do
advertising_sources.each { |source| expect(source.keys).to include(attr) }
end
end
end
I would personally simplify this even further to:
describe 'advertising_sources' do
let(:parsed_response_body) { JSON.parse(response.body) }
let(:advertising_sources) { parsed_response_body['advertising_sources'] }
it 'returns list of advertising sources' do
expect(advertising_sources.size).to eq(2)
end
it 'includes an id and title for each source' do
advertising_sources.each { |source| expect(source.keys).to match_array(%w(id title)) }
end
end

Related

file_fixture_upload always passes File object as string in rspec rails 6

I am trying to upload a file in rspec and use that file in my controller. Everything worked fine in Rails 4 but now in Rails 6, the Rack::Test::UploadedFile hash when received in controller, my tempfile filed is a File hash string object which makes it impossible to be used.
describe V1::ApplicationsApiController, type: :request do
let(:ic_front) { fixture_file_upload(file_fixture("ic_front.jpg")) }
let(:ic_back) { fixture_file_upload(file_fixture("ic_back.png")) }
context "Loan Eligibility Check Screening" do
it "runs background check v1.0.1" do
payload = {
"ic_front" => ic_front,
"ic_back_path" => ic_back,
"data" => {
"product_code" => product.code,
"include" => ["fico"],
"applicant" => {
"name" => "Florian Test 1",
"ic_or_passport_no" => "1234567890",
"consented_at" => Time.now.iso8601,
}
}
}
VCR.use_cassette('ic_check_and_ctos') do
post "/api/v1.0.1/applications/background_check", params: payload, as: :json
expect(response.status).to eq 200
end
end
end
end
Now in my controller, i get the following in params[:ic_front]
<ActionController::Parameters {"original_filename"=>"ic_front.jpg", "tempfile"=>"#<File:0x0000558457ecd5c0>", "content_type"=>nil} permitted: false>
In my controller, i want to be able to get the params[:ic_front].tempfile.path which i cannot do because tempfile is a string file hash.
I have added
include ActionDispatch::TestProcess
include Rack::Test::Methods
in my rails_helper.rb but didnt work. I tried moving into my tests but also didn't work as well.
Appreciate any new input on this

RSpec allow_any_instance_of does not work within request test

I'm new to RSpec, and testing out some webhook test with request type.
But here even I use allow_any_instance_of, it errors out got 500 instead of 200. I checked every variable with binding.pry but it seems all okay.
In my opinion, the mocking fails so it returns 500.
Any ideas?
describe "stripe_invoice_created_webhook", type: :request do
let(:card_invoice){ create(:card_invoice, id: invoice.id) }
let(:invoice){ create(:invoice, payment_account_id: payment_card_account.payment_account_id) }
let(:payment_card_account){ create(:payment_card_account,
stripe_customer_id: event.data.object.customer) }
let(:event){ StripeMock.mock_webhook_event('invoice.created', {
closed: false
}) }
it 'responds 200 to invoice_created webhook with valid endpoint' do
allow_any_instance_of(CardInvoice).to receive(:process_invoice_items)
allow_any_instance_of(CardInvoice).to receive(:process!)
post '/stripe-events', event.as_json
expect(response.status).to eq 200
expect{ card_invoice.process_invoice_items }.not_to raise_error
expect{ card_invoice.process! }.not_to raise_error
end
and the original code is
class InvoiceCreated
def call(event)
invoice = event.data.object
# NOTE: Skip if the invoice is closed.
if invoice.closed == false
stripe_customer = invoice.customer
payment_account = PaymentCardAccount.find_by(stripe_customer_id: stripe_customer)
card_invoice = Invoice.find_card_invoice_in_this_month_within(payment_account: payment_account)
card_invoice.process_invoice_items(stripe_customer: stripe_customer,
event_invoice_id: invoice.id)
card_invoice.process!(:pending, id: invoice.id)
end
end
end
Yeah the mocking fails. You are expecting the object CardVoice to receive process! or process_invoice_item but you have notnta specified a return value. The syntax for allow_any_instance_of is
allow_any_instance_of(Object).to receive(:function).and_return(:return_value)

getting httparty undefined method `code' for #<Hash:0x007ff3625a4800> in rspec

I am writing specs for my first gem. But i am stuck with this weird error.
code for my rspec is
describe '#success' do
let(:resp) { {"TwilioResponse"=>{"SMSMessage"=>{"Sid"=>"0d1c0cbfb2b5e8f97dddb4479bdbbc6a", "AccountSid"=>"exotel_sid", "From"=>"/exotel_sid", "To"=>"1234", "DateCreated"=>"2016-07-18 15:35:29", "DateUpdated"=>"2016-07-18 15:35:29", "DateSent"=>nil, "Body"=>"test sms", "Direction"=>"outbound-api", "Uri"=>"/v1/Accounts/exotel_sid/Sms/Messages/0d1c0cbfb2b5e8f97dddb4479bdbbc6a", "ApiVersion"=>nil, "Price"=>nil, "Status"=>"queued"}}} }
before{ allow(Generator::Exotel).to receive(:post).with("/#{Generator::configuration.sid}/Sms/send",
{:body => {:To => 1234, :Body => 'test sms'},:basic_auth=>{:username=>"#{Generator::configuration.sid}", :password=>"#{Generator::configuration.token}"}}).and_return(resp) }
it 'returns response object' do
response = Generator::Exotel.send(:to => 1234, :body => "test sms")
expect(response).to eq ({"Status"=>200, "Message"=>"Success"})
end
end
when i run rspec i am getting this error
NoMethodError:
undefined method `code' for #<Hash:0x007ff3625a4800>
This is where my response.code is being called
def handle_response(response)
response_base = response['TwilioResponse']
if response_base.include?('RestException')
response_base['RestException']
else
{"Status" => response.code, "Message" => "Success" }
end
end
I know httparty creates a response object for request and returns response code. But i am not getting how do i create a dummy response_code
so that my test case pass. It's nearly 2 days since i am stuck here. Anyone help please. I am really new to ruby and for first time writing spec. Any help will be appreciated.
Update - result for response.inspect
> Generator::Exotel.send(:to => 9030435595, :body => 'jsdhgjkdfg')
it returns following response
> #<HTTParty::Response:0x7fb8c02f93d0 parsed_response={"TwilioResponse"=>{"SMSMessage"=>{"Sid"=>"d6ee0650072c82941ad2f06746d14ab4", "AccountSid"=>"sinscary", "From"=>"/sinscary", "To"=>"9030435595", "DateCreated"=>"2016-07-21 19:56:07", "DateUpdated"=>"2016-07-21 19:56:07", "DateSent"=>nil, "Body"=>"jsdhgjkdfg", "Direction"=>"outbound-api", "Uri"=>"/v1/Accounts/sinscary/Sms/Messages/d6ee0650072c82941ad2f06746d14ab4", "ApiVersion"=>nil, "Price"=>nil, "Status"=>"queued"}}}, #response=#<Net::HTTPOK 200 OK readbody=true>, #headers={"content-type"=>["application/xml"], "date"=>["Thu, 21 Jul 2016 14:26:07 GMT"], "server"=>["Apache/2.2.29 (Amazon)"], "x-powered-by"=>["PHP/5.3.28"], "content-length"=>["542"], "connection"=>["Close"]}>
OK, you are mocking an HTTParty::Response. One way would be to mock it directly with only code and parsed_response:
let(:resp) do
Struct.new(:code, :parsed_response).new(200, {"TwilioResponse"=>...})
end
Another way would be to instantiate a real HTTParty::Response with:
let(:resp) do
HTTParty::Response.new(
nil,
Struct.new(:code, :parsed_response)
.new(200, {"TwilioResponse"=>...}), -> { ... }
)
end
I would go with the first approach. Please note, that you probably will need to change in handle_response:
response_base = response['TwilioResponse']
to
response_base = response.parsed_response['TwilioResponse']

RSpec - Type error - no implicit conversion of String into Integer

I'm trying to make a test green and I'm getting this error. Even when I pry the code it shows that a string is being returned so I have no idea why the test won't green. Here is my test and error for clarification. I'm assuming this is a common error for folks.
Here is my test -
require "rails_helper"
RSpec.describe "/api/retailers" do
describe "GET /api/retailers" do
it "Returns JSON for retailers" do
location = Location.create!(
city: "Portland",
street_1: "Cherry",
state: "Oregon",
zip: "49490"
)
retailer = Retailer.create!(
name: "Good Coffee Co.",
description: "Hipster Good",
image_url: "http://www.example.com/foo_bar.jpg",
location: location
)
get "/api/retailers.json"
expect(response).to be_success
json = JSON.parse(response.body)
expect(json["name"]).to eql("Good Coffee Co.")
expect(json["description"]).to eql("Hipster Good")
expect(json["image_url"]).to eql("http://www.example.com/foo_bar.jpg")
expect(json["location"]).to eql(location.city)
end
end
end
here is my error message -
/api/retailers GET /api/retailers Returns JSON for retailers
Failure/Error: expect(json["name"]).to eql("Good Coffee Co.")
TypeError:
no implicit conversion of String into Integer
# ./spec/requests/retailers_spec.rb:28:in `[]'
# ./spec/requests/retailers_spec.rb:28:in `block (3 levels) in <top (required)>'
As mentioned in the comments, the issue was that the JSON object returns an array of objects. So the proper test expectations would be the following:
expect(json.first["name"]).to eq("Good Coffee Co.")
expect(json.first["description"]).to eq("Hipster Good")
expect(json.first["image_url"]).to eq("http://www.example.com/foo_bar.jpg")
expect(json.first["city"]).to eq("Portland")
It looks like you're hitting an index endpoint which is returning an array of retailers, in your case an array with a single retailer. To access this you'll need to select the first object in the array and then do your comparisons:
expect(json[0]['name']).to eq('Good Coffee Co.')
On a similar note you should be using let and before to setup your tests. Following the rspec conventions will not only make your code more readable, but easier to maintain. I've made style changes below as to how I typically lay out my tests. These are just suggestions of course and make a couple of assumptions as to the naming and scoping of your controller.
require "rails_helper"
RSpec.describe Api::RetailersController do
# /api/retailers
describe '#index' do
let!(:location) {
Location.create!(
city: "Portland",
street_1: "Cherry",
state: "Oregon",
zip: "49490"
)
}
let!(:retailer) {
Retailer.create!(
name: "Good Coffee Co.",
description: "Hipster Good",
image_url: "http://www.example.com/foo_bar.jpg",
location: location
)
before(:each) do
get :index, format: :json
json = JSON.parse(response.body)
end
it 'Returns a status 200' do
expect(response).to be_success
end
it 'Returns a JSON list of retailers' do
expect(json.length).to eq(1)
end
# I would probably only check these in the show action
# unless needing different data on index
it 'Contains a name attribute' do
expect(json[0]['name']).to eq('Good Coffee Co.')
end
it 'Contains a description attribute' do
expect(json[0]['description']to eq('Hipster Good')
end
it 'contains an image_url attribute' do
expect(json[0]['image_url']).to eq('http://www.example.com/foo_bar.jpg')
end
it 'contains a location attribute' do
expect(json[0]['location']).to eq(location.city)
end
end
end

Stubbing out Doorkeep Token using rspec_api_documentation

I'm building an API in Rails 4 using rspec_api_documentation and have been really impressed. Having opted to use DoorKeeper to secure my endpoints, I'm successfully able to test this all from the console, and got it working.
Where I am having difficulty now is how to spec it out, and stub the token.
DoorKeeper's documentation suggests using the following:
describe Api::V1::ProfilesController do
describe 'GET #index' do
let(:token) { stub :accessible? => true }
before do
controller.stub(:doorkeeper_token) { token }
end
it 'responds with 200' do
get :index, :format => :json
response.status.should eq(200)
end
end
end
However, I've written an acceptance test in line with rspec_api_documentation. This is the projects_spec.rb that I've written:
require 'spec_helper'
require 'rspec_api_documentation/dsl'
resource "Projects" do
header "Accept", "application/json"
header "Content-Type", "application/json"
let(:token) { stub :accessible? => true }
before do
controller.stub(:doorkeeper_token) { token }
end
get "/api/v1/group_runs" do
parameter :page, "Current page of projects"
example_request "Getting a list of projects" do
status.should == 200
end
end
end
When I run the test I get the following:
undefined local variable or method `controller' for #<RSpec::Core
I suspect this is because it's not explicitly a controller spec, but as I said, I'd rather stick to this rspec_api_documentation way of testing my API.
Surely someone has had to do this? Is there another way I could be stubbing the token?
Thanks in advance.
I had the same problem and I created manually the access token with a specified token. By doing that, I was then able to use my defined token in the Authorization header :
resource "Projects" do
let(:oauth_app) {
Doorkeeper::Application.create!(
name: "My Application",
redirect_uri: "urn:ietf:wg:oauth:2.0:oob"
)
}
let(:access_token) { Doorkeeper::AccessToken.create!(application: oauth_app) }
let(:authorization) { "Bearer #{access_token.token}" }
header 'Authorization', :authorization
get "/api/v1/group_runs" do
example_request "Getting a list of projects" do
status.should == 200
end
end
end
I wouldn't recommend stubbing out DoorKeeper in an rspec_api_documentation acceptance test. One of the benefits of RAD is seeing all of the headers in the examples that it generates. If you're stubbing out OAuth2, then people reading the documentation won't see any of the OAuth2 headers while they're trying to make a client.
I'm also not sure it's possible to do this nicely. RAD is very similar to a Capybara feature test and a quick search makes it seem difficult to do.
RAD has an OAuth2MacClient which you can possibly use, here.
require 'spec_helper'
resource "Projects" do
let(:client) { RspecApiDocumentation::OAuth2MACClient.new(self) }
get "/api/v1/group_runs" do
example_request "Getting a list of projects" do
status.should == 200
end
end
end

Resources