Stub or mock instance method rspec - ruby-on-rails

The below function is a controller action and gets the data to be served from an AWS S3 bucket.
def show
s3_response = Zlib::GzipReader.new(ApiBucket.bucket.object(id).get.body).read
render json: s3_response
end
I am writing a spec for this method. I need to stub/mock such that s3_response doesn't actually request from s3. Here is what I tried. This doesn't work though. Please help.
describe '#GET show' do
let!(:resource) { create(:resource) }
before do
json_data = Api::V2::Presenter.consume_as_json(resource)
allow_any_instance_of(Zlib::GzipReader).to receive(:read).and_return(json_data)
end
it 'should return the resource in page format' do
get :show, format: :json, params: { id: resource.uuid }
response_body_json = JSON.parse(response.body)
json_data = Api::V2::Presenter.consume_as_json(
Api::V2::ResourcePresenter.new(resource).page,
true
)
expect(response_body_json).to eql(JSON.parse(json_data))
end
end
I am getting this error Zlib::GzipFile::Error: not in gzip format

Instead of stubbing Zlib::GzipReader or the S3 Bucket. One simple and easy way to handle such cases would be to create a new private function in the controller and then stub the controller function.
In the controller side:
def show
render json: s3_response
end
private:
def s3_response
Zlib::GzipReader.new(ApiBucket.bucket.object(id).get.body).read
end
The spec will be:
describe '#GET show' do
let!(:resource) { create(:resource) }
before do
json_data = Api::V2::Presenter.consume_as_json(resource)
ResourceController.any_instance.stub(:s3_response).and_return(json_data)
end
it 'should return the resource in page format' do
get :show, format: :json, params: { id: resource.uuid }
response_body_json = JSON.parse(response.body)
json_data = Api::V2::Presenter.consume_as_json(
Api::V2::Presenter.new(resource).page,
true
)
expect(response_body_json).to eql(JSON.parse(json_data))
end
end

Related

Rails + Rspec: Test that service is called

