Ruby on Rails Rspec validation - ruby-on-rails

validation
def active_consent_run
return unless self.consent_run_id
unless [
ConsentRun::STATUS[:in_progress],
ConsentRun::STATUS[:needs_witness_signature],
ConsentRun::STATUS[:needs_researcher_signature]
].include?(self.consent_run.status)
errors.add(:consent_run_id, 'needs to be in progress')
false
end
end
rspec test
it "#active_consent_run" do
consent_run = create(:consent_run, :in_progress)
consent_question = create(:consent_run_question,
consent_run: consent_run)
expect(consent_question.valid?).to eq false
expect(consent_question.errors[:consent_run_id]).to \
eq ('needs to be in progress')
end
I have this validation and i wrote a rspec test in rails but test keeps falling. Can someone help me? Thanks! :)

OK, seems you have to create the consent_run with the status :in_progress, then create a consent_question, and only then update the status of consent_run to be invalid:
it "#active_consent_run" do
consent_run = create(:consent_run, :in_progress)
consent_question = create(:consent_run_question,
consent_run: consent_run)
# I guess it’s an active record ⇓ for illegal status
consent_run.update_column :status, 4
expect(consent_question.valid?).to eq false
expect(consent_question.errors[:consent_run_id]).to \
eq(['needs to be in progress'])
end

scope :by_trial_id, -> trial_id { joins(consent_run: :consent_form).where("consent_forms.trial_id = ?", trial_id) }
scope :by_patient_id, -> patient_id { joins(:consent_run).where("consent_runs.patient_id = ?", patient_id) }
i have these scopes and i need to write a rspec test.

Related

Rspec: Mock recaptcha verification

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)

How to know the flow of the controller method using Rspec

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.

rails model unit test trying to hit database throws nil

