I am trying to create a request spec for a form submission and my recaptcha verification is causing the test to fail. I have a pretty simple test:
RSpec.describe "PotentialClients", type: :request do
let(:pc_attributes) { ... }
describe "POST /potential_clients" do
it "should create record" do
expect { post potential_clients_path, params: { potential_client: pc_attributes } }
.to change(PotentialClient, :count).by(+1)
end
end
end
I run into an issue because in PotentialClients#create I make a call to verify_recaptcha? which returns false in the test instead of true:
# potential_clients_controller.rb
def create
#potential_client = PotentialClient.new(potential_client_params)
page_success = verify_recaptcha?(params[:recaptcha_token], 'lead_creation_page')
if page_success && #potential_client.save
...
end
end
# application_controller.rb
def verify_recaptcha?(token, recaptcha_action)
secret_key = ENV['CAPTCHA_SECRET_KEY']
uri = URI.parse("https://www.google.com/recaptcha/api/siteverify?secret=#{secret_key}&response=#{token}")
response = Net::HTTP.get_response(uri)
json = JSON.parse(response.body)
if json['success'] && json['score'] > RECAPTCHA_MINIMUM_SCORE && (json['action'] == "lead_creation_page" || json['action'] == "lead_creation_modal")
return true
elsif json['success'] == false && json["error-codes"].include?("timeout-or-duplicate")
return true
end
return false
end
How should I mock the call to verify_recapthca? so that my test passes? I've tried:
allow(PotentialClient).to receive(:verify_recaptcha?).and_return(true)
# and
allow_any_instance_of(PotentialClient).to receive(:verify_recaptcha?).and_return(true)
but both threw errors:
PotentialClient(...) does not implement: verify_recaptcha?
allow(PotentialClient).to receive(:verify_recaptcha?).and_return(true)
This isn't working because—as the error message says—PotentialClient (the model) doesn't have a method called verify_recaptcha?. The method is defined in ApplicationController, which is extended by PotentialClientsController, and that's where you need to mock it.
My Rails is rusty, but it looks like in an rspec-rails controller spec the current instance of the controller is exposed by the controller method. In that case, what you want is this:
allow_any_instance_of(ApplicationController).to receive(:verify_recaptcha?).and_return(true)
Related
While writing tests, I stopped at trying to test Service in another Service. In such a situation, I should probably just check if Service has been called because it has already been tested elsewhere. I did a little research on the Internet and found something like have_received but I have no idea how to use it in my example.
check_service.rb
Class CheckService
def initialize(params)
#params = params
end
def self.call(params)
new(params).call
end
def call
CheckUser.call(params[:user_id])
end
end
check_service_spec.rb
...
describe 'call' do
let(:result) { CheckService.call(params) }
let(:params) { { user_id: "100" } }
let(:check_user) { instance_double(CheckUser) }
before do
allow(check_user).to receive(:call).and_return(true)
end
it do
result
expect(check_user).to have_received(:call)
end
end
...
I was trying something like this (it's simple example), but I get error:
(InstanceDouble(CheckUser) (anonymous)).call(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
Is there any option to test situation I presented?
Short anwser
describe 'call' do
let(:result) { CheckService.call(params) }
let(:params) { { user_id: "100" } }
## let(:check_user) { instance_double(CheckUser) } delete this
before do
allow(CheckUser).to receive(:call).and_return(true)
end
it do
result
expect(CheckUser).to have_received(:call)
end
end
Alternative
I think a better way to test this is to use DI (Dependency Injection), so you pass CheckUser as a dependency to CheckService. I prefer to write the whole test inside the it block too!
class CheckService
def initialize(params, check_handler:)
#params = params
#check_handler = check_handler
end
def self.call(params, check_handler: CheckUser)
new(params, check_handler: check_handler).call
end
def call
#check_handler.call(#params[:user_id])
end
end
describe 'call' do
it 'check user with params' do
check_user = class_double(CheckUser)
allow(check_user).to receive(:call).and_return(true)
params = { user_id: "100" }
CheckService.call(params, check_handler: check_user)
expect(check_user).to have_received(:call)
end
end
A blog post to read more about -> https://blog.testdouble.com/posts/2018-05-17-do-we-need-dependency-injection-in-ruby/
require 'requiredclass'
class Test
def get_client()
return some_client
end
def intermediate_method()
res = nil
self.class
.get_client
.retry(tries:5, on: [RequiredClass::ClientTimeout]) do |myclient|
call_count += 1
res = myclient.dosomething()
end
return res
end
def method_to_test()
x = intermediate_method()
y = false
return x && y
end
end
How can I write rspec for method_to_test here. How can I mock get_client.retry as well as calls to get_client while also mocking res variable assignment so that gets assigned the value i would like it to assign.
As written, this code is difficult to test. That's a smell and a sign that the code should be restructured. Really any time you feel tempted to mock a method in the current class, that's a sign that the thing you want to mock does not belong in that class. It should be injected (passed in) instead. Like this:
require 'requiredclass'
class Test
attr_reader :client
def initialize(client)
#client = client
end
def method_to_test
x = intermediate_method
y = false
x && y
end
def intermediate_method
res = nil
client.retry(tries: 5, on: [RequiredClass:ClientTimeout]) do |my_client|
call_count += 1
res = my_client.do_something
end
res
end
end
Given this refactored code, the tests might look like this:
RSpec.describe Test do
subject(:test) { Test.new(client) }
let(:client) { instance_double(Client, retry: true, do_something: true) }
describe '#method_to_test'
subject(:method_to_test) { test.method_to_test }
it 'returns false' do
expect(method_to_test).to be_false
end
end
end
In this code I've passed a double with a stubbed retry method into the Test class on instantiation. You could optionally use a mock, instead. That would look like this:
RSpec.describe Test do
subject(:test) { Test.new(client) }
let(:client) { instance_double(Client) }
before do
allow(client).to receive(:retry)
allow(client).to receive(:do_something)
end
describe '#method_to_test'
subject(:method_to_test) { test.method_to_test }
it 'returns false' do
expect(method_to_test).to be_false
end
end
end
There's a good write up of mocks and doubles in the RSpec documentation.
I am testing out one of my controllers and have attempted to stub a function call with no luck. Here is the function:
def fetch_typeform_response
hp = HealthProfile.find(params[:id])
form = TypeformService.new('x')
response = form.getResponse("query=#{ hp[:id] }")
if response['total_items'] != 1
if response[:response_id].present?
response = form.getResponse("included_response_ids=#{ hp[:response_id] }")
end
end
if response['total_items'] == 1
response = response['items'].first
health_profile = HealthProfile.map_typeform_response(response)
if health_profile.save
health_profile.reload
redirect_to health_profile_path(health_profile), notice: "Successfully updated the health profile response."
return
end
end
redirect_to health_profiles_path, notice: "We could not locate the health profile."
end
In my test, I stub out :getResponse and :map_typeform_response since they involve an outside API:
it "expects to fetch typeform response" do
new_hp = build(:health_profile)
new_hp_after_mapping = build(:health_profile)
allow_any_instance_of(TypeformService).to receive(:getResponse).and_return({ 'total_items': 1, 'items': [ new_hp ] }.as_json)
allow_any_instance_of(HealthProfile).to receive(:map_typeform_response).and_return(new_hp_after_mapping)
get :fetch_typeform_response, params: { id: #hp.id }
expect(response).to redirect_to(health_profile_path(#hp.id))
end
But I receive the error: HealthProfile does not implement #map_typeform_response.
If I remove the stub line, I see the error:
Failure/Error: p "Using health_profile_id: #{response['hidden']['id']}"
NoMethodError:
undefined method `[]' for nil:NilClass
Which is occurring inside the :map_typeform_response function (so clearly it is called!). Any idea why this might happen?
You are calling map_typeform_response method on class HealthProfile and not on instance of the class.
change
allow_any_instance_of(HealthProfile).to receive(:map_typeform_response).and_return(new_hp_after_mapping)
to
allow(HealthProfile).to receive(:map_typeform_response).and_return(new_hp_after_mapping)
That happens because rspec prevents you from mocking or stubbing a method that does not exist on a real object. Default is true since Rails 4.
RSpec.configure do |config|
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
end
Some more recommendations
I'd also recommend to move building of new_hp and new_hp_after_mapping variables to let
let(:new_hp) { build(:health_profile) }
let(:new_hp_after_mapping) { build(:health_profile) }
move stubs to before
before do
allow_any_instance_of(TypeformService).to receive(:getResponse).and_return({ 'total_items': 1, 'items': [ new_hp ] }.as_json)
allow(HealthProfile).to receive(:map_typeform_response).and_return(new_hp_after_mapping)
end
so your test will look like
it "expects to fetch typeform response" do
# make sure variable #hp intialized in your test.
get :fetch_typeform_response, params: { id: #hp.id }
expect(response).to redirect_to(health_profile_path(#hp.id))
end
I have two dependent drop down.One gives me orgname and other drop down populates on selecting a orgname, That is teamname.
This is my github_leader_board_spec.rb
describe "github_leader_board" do
before do
#obj = DashboardsController.new
end
context "with session" do
subject { get :github_leader_board, :params => { :orgname => "test", :teamname=> "team"}}
it "returns http success" do
expect(response).to have_http_status(:success)
end
it "executes other functions" do
expect(#org_data).not_to be_nil
expect(#obj.get_team_api("DevCenter")).not_to be_nil
end
end
end
This is my controller method
def github_leader_board
myhash = {}
#points_hash = {}
member_data = []
#org_data = get_org_api
#orgs = get_names(org_data)
team_data = get_team_api(params[:orgname])
#teams = get_names(team_data)
teamid = get_team_id(team_data)
#teams.each_with_index {|k,i|myhash[k] = teamid[i]}
myhash.each do |key,value|
if key == params[:teamname]
member_data = get_members("#{value}")
end
end
#memberids = get_names(member_data)
member_names = get_member_names(#memberids)
review_comments = get_reviewcoments(#memberids)
reactions = points(#memberids)
points = [review_comments, reactions].transpose.map {|x| x.reduce(:+)}
member_names.each_with_index {|k,i|#points_hash[k] = points[i]}
end
If i run my spec file it says, undefined #org_data. The function inside the github_leader_board controller is not calling the get_org_api and storing the value to the #org_data variable.
Can anybody suggest what is wrong with the code and how can i improve it. As i'm new to ror.
Any help would be appreciated.
Thank you.
I believe you could use a test of the type controller, instead of instantiating your controller and then use the RSpec method assigns (docs) to test your instance variables, something like this:
RSpec.describe DashboardsController, :type => :controller do
context "with session" do
# ...
it "executes other functions" do
expect(assigns(:org_data)).not_to be_nil
end
end
end
https://relishapp.com/rspec/rspec-rails/docs/controller-specs
Also, if you want to check the flow, and debug your code, you can use the gems pry, pry-rails and pry-nav as #Marek Lipka stated.
My problem is I am trying to get as much coverage over my methods using rspec and I am unable to test a few certain lines. I am trying to pass a params hash to my controller method in my rspec to simulate the values from the view. Essentially, these values will filter results to be displayed on my index page.
The controller method I am testing is:
def index
#buildings = Building.all
#buildings = #buildings.searchaddress(params[:searchaddress])
if params[:searchcompany] != nil
#buildings = #buildings.searchcompany(params[:searchcompany][:management])
end
if params[:searchcompany] != nil
#buildings = #buildings.searchcity(params[:searchcity][:city])
end
if params[:searchparking] == 'on'
params[:searchparking] = 't'
#buildings = #buildings.searchparking(params[:searchparking])
end
if params[:searchpets] != nil
params[:searchpets] = 't'
#buildings = #buildings.searchpets(params[:searchpets])
end
end
I am trying to pass the params hash in my rspec test. I have tried a few ways including this one:
describe "viewing all buildings" do
it "renders index template" do
param = Hash.new()
param[:searchcompany] = [management:'asdf']
param[:searchcity] = [city:'asdf'] #have also tried {city:""}
param[:searchparking] = ['on']
param[:searchpets] = [true]
param[:searchaddress] = ['lalala']
get :index, params:param #{searchcompany:{management:'asdf'}, searchcity:{city:'asdf'}, searchparking:'on', searchpets:true}
expect(response).to render_template('index')
expect(assigns(:buildings)).to be_truthy
expect(Building).to receive(Building.searchcompany)
expect(Building).to receive(Building.searchcity)
expect(Building).to receive(Building.searchpets)
expect(Building).to receive(Building.searchparking)
end
end
The searchpets, searchcompany, etc. methods are from my Building model and are implemented as
def self.searchaddress(search)
where("address LIKE ?", "%#{search}%")
end
Here is the error I am getting:
1) BuildingsController viewing all buildings renders index template
Failure/Error:
def self.searchcity(search)
where("city LIKE ?", "%#{search}%")
ArgumentError:
wrong number of arguments (0 for 1)
# ./app/models/building.rb:39:in `searchcity'
# ./spec/controllers/buildings_controller_spec.rb:103:in `block (3 levels) in <top (required)>'
How do i pass [:searchcity][:city] to my controller method through my rspec test?
You could try:
it "renders index template" do
params = {
searchcompany: {
management: 'asdf'
},
searchcity: {
city: 'asdf'
},
searchparking: 'on'
}
# All your expectations like 'expect(sth).to receive(:method)' go here
get :index, params
# Your remaining expectations go here
end