I am new with Ruby/Rails and the testing frameworks within Ruby/Rails. I have a pre-validation method (external API) that validates the incoming request. For all test cases, I want to stub that call and test the remaining functionalities.
I am knowledgeable about testing and mocks/stubs/spies (mostly Mockito/Powermockito stuffs), but do not know my way around Rails testing. I tried looking into RSpec / MiniTest stuffs, but it is getting overwhelming.
I have a controller method like this:
def handler
# validate
request_validated = validate_request
unless request_validated
head :unauthorized
return
end
#... remaining codes
end
def validate_request
# validation with external API
end
I have controller tests set up using ActionController::TestCase. Prior to adding the validation stuffs, all my test cases have tested out. But I cannot stub around the validation check.
I would want to have something like
controller.stub(validate_request).then_and_return(true) # need something of this sort
post :handler, as: :json, params: completed_service_parameters
assert_response :no_content
I'm open to using any library, though would prefer to use any Rails in-built, if there's anything. Thanks.
I ended up using 'minitest/stub_any_instance'
require 'minitest/stub_any_instance'
...
test 'test stub' do
...
Controller.stub_any_instance(:function, return-value) do
# perform the call within the stubbed block
post :function, as: :json, params: { :param1 => 'random' }
end
...
end
Related
I'm wanting to mock HTTP calls made by Geocoder. I found this post - How can I optionally mock geocoder?
Here is my model
class Listing < ActiveRecord::Base
geocoded_by :address
before_save :geocode, :if => :address_changed?
end
There were two recommendations: VCR (for HTTP requests only?), and Mocha.
I like the looks of Mocha as I can use this for other objects (e.g. models), not just HTTP requests. However, the poster wasn't familiar with Geocoder and didn't know what methods of it to mock, neither do I - still a little new to Rails. Not really sure how/where to trace method calls.
My test looks like this:
test "should create listing" do
sign_in #current_user
assert_difference('Listing.count') do
post :create, listing: valid_params
end
listing = Listing.last
# confirm lat/lng are set
assert_not_nil listing.latitude
assert_not_nil listing.longitude
assert_redirected_to listing_path(assigns(:listing))
end
The test passes, but it can be a little slow to run as it is doing the HTTP request during the test. Would be good to just mock this part in Geocoder.
Alternatively I'll give VCR a go if Geocoder doesn't work with Mocha.
You can stub geocode method on you model it would be enought. For example:
Listing.any_instance.stub(:geocode).and_return([1,1])
Or
allow(Listing).to receive(:geocode).and_return([1,1])
For test unit I guess you should write
Listing.stubs(:geocode).returns([1,1])
I am testing a controller and would like to force a db update to fail in order to verify that my error handling is working properly. I am fairly new to rails so I apologize if I am not following all of the best practices. Below is the relevant code:
Code is not complete as to focus on the important parts relevant to this question.
Controller:
class SomeController < ApplicationController
...
# relevant actions
def enable
able true
end
def disable
able false
end
...
private
def able (b)
...
# #dbobject will be set in a 'before_filter' function
if #dbobject.update_attribute(enabled: b)
# do some stuff
else # <------ need to force execution of this block
# error handling, logging, boring stuff
redirect_to #dbobject
...
end
...
end
Test:
class SomeController::AbleTest < ActionController::TestCase
tests SomeController
setup
# create #dbobject
end
test 'redirect occurs on update fail' do
get :enable, id: #dbobject
assert_redirected_to #dbobject
end
...
end
I also have tests in SomeController::AbleTest that require update_attribute to work properly so I would prefer to stay away from overriding the method completely. Is there any way to force the db to raise an exception if this record is accessed or something similar? I am not able to call the able method directly from the test class because it relies heavily on instance variables set by various before_filter methods and it feels too much like fighting the framework to not make the get :enable and have these methods run automatically.
You can use stubs. Look into mocha:
DBObject.any_instance.stubs(:update_attribute).returns(false)
This would mean whenever you can update_attribute on any instance of DBObject, it would return false, sending you into the else. So teh whole code would be:
test 'redirect occurs on update fail' do
DBObject.any_instance.stubs(:update_attribute).returns(false)
get :enable, id: #dbobject
assert_redirected_to #dbobject
end
Try using the mocha gem. You can temporarily stub update_attributes so it returns false.
dbobject.stubs(:update_attributes).returns(false)
Note that dbobject isn't the same as the #dbobject you're passing to get in your test. You'll have to stub the code that fetches the record in your controller. This is more troublesome than Yule's way of any_instance, so try that first.
I am using rspec-rails and I want to test that my mailer is rendering the correct view template.
describe MyMailer do
describe '#notify_customer' do
it 'sends a notification' do
# fire
email = MyMailer.notify_customer.deliver
expect(ActionMailer::Base.deliveries).not_to be_empty
expect(email.from).to include "cs#mycompany.com"
# I would like to test here something like
# ***** HOW ? *****
expect(template_path).to eq("mailers/my_mailer/notify_customer")
end
end
end
Is this a valid approach? Or shall I do something completely different to that?
Update
MyMailer#notify_customer might have some logic (e.g. depending on the locale of the customer) to choose different template under different circumstances. It is more or less similar problem with controllers rendering different view templates under different circumstances. With RSpec you can write
expect(response).to render_template "....."
and it works. I am looking for something similar for the mailers.
I think this is a step closer to the answer above, since it does test for implicit templates.
# IMPORTANT!
# must copy https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/support/helpers/next_instance_of.rb
it 'renders foo_mail' do
allow_next_instance_of(described_class) do |mailer|
allow(mailer).to receive(:render_to_body).and_wrap_original do |m, options|
expect(options[:template]).to eq('foo_mail')
m.call(options)
end
end
body = subject.body.encoded
end
OK, I understand what you're trying to achieve now.
You should be able to test which template is called by setting expectations on your mailer for the mail method having been called with particular arguments.
Try this in your test:
MyMailer.should_receive(:mail).with(hash_including(:template => 'expected_template'))
I have a Rails app with an Rspec test suite which has some feature/controller tests depending on ElasticSearch.
When we test the "search" feature around the system (and other features depending on ES) we use a real ES, it works perfectly at development environment when we're running single spec files.
When the suite runs at our CI server it gets weird, because sometimes ES won't keep in sync fast enough for the tests to run successfully.
I have searched for some way to run ES in "syncronous mode", or to wait until ES is ready but haven't found anything so far. I've seen some workarounds using Ruby sleep but it feels unacceptable to me.
How can I guarantee ES synchronicity to run my tests?
How do you deal with ES on your test suite?
Here's one of my tests:
context "given params page or per_page is set", :elasticsearch do
let(:params) { {query: "Resultados", page: 1, per_page: 2} }
before(:each) do
3.times do |n|
Factory(:company, account: user.account, name: "Resultados Digitais #{n}")
end
sync_companies_index # this is a helper method available to all specs
end
it "paginates the results properly" do
get :index, params
expect(assigns[:companies].length).to eq 2
end
end
Here's my RSpec configure block and ES helper methods:
RSpec.configure do |config|
config.around :each do |example|
if example.metadata[:elasticsearch]
Lead.tire.index.delete # delete the index for a clean environment
Company.tire.index.delete # delete the index for a clean environment
example.run
else
FakeWeb.register_uri :any, %r(#{Tire::Configuration.url}), body: '{}'
example.run
FakeWeb.clean_registry
end
end
end
def sync_companies_index
sync_index_of Company
end
def sync_leads_index
sync_index_of Lead
end
def sync_index_of(klass)
mapping = MultiJson.encode(klass.tire.mapping_to_hash, :pretty => Tire::Configuration.pretty)
klass.tire.index.create(:mappings => klass.tire.mapping_to_hash, :settings => klass.tire.settings)
"#{klass}::#{klass}Index".constantize.rebuild_index
klass.index.refresh
end
Thanks for any help!
Your test is confused - it's testing assignment, pagination, and (implicitly) parameter passing. Break it out:
Parameters
let(:tire) { double('tire', :search => :sentinel) }
it 'passes the correct parameters to Companies.tire.search' do
expected_params = ... # Some transformation, if any, of params
Companies.stub(:tire).with(tire)
get :index, params
expect(tire).to have_received(:search).with(expected_params)
end
Assignment
We are only concerned that the code is taking one value and assigning it to something else, the value is irrelevant.
it 'assigns the search results to companies' do
Companies.stub(:tire).with(tire)
get :index, params
expect(assigns[:companies]).to eq :sentinel
end
Pagination
This is the tricky bit. You don't own the ES API, so you shouldn't stub it, but you also can't use a live instance of ES because you can't trust it to be reliable in all testing scenarios, it's just an HTTP API after all (this is the fundamental issue you're having). Gary Bernhardt tackled this issue in one of his excellent screencasts - you simply have to fake out the HTTP calls. Using VCR:
VCR.use_cassette :tire_companies_search do
get :index, params
search_result_length = assigns[:companies].length
expect(search_result_length).to eq 2
end
Run this once successfully then forever more use the cassette (which is simply a YAML file of the response). Your tests are no longer dependent on APIs you don't control. If ES or your pagination gem update their code, simply re-record the cassette when you know the API is up and working. There really isn't any other option without making your tests extremely brittle or stubbing things you shouldn't stub.
Note that although we have stubbed tire above - and we don't own it - it's ok in these cases because the return values are entirely irrelevant to the test.
Anyone have any tips for best practices for mocking out facebook requests in functional tests? Is it just as simple as adding all of the proper params to the request? Is there a way to stub those out?
I'm using facebooker, which comes with a mock service:
# A mock service that reads the Facebook response from fixtures
# Adapted from http://gist.github.com/44344
#
# Facebooker::MockService.fixture_path = 'path/to/dir'
# Facebooker::Session.current = Facebooker::MockSession.create
But when I write a basic get test, it tries to redirect the browser to the facebook page for adding the app, which I assume indicates that the mocking isn't working.
test "loads respondent" do
Facebooker::Session.current = Facebooker::MockSession.create
get :index
puts #response.body # => <html><body>You are being redirected.</body></html>
end
I got this working with the latest version of facebooker (1.0.58):
# test_helper.rb
require 'facebooker/mock/session'
require 'facebooker/mock/service'
Facebooker::MockService.fixture_path = File.join(RAILS_ROOT, 'test', 'fixtures', 'facebook')
Obviously you will have to create the facebook directory in fixtures, or put it wherever. Inside you have to add a folder for each facebook method, and an xml file for the different types of responses you want to test for. I had to add facebook.users.getInfo and facebook.users.hasAppPermission. The easiest is just to add a file named default.xml with the example code from the facebook wiki for those actions.
# Controller test
test "facebook action" do
get :index, {:fb_sig_added => true}, :facebook_session => Facebooker::MockSession.create
assert_response :success
end
The fb_sig_added param is necessary as far as I can tell, because the internal facebooker logic checks the params directly before checking the session on that one. Which seems a bit wanky to me but maybe there's a reason for that.