How can I stub method in rspec model - ruby-on-rails

I have a below method in my model and I want to stub the value of below
"esxi_hosts = RezApi.new.get_esxi(type, vrealm_id)"
with values like [{x: "y"}]
what is the way to do it in rspec.
def create_esxi
if (["vRealm", "Praxis Parent vRealm", "Praxis Child vRealm"].include?(self.collection_type.try(:name)))
esxi_hosts = []
if(( self.parent && self.parent.parent && self.parent.collection_type.name.upcase == "POD" && self.parent.parent.collection_type.name.upcase == "DATACENTER") or ( self.parent && self.parent.parent && self.parent.parent.parent && (self.parent.collection_type.name.upcase == "RELEASE - PRAXIS" or self.parent.collection_type.name.upcase == "RELEASE - SUBSCRIPTION") && self.parent.parent.collection_type.name.upcase == "POD" && self.parent.parent.parent.collection_type.name.upcase == "DATACENTER"))
vrealm_type = collection_type.try(:name)
vrealm_id = "d#{self.parent.parent.instance}p#{self.parent.instance}v#{instance}"
case vrealm_type
when "vRealm"
types = ["vrealm-multitenant-dr2c", "vpc-standard"]
types.each do |type|
if esxi_hosts.empty?
esxi_hosts = RezApi.new.get_esxi(type, vrealm_id)
end
end
when "Praxis Parent vRealm"
esxi_hosts = RezApi.new.get_esxi("praxis-core", vrealm_id)
when "Praxis Child vRealm"
esxi_hosts = RezApi.new.get_esxi("praxis-node-mgmt", vrealm_id)
end
end
if esxi_hosts.flatten.any?
assign_esxi(esxi_hosts)
end
end
end
I have already tried the below code but it didnt work
require 'spec_helper'
describe "Esxi Host creation from Rez Api" do
let(:federation) {create(:federation_collection, parent_id: nil, name: "Test", usage: "Tech Ops hosted in Vmware Data Centers", owner: "--", date_from: "2015-08-26", date_to: nil, collection_type_id: 2, zone_name: "se.vpc.vmw")}
let(:datacenter) {create(:datacenter_collection, name: "Datacenter", parent_id: federation.id)}
let(:pod) {create(:pod_collection, parent_id: datacenter.id)}
let(:fqdn) {"d2p1s0ch10srv0v101-esx0.se.vpc.vmw"}
let(:vrealm1) {create(:vrealm_collection, name: "vRealm1", parent_id: pod.id)}
let(:vrealm2) {create(:vrealm_collection, name: "vRealm2", parent_id: pod.id)}
context "When response has esxi hosts " do
let(:rez_response) {[{"esx0"=>{"nodefqdn"=>"d2p1s0ch10srv0.se.vpc.vmw","fqdn"=>"d2p1s0ch10srv0v101-esx0.se.vpc.vmw","vmk0"=>{"pg_name"=>"d2p1v101-esx-pg-1062","ip_addr"=>"10.141.112.71","netmask"=>"255.255.255.0",},"vmk1"=>{"pg_name"=>"d2p1pod-sto-pg-17","ip_addr"=>"172.16.160.51","netmask"=>"255.255.252.0"},"vmk2"=>{"pg_name"=>"d2p1v101-ftx-pg-1063","ip_addr"=>"172.16.165.126","netmask"=>"255.255.255.0"}}}]}
subject {RezApi.new}
it "should create esxi hosts" do
allow(subject).to receive(:get_esxi).with("type", "101").and_return(rez_response)
expect(subject).to receive(:get_esxi).with("type", "101").and_return(rez_response)
vrealm1.create_esxi
vrealm1.resources.map(&:fqdn).should include(fqdn)
end
end
end
Getting the below error
Failures:
1) Esxi Host creation from Rez Api When response has esxi hosts should create esxi hosts
Failure/Error: expect(subject).to receive(:get_esxi).with("type", "101").and_return(rez_response)
(#<RezApi:0xc26d69c>).get_esxi("type", "101")
expected: 1 time with arguments: ("type", "101")
received: 0 times

subject { RezApi.new }
context "....." do
it "....." do
allow(subject).to receive(:get_esxi).with("type", "101").and_return({x: "y"})
expect(subject).to receive(:get_esxi).with("type", "101").and_return({x: "y"})
end
end
So, your updated test code should be this:
describe "Esxi Host creation from Rez Api" do
let(:federation) {create(:federation_collection, parent_id: nil, name: "Test", usage: "Tech Ops hosted in Vmware Data Centers", owner: "--", date_from: "2015-08-26", date_to: nil, collection_type_id: 2, zone_name: "se.vpc.vmw")}
let(:datacenter) {create(:datacenter_collection, name: "Datacenter", parent_id: federation.id)}
let(:pod) {create(:pod_collection, parent_id: datacenter.id)}
let(:fqdn) {"d2p1s0ch10srv0v101-esx0.se.vpc.vmw"}
let(:vrealm1) {create(:vrealm_collection, name: "vRealm1", parent_id: pod.id)}
let(:vrealm2) {create(:vrealm_collection, name: "vRealm2", parent_id: pod.id)}
context "When response has esxi hosts " do
let(:rez_response) {[{"esx0"=>{"nodefqdn"=>"d2p1s0ch10srv0.se.vpc.vmw","fqdn"=>"d2p1s0ch10srv0v101-esx0.se.vpc.vmw","vmk0"=>{"pg_name"=>"d2p1v101-esx-pg-1062","ip_addr"=>"10.141.112.71","netmask"=>"255.255.255.0",},"vmk1"=>{"pg_name"=>"d2p1pod-sto-pg-17","ip_addr"=>"172.16.160.51","netmask"=>"255.255.252.0"},"vmk2"=>{"pg_name"=>"d2p1v101-ftx-pg-1063","ip_addr"=>"172.16.165.126","netmask"=>"255.255.255.0"}}}}
subject { RezApi.new }
it "should create esxi hosts" do
allow(subject).to receive(:get_esxi).with("vrealm-multitenant-dr2c", "101").and_return(rez_response)
expect(subject).to receive(:get_esxi).with("vrealm-multitenant-dr2c", "101").and_return(rez_response)
vrealm1.create_esxi
vrealm1.resources.map(&:fqdn).should include(fqdn)
end
end
end

It worked with below code
RezApi.any_instance.stub(:get_esxi).with("vrealm-multitenant-dr2c", "101").and_return(success_response)

Related

How to fix a task test in RSpec?

I have a table called Jurisdiction and created that task to update the kind column:
namespace :populate_jurisdiction do
desc "Populate column kind of jurisdiction model"
task kind: :environment do
Jurisdiction.all.each { |jurisdiction|
case (jurisdiction.kind.nil? || jurisdiction.kind.blank?)
when jurisdiction.name.parameterize == "federal"
jurisdiction.kind = 1
when jurisdiction.name.parameterize == "estadual"
jurisdiction.kind = 2
when jurisdiction.name.parameterize == "municipal"
jurisdiction.kind = 3
when jurisdiction.name.parameterize == "privado"
jurisdiction.kind = 4
end
jurisdiction.save!
}
end
end
Then I created that test
require "spec_helper"
Rails.application.load_tasks
describe "populate_jurisdiction:kind" do
context "when update kind column of jurisdiction" do
let(:federal) { create(:jurisdiction, name: "Federal", kind: nil) }
let(:state) { create(:jurisdiction, name: "Estadual", kind: nil) }
let(:municipal) { create(:jurisdiction, name: "Municipal", kind: '') }
let(:particular) { create(:jurisdiction, name: "Privado", kind: '') }
it "when kind column is updated" do
Rake::Task["populate_jurisdiction:kind"].invoke
expect(federal.kind).to eq(1)
expect(state.kind).to eq(2)
expect(municipal.kind).to eq(3)
expect(particular.kind).to eq(4)
end
end
end
When I run the task in rails console it works, but when I run the test, I got this error
Failures:
1) populate_jurisdiction:kind when update kind column of jurisdiction when kind column is updated
Failure/Error: expect(federal.kind).to eq(1)
expected: 1
got: nil
(compared using ==)
What I'm doing wrong? How can I fix this test?
You're invoking the rake task before any of the Jurisdiction's are created. When you say this:
let(:federal) { create(:jurisdiction, name: "Federal", kind: nil) }
that will create federal when you first access it in a test and then remember that for the duration of the test. So there is no federal until after your rake task runs. If you used let! instead of let and reloaded the jurisdictions after the task runs, you'd get better results.
BTW, your rake task doesn't work they way you think it does. There are two forms of case:
case expr
when value1
...
when value2
...
end
and
case
when expr1
...
when expr2
...
end
You're using the first form when you mean to be using the second and your code is working by accident. I suspect that all your kinds are nil when you run this, otherwise you'd end up doing:
case false
...
end
and you'd go into the first branch where the jurisdiction.name.parameterize test failed.
Your task should look more like:
Jurisdiction.all.reject { |j| j.kind.blank? }.each do |jurisdiction|
case jurisdiction.name.parameterize
when 'federal'
jurisdiction.kind = 1
when 'estadual'
jurisdiction.kind = 2
when 'municipal'
jurisdiction.kind = 3
when 'privado'
jurisdiction.kind = 4
end
jurisdiction.save!
end
or:
Jurisdiction.all.reject { |j| j.kind.blank? }.each do |jurisdiction|
jurisdiction.kind = case jurisdiction.name.parameterize
when 'federal' then 1
when 'estadual' then 2
when 'municipal' then 3
when 'privado' then 4
end
jurisdiction.save!
end
If kind is an integer then kind.blank? will only true when kind.nil? so that can be pushed into the database:
Jurisdiction.where(kind: nil).each do |jurisdiction|
jurisdiction.kind = case jurisdiction.name.parameterize
when 'federal' then 1
when 'estadual' then 2
when 'municipal' then 3
when 'privado' then 4
end
jurisdiction.save!
end
and it looks like #parameterize in this case is only going to be converting the name to lower case so push all the logic into the database:
# This is an SQL CASE expression, not a Ruby one
Jurisdiction.where(kind: nil).update_all(%q(
kind = case lower(name)
when 'federal' then 1
when 'estadual' then 2
when 'municipal' then 3
when 'privado' then 4
end
))
When you invoke the rake task, there are no jurisdictions, that's why you're getting nil. For example, federal jurisdiction is only created after the rake task when you call federal.kind.
require "spec_helper"
Rails.application.load_tasks
describe "populate_jurisdiction:kind" do
context "when update kind column of jurisdiction" do
let(:federal) { create(:jurisdiction, name: "Federal", kind: nil) }
let(:state) { create(:jurisdiction, name: "Estadual", kind: nil) }
let(:municipal) { create(:jurisdiction, name: "Municipal", kind: '') }
let(:particular) { create(:jurisdiction, name: "Privado", kind: '') }
it "when kind column is updated" do
# NOTE: jurisdictions are not created until `let` blocks are called.
federal
state
municipal
particular
Rake::Task["populate_jurisdiction:kind"].invoke
# NOTE: `let` return values are memoized, second call will just
# retrieve the value. You have to reload the models as well
# to get the updated values from the database.
expect(federal.reload.kind).to eq(1)
expect(state.reload.kind).to eq(2)
expect(municipal.reload.kind).to eq(3)
expect(particular.reload.kind).to eq(4)
end
end
end
https://relishapp.com/rspec/rspec-core/v/3-11/docs/helper-methods/let-and-let

RSpec Fails argument match expectation despite no diff

How do you place an expectation that the correct ActiveRecord::Relation is sent as a keyword argument? I've seen this sort of problem before, and in the past using hash_including matcher resolves the issue, but not with ActiveRecord::Relation. It is so frustrating because the error shows that there is no diff between the expectation and the actual received.
I have a spec that looks something like this:
describe ProcessAccountsJob, type: :job do
subject { described_class.new }
let!(:incomplete) { create(:account, :incomplete_account) }
it 'calls process batch service' do
expect(ProcessAccounts).to receive(:batch).with(
accounts: Account.where(id: incomplete.id)
)
subject.perform
end
end
and I get an error that looks like this:
1) ProcessAccounts calls process batch service
Failure/Error: ProcessAccounts.batch(accounts: accounts)
ProcessAccounts received :batch with unexpected arguments
expected: ({:accounts=>#<ActiveRecord::Relation [#<Account id: 14819, account_number: nil...solar: nil, pap: nil, types: [], annualized_usage: nil>]>})
got: ({:accounts=>#<ActiveRecord::Relation [#<Account id: 14819, account_number: nil...solar: nil, pap: nil, types: [], annualized_usage: nil>]>})
Diff:
# ./app/jobs/process_accounts_job.rb:13:in `perform'
# ./spec/jobs/process_accounts_job_spec.rb:9:in `block (2 levels) in <main>'
As mentioned, trying to use hash_including isn't helping. When the spec is changed to:
describe ProcessAccountsJob, type: :job do
subject { described_class.new }
let!(:incomplete) { create(:account, :incomplete_account) }
it 'calls process batch service' do
expect(ProcessAccounts).to receive(:batch).with(
hash_including(accounts: Account.where(id: incomplete.id))
)
subject.perform
end
end
the diff becomes:
-["hash_including(:accounts=>#<ActiveRecord::Relation [#<Account id: 14822, account_number: nil, service_address: \"123 Main St\", created_at: \"2020-07-12 15:50:00\", updated_at: \"2020-07-12 15:50:00\", solar: nil, pap: nil, types: [], annualized_usage: nil>]>)"]
+[{:accounts=>
+ #<ActiveRecord::Relation [#<Account id: 14822, account_number: nil, service_address: "123 Main St", created_at: "2020-07-12 15:50:00", updated_at: "2020-07-12 15:50:00", solar: nil, pap: nil, types: [], annualized_usage: nil>]>}]
It turns out match_array matcher solves the problem in this case; which is pretty misleading because neither the expected nor actual is an array. 🤷🏻‍♂️
describe ProcessAccountsJob, type: :job do
subject { described_class.new }
let!(:incomplete) { create(:account, :incomplete_account) }
it 'calls process batch service' do
expect(ProcessAccounts).to receive(:batch).with(
accounts: match_array(Account.where(id: incomplete.id))
)
subject.perform
end
end

and sometimes I get failed spices because of Time.now, can't understand why

so i have a method in model
class << self
def last_week
start = Time.zone.now.beginning_of_week - 7.days
finish = start + 7.days
where('appointment_at >= ? AND appointment_at < ?', start, finish).order(appointment_at: :desc)
end
end
And I write spec for this method.
RSpec.describe Appointment, type: :model, vcr: { record: :none } do
let!(:time) { Time.now }
let(:appointment_at) { time }
context '.last_week' do
let!(:scoped_appointment) { create(:appointment, appointment_at: time - 2.days) }
let!(:another_appointment) { create(:appointment, appointment_at: time - 16.days) }
it do
travel_to(time) do
expect(Appointment.last_week).to include(scoped_appointment)
expect(Appointment.last_week).not_to include(another_appointment)
end
end
end
end
And sometime i get failed this spec with error.
expected #<ActiveRecord::Relation []> to include #<Appointment id: 18, lead_id: 27, body: nil, appointment_at: "2019-02-25 00:59:47", google_id: nil, ... "pending", user_id: 22, notify: nil, cc_emails: nil, appointment_minutes: nil, status_message: nil>
Diff:
## -1,2 +1,2 ##
-[#<Appointment id: 18, lead_id: 27, body: nil, appointment_at: "2019-02-25 00:59:47", google_id: nil, created_at: "2019-02-27 00:59:47", updated_at: "2019-02-27 00:59:47", timezone: nil, subject: "Meeting with Lead", address: nil, notification: nil, status: "pending", user_id: 22, notify: nil, cc_emails: nil, appointment_minutes: nil, status_message: nil>]
+[]
I can't understand why?
And I have a suggestion that I should tightly set time
in spec_helper.rb
$now = DateTime.parse('2020-01-01 00:00:01 -0500')
will it be right? and why ?
Your test setup is brittle. It will break depending on the day of the week you run your spec.
The scope in your model returns appointments from the previous week, Monday through Sunday (you are calling beginning_of_week and adding 7 days to it)
So if your tests run on a Wednesday, like in the example you provided, the appointment’s appointment_at field will be set to Monday (since you are calculating it as Time.now - 2.days). That means your scope will not cover that appointment.
I suggest you use a specific time in your setup. Given your current setup, using let(:time) { DateTime.parse('2019-02-25 00:00:00') } should work

Testing Cronjobs in Rails

I have a cronjob, which moves users from one table to another according some deadline reached.
This cronjob works in rails console, but the test is red. If I test the function from this cronjob, the test is green. When I go inside the cronjob with binding.pry, it holds all necessary variables and does its work correctly.
What can be wrong?
Test:
describe 'try various methods' do
before(:each) do
Obparticipant::Participant.all.delete_all
#content = FactoryBot.create(:content, :with_department_ob, target_group: 'child', subject: 'Infos für Teilnehmer aus {ort}', message: '«{geschlecht} | Lieber | Liebe» {vorname}, du bist am {geburtsdatum} geboren.', notification_email: '{nachname}, {straße}, {plz}, {wohnland}, {bundesland}, {landesgruppe}')
germany = ::Physical::Base::Country.GERMAN
address = FactoryBot.create(:address, addressline_1: 'Sesamstraße', addressline_2: 'Kaufmannstraße', state: 'Bayern', city: 'München', zip: '80331', country_id: germany.id)
person = FactoryBot.create(:person, firstname: 'Pablo', lastname: 'Domingo', dateofbirth: Date.new(2001,2,3), gender: 'm', address_id: address.id)
#participant = FactoryBot.create(:participant, person_id: person.id)
#participant.open_todos.by_task(:account_data).each{ |t| t.complete! }
end
it 'should move recipients with a start_date of today back to content_recipients' do
person_two = FactoryBot.create(:person)
participant_two = FactoryBot.create(:participant, person_id: person_two.id, program_season_id: #participant.program_season_id)
participant_two.open_todos.by_task(:account_data).each{ |t| t.complete! }
filter = '{"program_season":"' + #participant.program_season_id.to_s + '"}'
#content.update_attributes(for_dynamic_groups: true, filter: filter, is_draft: false, delay_days: 5)
FactoryBot.create(:delayed_content_recipient, content_id: #content.id, recipient_id: participant_two.id, start_date: Date.today)
expect(#content.content_recipients.size).to eq(0)
Cronjobs.check_recipients # or #content.insert_open_recipients
expect(#content.delayed_content_recipients.size).to eq(1)
expect(#content.content_recipients.map(&:recipient_id).last).to eq(participant_two.id) # this expectation fails, when a cronjob is tested, and passes, when a function is tested
end`
Cronjob:
def self.check_recipients
contents = ::Content.published.current.by_for_dynamic_groups(true)
contents.each do |content|
content.insert_open_recipients
end
end
Function
def insert_open_recipients
search = ::SimpleParticipantSearch.new(JSON.parse(self.filter))
new_recipients = search.result.without_content(self.id)
new_recipients.each do |nr|
if self.delay_days.present?
unless self.delayed_content_recipients.map(&:recipient_id).include?(nr.id)
self.delayed_content_recipients.create(content_id: self.id, recipient_id: nr.id, start_date: Date.today + self.delay_days.days)
end
else
self.participant_recipients << nr unless errors_with_participant?(nr)
end
end
if self.delayed_content_recipients.any?
self.delayed_content_recipients.each do |recipient|
if new_recipients.map(&:id).include?(recipient.recipient_id)
if recipient.start_date == Date.today
self.delayed_content_recipients.delete(recipient)
self.participant_recipients << Obparticipant::Participant.find_by(id: recipient.recipient_id) unless errors_with_participant?(Obparticipant::Participant.find_by(id: recipient.recipient_id))
end
else
self.delayed_content_recipients.delete(recipient)
end
end
end
end
The solution I found is to test separately whether a Cronjob is run, and whether the function it calls works.
I wrote a stub for this Cronjob in the cronjobs controller rspec
it 'should call the correct method on the Cronjobs.check_recipients object' do
Cronjobs.stub(:check_recipients)
post :create, job: 'CheckRecipients'
expect(Cronjobs).to have_received(:check_recipients)
expect(response).to have_http_status(200)
end
and tested the function in the test i provided above.
it 'should move recipients with a start_date of today back to content_recipients' do
person_two = FactoryBot.create(:person)
participant_two = FactoryBot.create(:participant, person_id: person_two.id, program_season_id: #participant.program_season_id)
participant_two.open_todos.by_task(:account_data).each{ |t| t.complete! }
filter = '{"program_season":"' + #participant.program_season_id.to_s + '"}'
#content.update_attributes(for_dynamic_groups: true, filter: filter, is_draft: false, delay_days: 5)
FactoryBot.create(:delayed_content_recipient, content_id: #content.id, recipient_id: participant_two.id, start_date: Date.today)
expect(#content.content_recipients.size).to eq(0)
#content.insert_open_recipients
expect(#content.delayed_content_recipients.size).to eq(1)
expect(#content.content_recipients.map(&:recipient_id).last).to eq(participant_two.id)
end

Rspec - archives array to match tested array

I'm having this class method on my Post model for getting archives
def self.archives
Post.unscoped.select("YEAR(created_at) AS year, MONTHNAME(created_at) AS month, COUNT(id) AS total")
.group("year, month, MONTH(created_at)")
.order("year DESC, MONTH(created_at) DESC")
end
This is the test I have wrote for my method
context '.archives' do
first = FactoryGirl.create(:post, published_at: Time.zone.now)
second = FactoryGirl.create(:post, published_at: 1.month.ago)
it 'returns articles archived' do
archives = Post.archives()
expect(
[{
year: first.published_at.strftime("%Y"),
month: first.published_at.strftime("%B"),
published: 1
},
{
year: second.published_at.strftime("%Y"),
month: second.published_at.strftime("%B"),
published: 1
}]
).to match_array(archives)
end
end
However I get the following error
expected collection contained: [#<Post id: nil>, #<Post id: nil>]
actual collection contained: [{:year=>"2017", :month=>"October", :published=>1}, {:year=>"2017", :month=>"September", :total=>1}]
the missing elements were: [#<Post id: nil>, #<Post id: nil>]
the extra elements were: [{:year=>"2017", :month=>"October", :total=>1}, {:year=>"2017", :month=>"September", :total=>1}]
So although I have created 2 factories, the archives array is empty. What am I doing wrong?
Rspec standard is to use the let syntax for defining variables within a context or describe block. The test should look something like this:
describe '.archives' do
let!(:first) { FactoryGirl.create(:post, published_at: Time.zone.now) }
let!(:second) { FactoryGirl.create(:post, published_at: 1.month.ago) }
it 'returns year, month, and total for articles archived' do
actual_attributes = Post.archives.map { |post| [post.year, post.month, post.total] }
expected_total = 1 # I don't know why the query is returning 1 for total, but including this for completeness
expected_attributes = [first, second].map { |post| [post.created_at.year, post.created_at.strftime("%B"), expected_total] }
expect(actual_attributes).to match_array(expected_attributes)
end
end
The issue here is that you are comparing records pulled with only a few attributes (the result of your SQL query) with fully-formed records (created by your test). This test pulls the applicable attributes from both groups and compares them.
Actual array is not empty, it's an array of two Post instances with ids unset (because Select in .archives method doesn't contain id field).
You could compare expected hashes not with archives, but with smth like that:
actual = Post.archives().map do |post|
{ year: post["year"].to_s, month: post["month"], published: post["total"] }
end
expected = [{
year: first.published_at.strftime("%Y").to_s,
month: first.published_at.strftime("%B"),
published: 1
},
{
year: second.published_at.strftime("%Y").to_s,
month: second.published_at.strftime("%B"),
published: 1
}]
expect(actual).to match_array(expected)

Resources