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.
Related
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
I have a situation to render a HTML template outside of controller(A class under service/lib directory) and I am rendering the template using the below code.
class SomeClass
def some_method
#template = ApplicationController.render(
template: 'template',
layout: mailer_template,
)
end
end
Is there any ways to test if the rendered template is the expected one and whether render happened during that method call?
EDIT
class BatchSendingService < AbstractController::Base
require 'abstract_controller'
include AbstractController::Rendering
include AbstractController::AssetPaths
include AbstractController::Helpers
include Rails.application.routes.url_helpers
include ActionView::Rendering
include ActionView::ViewPaths
include ActionView::Layouts
self.view_paths = "app/views"
def send_batch_email(mail, domain)
#project = mail.project
#client = Mailgun::Client.new ENV['MAILGUN_API_KEY']
batch_message = Mailgun::BatchMessage.new(#client, domain)
batch_message.from(from_data)
mailer_layout = get_mailer_layout(mail.layout)
mail_html = render(
template: 'send_batch_email',
layout: mailer_layout
)
batch_message.body_html(mail_html.to_s)
batch_message.add_recipient(:to, recipient_email, {})
response = batch_message.finalize
end
EDIT
obj= BatchSendingService.new
allow(obj).to receive(:render)
BatchSendingService.send_batch_email(mail, domain)
expect(obj) .to have_received(:render)
.with({ template: "template", layout: "layout" })
By using the class where the instance method is called, the error is gone.
ActionController.render is a well tested method. The Rails Core Team saw to that. There's no need to test that it does what it says it does.
Rather, what you want to do is to make sure you called ActionController.render with the right parameters, using mock objects, like this:
describe SomeClass do
subject(:some_class) { described_class.new }
describe '#some_method' do
let(:template) { 'template' }
let(:layout) { 'mailer_template' }
before do
allow(ActionController).to receive(:render)
some_class.some_method
end
it 'renders the correct template' do
expect(ActionController)
.to have_received(:render)
.with({ template: template, layout: layout })
end
end
end
EDIT
Given the edited post, here's how I would approach the test. Note that not all of the code in your send_batch_email method is visible in your edit. So, YMMV:
describe BatchSendingService do
subject(:batch_sending_service) { described_class.new }
describe '#send_batch_email' do
subject(:send_batch_email) do
batch_sending_service.send_batch_email(email, domain)
end
let(:email) { 'email' }
let(:domain) { 'domain' }
let(:batch_message) do
instance_double(
Mailgun::BatchMessage,
from: true,
body_html: true,
add_recipient, true,
finalize: true
)
end
let(:template) { 'send_batch_template' }
let(:layout) { 'layout' }
before do
allow(Mailgun::Client).to receive(:new)
allow(Mailgun::BatchMessage)
.to receive(:new)
.and_return(batch_message)
allow(batch_sending_service)
.to receive(:render)
send_batch_email
end
it 'renders the correct template' do
expect(batch_sending_service)
.to have_received(:render)
.with(template, layout)
end
end
end
I'm new to programming and sorry if this is a stupid question. I'm writing a test for controllers in Ruby on Rails. The test is expecting a parameter but I'm not sure how to do that. When I run the test with rspec I get the error you see in the title.
This is PART of the controller's code:
class DemographicDetailController < ApplicationController
#before_filter :authorize
include ::Demographic
def show
#cinema_id = params[:cinema_id]
#cinema = Cinema.find(#cinema_id)
#cinema_name = #cinema.name
#cinmea_address = #cinema.address.full_street_address
#cinema_authority_id = #cinema.address.post_code.local_authority_id
#c_working_age = Population.where(:local_authority_id => #cinema_authority_id , :year => Population.maximum("year")).first
#c_working_age = #c_working_age.working_age_16_to_64
#c_total_population = totalpopulation(#cinema_authority_id)
#c_average_income = latestaverageincome(#cinema_authority_id)
#all_cinemas = Cinema.all
(...)
end
and this is the test I wrote for show method:
describe "Demographic" do
context "when testing the DemographicDetail controller" do
#let(:params) { { disabled: false } }
let(:cinema_id) { create(:cinema) }
it "should show demoraphic details successfully" do
get :show, params: { id: #cinema_id }
assert_response :success
end
end
This is the route:
controller :demographic_detail do
get '/demographic_detail/show/:cinema_id' => :show
end
Try this
modify the route as
get '/demographic_detail/show' => 'demographic_detail#show'
Try this:
get '/demographic_details/:cinema_id' => 'demographic_detail#show'
or
controller :demographic_detail do
get '/demographic_detail/:cinema_id' => :show
end
My controller has an around_action filter on its update action, to trigger specific behavior if a particular attribute is updated. Something like below:
class EventsController < ApplicationController
around_action :contact_added_users
def contact_added_users
#event = Event.find(params[:id])
existing_users = #event.users
yield
added_users = #event.users.reject{|u| existing_users.include? u }
added_users.each { |u| u.contact }
end
end
I've verified that it works manually, but how can I test my around_action filter in Rspec? I've tried something like:
describe EventsController do
describe "PUT update" do
let(:event) { FactoryGirl.create(:event) }
let(:old_u) { FactoryGirl.create(:user) }
let(:new_u) { FactoryGirl.create(:user) }
before(:each) { event.users = [ old_u ]
event.save }
context "when adding a user" do
it "contacts newly added user" do
expect(new_u).to receive(:contact)
expect(old_u).not_to receive(:contact)
event_params = { users: [ old_u, new_u ] }
put :update, id: event.id, event: event_params
end
end
...but it fails. Also tried adding
around(:each) do |example|
EventsController.contact_added_users(&example)
end
but still no dice. How can I test this correctly?
I'd suggest stubbing the call to Event and returning a double that can respond to :users with the results you need for the spec to pass. The trick is that :users must be called twice with different results. RSpec allows you to pass a list of values that will be returned for successive calls:
let(:existing_users) { [user_1, user_2] }
let(:added_users) { [user_3] }
let(:event) { double('event') {
before(:each) do
Event.stub(:find).with(params[:id]) { event }
event.should_receive(:users).exactly(2).times.and_return(existing_users, added_users)
end
Still working on my Rails/MongoDB app, I have yet another problem.
This time, I can create embedded documents, but can't delete them, though I've been doing what has been said in another Stackoverflow topic (http://stackoverflow.com/questions/3693842/remove-an-embedded-document-in-mongoid)
Here goes my controller :
class FeedSubscribtionsController < ApplicationController
has_to_be_connected
def create
if session[:user_id] != params[:id]
#self = current_user
attributes = { :user => #self, :userId => params[:id], :feedId => params[:feed] }
subscribtion = FeedSubscribtion.create attributes
success = subscribtion.save
render json: { :success => success, :feed => params[:feed] }
end
end
def destroy
success = false
if session[:user_id] != params[:id]
#self = current_user
uid, fid = params[:id], params[:feed]
#feed = #self.feed_subscribtions.where :userId => uid, :feedId => fid
if #feed.count > 0
#self.feed_subscribtions.delete #feed.first.id.to_s
success = #feed.first.save
end
end
render json: { :success => success, :feed => params[:feed] }
end
end
The weirdest part is that everything seems to go well : success is equal to true in the rendered JSON object.
I also tried to replace "success = #feed.first.save" with "#self.save" : in that case, it returns false, but with no further explanations.
(I do know that for the logic behind this controller to be perfect, I should loop on the #feed array, and I will once it starts working ^^ it's just easier to debug that way)
So, is there any way I may find out why #ßelf.save fails, or why #feed.first.save doesn't fail but doesn't actually save either ?
Thanks.
Here is what you're doing as I see it
You are using FeedSubscriptionsController to delete the object with id if #feed.first, then you try and save #feed.first, but #feed.first points to an already deleted object, so it's failing to save it.