Any suggestions on how to resolve this test error. The database has records which I try to fetch during test and run the test against. I am using minitest 5.3.3, rails 4.1.1 app and ruby 2.0.0p247
The test output and error thrown:
Finished in 0.017919s, 167.4200 runs/s, 55.8067 assertions/s.
1) Error:
Ff::BendRateTest#test_class_method:
NoMethodError: undefined method `rate' for nil:NilClass
Which is caused by this line that uses activerecord scopes to query the database. So I try to get the record nd then get the value of the rate from the fetched record:
d = Ff::BendRate.by_counter(letter_b)
The test class:
module Ff
class BendRateTest < ActiveSupport::TestCase
def test_class_method
m = Ff::BendRate.convert('2014-05-06', 10, 'us', 'gb')
assert_equal 5, m
end
end
end
The model is shown below:
module Ff
class BendRate < ActiveRecord::Base
scope :by_counter, -> (letter) {where(letter: letter) }
def self.convert(date, amount, letter_a, letter_b)
Bender.new(date, letter_a, letter_b).converter(amount)
end
end
end
The Bender class that we instantiate in the model above:
class Bender
def initialize(date = Date.today, letter_a, letter_b)
#letter_a = letter_a
#letter_b = letter_b
#date = date
end
def attempter
baseup(#letter_a, #date)
counterup(#letter_b, #date)
end
def converter(amount = 10)
#rate = attempter
(#rate * amount.to_f).round(4)
end
private
def counterup(letter_b, date)
d = Ff::BendRate.by_counter(letter_b)
e = d.first
#counteracting = e.rate.to_f
#counter_up_rate = (#counteracting / #baserater).round(4)
end
def baseup (leter_a, date)
a = Ff::BendRate.by_counter(letter_a)
b = a.first
#baserater = b.rate.to_f
#base_to_base = ( #baserater / #baserater).round(4)
end
end
Your test do not show, why it should return 5 items, you do not have any code which insert manually or by fixtures. You should explicitly show in your tests that you added all needed records, and then check that your methods working correctly with that data.
Elsewhere my steps which I'd use to find the problem:
Write test for BendRate#converter - it should fail with the same error
Write test for Bender#attempter - it should fail with the same error, too
Write tests for scope BendRate.by_counter for two cases letter_a and letter_b - it will fail because you have not setup data
or cheater way:
def test_class_method
p Ff::BendRate.all
m = Ff::BendRate.convert('2014-05-06', 10, 'us', 'gb')
assert_equal 5, m
end

Override setter doesn't work with update_attributes

I'm making an task-manager and have an boolean attribute for 'finished'. I've tried to override the setter to implement an 'finished_at' date when i toggle 'finished' to true.
But i getting some mixed result. It doesn't work in browser but it will work in my rspec test.
Please help me out.
class TasksController < ApplicationController
# ...
def update
# ..
if #task.update_attributes(params[:task]) # where params[:task][:finished] is true
# ...
end
class Task < ActiveRecord::Base
#...
def finished=(f)
write_attribute :finished, f
write_attribute :finished_at, f == true ? DateTime.now : nil
end
end
# and in rspec i have
describe "when marked as finished" do
before { #task.update_attributes(finished: true) }
its(:finished_at) { should_not be_nil }
its(:finished_at) { should > (DateTime.now - 1.minute) }
describe "and then marked as unfinished" do
before { #task.update_attributes(finished: false) }
its(:finished_at) { should be_nil }
end
end
in browser it executes "UPDATE "tasks" SET "finished" = 't', "updated_at" = '2012-10-02 18:55:07.220361' WHERE "tasks"."id" = 17"
and in rails console i got the same with update_attributes.
But in rspec with update_attributes i get "UPDATE "tasks" SET "finished" = 't', "finished_at" = '2012-10-02 18:36:47.725813', "updated_at" = '2012-10-02 18:36:51.607143' WHERE "tasks"."id" = 1"
So I use the same method but it's only working in rspec for some reson...
using latest rails and latest spec (not any rc or beta).
Solution
Not mush i did need to edit. Thanks #Frederick Cheung for the hint.
I did notice i did like "self[:attr]" more than "write_attribute". Looks better imo.
def finished=(value)
self[:finished] = value
self[:finished_at] = (self.finished? ? Time.now.utc : nil)
end
Your setter is passed the values as they are passed to update_attributes. In particular when this is triggered by a form submission (and assuming you are using the regular rails form helpers) f will actually be "0" or "1", so the comparison with true will always be false.
The easiest thing would be to check the value of finished? after the first call to write_attribute, so that rails can convert the submitted value to true/false. It's also unrubyish to do == true - this will break if the thing you are testing returns a truthy value rather than actually true (for example =~ on strings returns an integer when there is a match)
You could use ActiveRecord Dirty Tracking to be notified of this change.
http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
class Task < ActiveRecord::Base
before_save :toggle_finished_at
def toggle_finished_at
if finished_changed?
before = changes['finished'][0]
after = changes['finished'][1]
# transition from finished => not-finished
if before == true && after == false
self.finished_at = nil
end
# transition from not finished => finished
if before == false && after == true
self.finished_at = Time.now.utc
end
end
end
end
This is a use case for a state machine. You call a :finish! event (a method) which is configured to change the state and to do whatever else needed.
https://github.com/pluginaweek/state_machine/

Rails - Triggering Flash Warning with method returning true

I'm trying to trigger a warning when a price is entered too low. But for some reason, it always returns true and I see the warning regardless. I'm sure there something wrong in the way I'm doing this as I'm really new to RoR.
In model:
def self.too_low(value)
res = Class.find_by_sql("SELECT price ……. WHERE value = '#{value}'")
res.each do |v|
if #{value} < v.price.round(2)
return true
else
return false
end
end
end
In controller:
#too_low = Class.too_low(params[:amount])
if #too_low == true
flash[:warning] = 'Price is too low.'
end
I would write it somewhat different. You iterate over all items, but you are only interested in the first element. You return from inside the iteration block, but for each element the block will be executed. In ruby 1.9.2 this gives an error.
Also i would propose using a different class-name (Class is used to define a class)
So my suggestion:
Class YourGoodClassName
def self.too_low(amount)
res = YourGoodClassName.find_by_sql(...)
if res.size > 0
res[0].price.round(2) < 1.00
else
true
end
end
end
You can see i test if any result is found, and if it is i just return the value of the test (which is true or false); and return true if no price was found.
In the controller you write something like
flash[:warning] = 'Price is too low' if YourGoodClassName.too_low(params[:amount])

Resources