I am trying to write a test to ensure that my service, WeeklyReportCardService, is instantiated and that it's method :send_weekly_report_card_for_repositioning is called.
Here's the controller:
def update
Audited.audit_class.as_user($user) do
if #check_in.update(check_in_params)
client = Client.find_by(id: check_in_params[:client_id])
if #check_in.repositioning.present? && #check_in.weigh_in.present? && #check_in.client&.location&.name == "World Wide"
# I see this in the console so the if statement returns true
p "hitting send!!"
WeeklyReportCardService.new.send_weekly_report_card_for_repositioning(#check_in.repositioning)
end
render json: #check_in, status: :ok, serializer: API::CheckInsIndexSerializer
else
render json: #check_in.errors.full_messages, sattus: :unprocessable_entity
end
end
end
Here's my test:
RSpec.describe API::CheckInsController, type: :request do
fit "should send if the client's location is World Wide" do
program = create(:program, :with_client)
worldwide = create(:location, name: "World Wide")
program.client.update(location_id: worldwide.id)
check_in = create(:check_in, client_id: program.client.id, program_id: program.id)
create(:repositioning, check_in_id: check_in.id)
create(:weigh_in, check_in_id: check_in.id)
url = root_url[0..-2] + api_check_in_path(check_in.id) + "?sendReportCardEmail=true"
put url, params: { check_in: {type_of_weighin: 'standard'}}, headers: { "HTTP_AUTHENTICATION": #token }
expect_any_instance_of(WeeklyReportCardService).to receive(:send_weekly_report_card_for_repositioning)
end
end
and the error I see is:
Failure/Error: DEFAULT_FAILURE_NOTIFIER = lambda { |failure, _opts| raise failure }
Exactly one instance should have received the following message(s) but didn't: send_weekly_report_card_for_repositioning
What else do I need to do to ensure that function is called?
You're doing it in the wrong order. You need to set the expectation first before the method is expected to be called:
expect_any_instance_of(WeeklyReportCardService).to receive(:send_weekly_report_card_for_repositioning)
put url, params: { check_in: {type_of_weighin: 'standard'}}, headers: { "HTTP_AUTHENTICATION": #token }
If you need to set the expecations afterwards you need to replace the method or object with a spy which is useful if you prefer the arrange-act-assert (or given-when-then)
pattern for structuring tests.
You should also note that the use of any instance is strongly discouraged and you can avoid it by providing a simple class method:
class WeeklyReportCardService
def self.send_weekly_report_card_for_repositioning(...)
new.send_weekly_report_card_for_repositioning(...)
end
end
RSpec.describe API::CheckInsController, type: :request do
it "should send if the client's location is World Wide" do
expect(WeeklyReportCardService).to receive(:send_weekly_report_card_for_repositioning)
put url, params: { check_in: {type_of_weighin: 'standard'}}, headers: { "HTTP_AUTHENTICATION": #token }
end
end
Or alternatively by stubbing the WeeklyReportCardService#new method to return a mock or spy.

RSpec returns empty string in a fairly simple get request

I want to test show action in my Shipment controller. To do so I've prepared fairly simple specs:
RSpec.describe ShipmentsController, type: :controller do
describe 'GET #show' do
let(:params) { { id: shipment.id, product_id: product.id } }
let!(:product) { create(:product) }
let!(:shipment) { create(:shipment, product: product) }
context 'when params are valid' do
before { get :show, params: params }
it 'return valid json' do
expect(JSON.parse(response.body)).to eq(expected_json)
end
end
end
end
ShimpentsController.rb
class ShipmentsController < ApplicationController
before_action :set_product
attr_reader :shipment
def show
#shipment = Shipment.find(params[:id])
#items = shipment&.grouped_shipment_items
end
private
def set_product
#product = Product.find(params[:product_id])
end
end
When I use postman everything went well - it returns expected json but in the RSpec test I'm getting:
response.body
=> ""
I think you need to add render_views in your controller spec file.
RSpec.describe ShipmentsController, type: :controller do
render_views
describe 'GET #show' do
let(:params) { { id: shipment.id, product_id: product.id } }
let!(:product) { create(:product) }
let!(:shipment) { create(:shipment, product: product) }
context 'when params are valid' do
before { get :show, params: params }
it 'return valid json' do
expect(JSON.parse(response.body)).to eq(expected_json)
end
end
end
end
Reference: https://rubyinrails.com/2019/04/11/rails-test-jbuilder-json-response-with-rspec/
I think you are not making a request for JSON response with rspec. You can check by putting a breakpoint in your controller action, then checking
request.format.json?
In order to ask for JSON response from an rspec test, you should add as: :json to the end of the request. The request should look like this:
get :show, params: params, as: :json

Devise Signin Testing with RSpec for API

authenticate_with_http_token do |token, options|
auth_key = AuthKey.find_by(authentication_token: token)
if auth_key.present?
if auth_key.token_valid?
auth_key.touch
sign_in(:user, auth_key.user, store: false, bypass: false) unless current_user.present?
else
render json: { message: t('invalid_otp_access'), errors: [t('token_expired')] }, status: 401 and return
end
else
render json: { message: t('invalid_access_message'), errors: [t('invalid_access')] }, status: 401 and return
end
end
i need to write spec for the above code, in my controller i am using current_user.
My controller looks like below.
def index
schedules = params[:type] == "upcoming" ? :upcoming : :past
schedules = current_user.audit_schedules.send(schedules)
if schedules.present?
paginate json: schedules, per_page:10, root: false, each_serializer: Api::V1::MyAuditSerializer
else
render json: { message: t('.no_audits_scheduled'), errors: [] }
end
end
and i am trying to test my index with passing valid token and params
context "with invalid attributes" do
it "It will return list of audits" do
request.headers["Authorization"] = "Token #{auth_key.authentication_token}"
#request.env["devise.mapping"] = Devise.mappings[:user]
get :index, { params: { type: "upcoming" } }
expect(response.body).to eq 200
end
end
the above spec returning body like
<html><body>You are being redirected.</body></html>
And in my spec helper i included devise helpers like
config.include Devise::TestHelpers, type: :controller
If i remove that helper current_user is always nil. if i add that helper it is redirecting like above, please let me know what i missed and how can i test those spec.
I think you want user_signed_in? vs. current_user.present?. This doesn't fix the problem.
You're sure user is not null? and that the user has been confirmed if you're using confirmable?
Digging through the code, I see this:
if options[:bypass]
warden.session_serializer.store(resource, scope)
elsif warden.user(scope) == resource && !options.delete(:force)
# Do nothing. User already signed in and we are not forcing it.
true
else
warden.set_user(resource, options.merge!(scope: scope))
end
source: https://github.com/hassox/warden/blob/906edf86c6c31be917a921097031b89361d022e8/lib/warden/proxy.rb
You can try adding :force which should force the setting of the user.

Figuring out the plan to write an RSpec test

I have a controller as following:
class ReportsController < ApplicationController
respond_to :html
def show
client = ReportServices::Client.new(ServiceConfig['reports_service_uri'])
#report = client.population_management(params[:id])
if #report
#kpis = #report[:key_performance_indicators]
#populations = #report[:population_scores]
#population_summaries = #report[:population_summaries]
#providers = #report[:provider_networks]
end
respond_with (#report)
end
end
Which I want to write a RSpec test for it but have no clue where to start from, I guess because it has that URL in it, it makes it harder for me, I am pretty new to Rails and RSpec and have some basic knowledge of writing RSpec for my Models but this one has puzzled me for the whole weekend.
So the first thing to tackle is mocking the external API request. The general idea here is that your going to return a mock object from new that will respond to population_management and return what you expect for #report.
describe ReportsController do
before do
#report_data = {
:key_performance_indicators => 'value',
:population_scores => 'value',
:population_summaries => 'value',
:provider_networks => 'value'
}
# This will fake the call to new, return a mock object that when
# population_management is called will return the hash above.
#fake_client = double(:population_management => #report_data)
ReportServices::Client.stub(:new => #fake_client)
end
describe '#show' do
it 'assigns #report' do
get :show, id: 1
assigns(:report).should == #report
end
it 'assigns some shortcut vars' do
[:kpis, :populations, :population_summaries, :providers].each do |var|
assigns(var).should_not be_nil
end
end
# and whatever else you'd like
end
end
You can stub the client interface to write an isolated test of your controller.
describe RerpotsController do
it "assigns a new report as #report" do
expected_id = '1234'
expected_kpi = 'kpi'
report = { key_performance_indicators: expected_kpi, ... }
client = double(ReportServices::Client)
client.should_receive(:population_management).with(expected_id) { report }
ReportServices::Client.should_receive(:new) { client }
get :show, id: expected_id
assigns(:kpis).should eq(expected_kpi)
# ...
end
end
You probably don't need to unpack the report in the controller.

Why is AJAX in Rails 3 so hard? Or, what am I doing wrong?

None of the tutorials I seem do what I'm trying to do. Very simply, I want a user to be able to submit a POST request to a controller (to "LIKE" a video) and have the controller respond back with a JSON object. Any help would be appreciated.
Thanks
EDIT Because SO is messing the formatting up, here is a gist of my code too:
https://gist.github.com/813503
Here is my controller:
class LikesController < ApplicationController
before_filter :get_ids
respond_to :json, :js
def videolink
results = {}
# check to see if the user has liked this videolink before
if current_user
liked = Like.video?(current_user, #vid_id)
results["status"] = "OK"
results["liked"] = liked
else
results["status"] = "Error"
results["message"] = "User not logged in"
end
respond_with( results.to_json )
end
def update
results = {}
if current_user
results["status"] = "OK"
else
results["status"] = "Error"
results["message"] = "User not logged in"
end
respond_with( results.to_json )
end
private
def get_ids
#vid_id = params[:videolink_id]
end
end
Here is my JS file:
$("#likeVideo").click(function() {
$.ajax({
contentType: "application/json",
data: { game_id: game_id, videolink_id: current_video["videolink"]["id"] },
dataType: "json",
type: "POST",
url: "/likes/" + game_id,
success: function(data) {
console.log("Success", data);
}
});
return false;
});
My routes:
resources :likes do
collection do
get "videolink"
end
member do
post :update
end
end
And here is the error I get:
NoMethodError
in LikesController#update
undefined method `{"status":"OK"}_url' for #<LikesController:0x0000010178be58>
If you want to send back custom JSON, Instead of respond_with(results.to_json)... just render the text
render :text=>results.to_json
The responds_with is a way for you to easily send back objects, with their location (url). So that's why your error is telling you that that '_url' is invalid.
More info on responds_with, courtesy of http://ryandaigle.com/articles/2009/8/10/what-s-new-in-edge-rails-default-restful-rendering
If another format was requested, (i.e.
:xml or :json)
If it was a GET request, invoke the :to_format method on the resource and
send that back
If the resource has validation errors, send back the errors in the
requested format with the
:unprocessable_entity status code
If it was a POST request, invoke the :to_format method on the resource
and send that back with the :created
status and the :location of the new
created resource
Else, send back the :ok response with no body

Resources