Rspec: test if method was called on an object inside a job? - ruby-on-rails

I made a job to call a method if ended_at was less than today. For some reason I keep getting failures even if I throw a binding pry right at the moment it is about to break and manually call the method and it works fine. However, I still get a failure if I just let the spec test run on its own.
My job:
class RemoveOldEventsFromAlgoliaJob < ApplicationJob
queue_as :default
def perform
old_events = Event.where("ended_at < ?", Date.today)
old_events.each do |e|
e.remove_from_algolia
end
end
end
My method:
def remove_from_algolia
index = Algolia::Index.new(##ALGOLIA_INDEX_NAME)
index.delete_object(self.id)
end
My spec test:
require 'rails_helper'
RSpec.describe RemoveOldEventsFromAlgoliaJob, type: :job do
it "will remove old events from the index" do
ActiveJob::Base.queue_adapter = :test
event = FactoryGirl.create(:event, title: "EXPIRED EVENT", ended_at: 1.day.ago)
RemoveOldEventsFromAlgoliaJob.perform_now
expect(event).to receive(:remove_from_algolia)
end
end
RSpec output:
Failures:
1) d will remove old events from the index
Failure/Error: expect(event).to receive(:remove_from_algolia)
(#<Event id: 401, uuid: "6e9a6f08-c34d-45af-9f3c-870b28643809", organization_id: nil, event_type_id: nil, name: "cool-event", title: "Cool Event", description: "Rad thing that's gonna happen", platform_type: "ee", platform_id: "12345678", platform_url: "http://event.com/12345678", featured: false, capacity: 100, rsvp_count: nil, attendee_count: nil, status: "upcoming", started_at: "2017-10-21 03:00:39", ended_at: "2017-03-23 07:00:00", deleted_at: "2017-10-21 03:00:39", created_at: "2017-03-24 00:24:54", updated_at: "2017-03-24 00:24:54", location_line_1: nil, location_line_2: nil, location_city: nil, location_state: nil, location_zip: nil, location_country: nil>).remove_from_algolia(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
# ./spec/jobs/remove_old_events_from_algolia_job_spec.rb:10:in `block (2 levels) in <top (required)>'
Finished in 0.8828 seconds (files took 7.12 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/jobs/remove_old_events_from_algolia_job_spec.rb:4 # RemoveOldEventsFromAlgoliaJob will remove old events from the index
Coverage report generated for RSpec to /workproject/coverage. 384 / 496 LOC (77.42%) covered.
I can put a binding.pry inside my job during the spec test and call that method successfully:
4: def perform
5:
6: old_events = Event.where("ended_at < ?", Date.today)
7: old_events.each do |e|
=> 8: binding.pry
9: e.remove_from_algolia
10: end
11: end
[1] pry(#<RemoveOldEventsFromAlgoliaJob>)> e.remove_from_algolia
=> {"deletedAt"=>"2017-03-24T00:37:53.743Z", "taskID"=>206043962, "objectID"=>"409"}
[2] pry(#<RemoveOldEventsFromAlgoliaJob>)>

Try using a stub for event instead of a real Event model:
require 'rails_helper'
RSpec.describe RemoveOldEventsFromAlgoliaJob, type: :job do
it "will remove old events from the index" do
ActiveJob::Base.queue_adapter = :test
event = double('event')
allow(Event).to receive(:where).and_return([event])
expect(event).to receive(:remove_from_algolia)
RemoveOldEventsFromAlgoliaJob.perform_now
end
end

Related

RSpec controller GET #index test is returning an empty ActiveRecord array instead of my model

Please excuse my rustiness, first time touching Rails and this project in quite some time.
Ruby Version: 2.5.0
Rails Version: 5.1.7
RSpec Version: 3.9.3
FactoryBot Version: 6.2.0
This is my scripts_controller_spec.rb file with model creation and the test in question:
require 'rails_helper'
describe ScriptsController, type: :controller do
userID_1 = User.create!(
email: 'ueferfrfrf#u1.com',
password: 'useruser',
password_confirmation: 'useruser'
)
script1 = Script.create!(
name: 'YWoodcutter',
skill: 'Woodcutting',
bot_for: 'TRiBot',
game_for: 'Oldschool Runescape 07',
user_id: userID_1.id
)
script1.save
describe "GET #index" do
it "assigns #scripts" do
get :index
p script1
expect(assigns(:scripts)).to eq([script1])
end
end
When running the tests, the print line above outputs this, as expected:
#<Script id: 1, name: "YWoodcutter", skill: "Woodcutting", bot_for: "TRiBot", game_for: "Oldschool Runescape 07", user_id: 1, created_at:
"2021-10-19 08:29:43", updated_at: "2021-10-19 08:29:43">
However, I get this test failure:
Failures:
ScriptsController GET #index assigns #scripts
Failure/Error: expect(assigns(:scripts)).to eq([script1])
expected: [#<Script id: 1, name: "YWoodcutter", skill: "Woodcutting", bot_for: "TRiBot", game_for: "Oldschool Runescape 07",
user_id: 1, created_at: "2021-10-19 08:29:43", updated_at: "2021-10-19
08:29:43">]
 
got: #<ActiveRecord::Relation []>
(compared using ==)
My scripts_controller.rb index function looks like so:
class ScriptsController < ApplicationController
def index
#scripts = Script.order(:created_at)
end
Let me know if you need any more info, and thanks for your help!
I think the Script object is not getting created before calling the index action. Because of this, you are getting the empty ActiveRecord::Relation. In this situation let! should fix your problem
require 'rails_helper'
describe ScriptsController, type: :controller do
let!(:user_1) do
User.create!(
email: 'ueferfrfrf#u1.com',
password: 'useruser',
password_confirmation: 'useruser'
)
end
let!(:script1) do
Script.create!(
name: 'YWoodcutter',
skill: 'Woodcutting',
bot_for: 'TRiBot',
game_for: 'Oldschool Runescape 07',
user_id: user_1.id
)
end
describe "GET #index" do
before { get :index }
it "assigns #scripts" do
expect(assigns(:scripts)).to eq([script1])
end
end
end
Based on the current code, it seems you were not calling all script.
Using
### Controller
#scripts = Script.all.order(:created_at)
### Test
## Should use factories to create the records
let(:user) do
create(:user, email: 'ueferfrfrf#u1.com',
password: 'useruser',
password_confirmation: 'useruser')
end
let(:script) do
create(:script, name: 'YWoodcutter',
skill: 'Woodcutting',
bot_for: 'TRiBot',
game_for: 'Oldschool Runescape 07',
user: user)
end
should fix it.

Rails Rspec allow multiple method call in one line

desc 'Remove credential state users who no longer request for confirm otp within 10 minutes'
task failed_user_cleaner: :environment do
puts "Daily UserRecord Cleaning CronJob started - #{Time.now}"
#user = User.with_state("credentials").with_last_otp_at(Time.now - 10.minutes)
Users::Delete.new(#user).destroy_all
puts "Daily UserRecord Cleaning CronJob ended - #{Time.now}"
end
Above is crop job rake file code.
then I've tried in many times and found in many times.
But I couldn't find the way to write unit test case for above job.
Help me to write test case correctly.
here is my spec code
require 'rails_helper'
describe 'users rake tasks' do
before do
Rake.application.rake_require 'tasks/users'
Rake::Task.define_task(:environment)
end
context 'when remove credential state users who no longer request for confirm otp within 10 minutes' do
let(:user) { create(:user, last_otp_at: Time.now - 11.minutes, state: "credentials") }
let (:run_users_rake_task) do
Rake.application.invoke_task 'users:failed_user_cleaner'
end
it 'calls right service method' do
#users = Users::Delete.new([user])
expect(#users).to receive(:destroy_all)
run_users_rake_task
end
end
end
here is the error log
Failures:
1) users rake tasks when remove credential state users who no longer request for confirm otp within 10 minutes calls right service method
Failure/Error: expect(#users).to receive(:destroy_all)
(#<Users::Delete:0x0000556dfcca3a40 #user=[#<User id: 181, uuid: nil, phone: "+66969597538", otp_secret: nil, last_otp_at: "2021-09-30 09:32:24.961548000 +0700", created_at: "2021-09-30 09:43:24.973818000 +0700", updated_at: "2021-09-30 09:43:24.973818000 +0700", email: nil, avatar: "https://dummyimage.com/300x300/f04720/153572.png?t...", refresh_token: "eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MzI5Njk4MDQsImV4c...", first_name_en: "Jenise", first_name_th: "Damion", last_name_en: "McCullough", last_name_th: "Beatty", nationality: "TH", thai_national_id: nil, thai_laser_code: nil, company_id: 200, role: nil, state: "credentials", date_of_birth: "2020-10-30 00:00:00.000000000 +0700", deleted_at: nil, password_digest: "$2a$04$jfR9X9ci06602tlAyLOoRewTK1lZ12vJ2cZ9Dc2ov4F...", username: "zreejme238", shopname: nil, access_token: nil, locked_at: nil, login_attempts: 0, locale: "th", scorm_completed: false>]>).destroy_all(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
# ./spec/tasks/users_spec.rb:19:in `block (3 levels) in <top (required)>'
You are creating two instances of Users::Delete when running this test, one within the test and one within the task. Since the instance within the test is not used, it is incorrect to expect it to receive a message.
Rspec has an expectation, expect_any_instance_of, that will fix this however consider reading the full page since it can create fragile or flaky tests. If you wanted to use this method, your test would look something like:
it 'calls right service method' do
expect_any_instance_of(Users::Delete).to receive(:destroy_all)
run_users_rake_task
end
Personally I'd instead check that the expected users were deleted with something like:
it 'removes the user' do
expect { run_users_rake_task }.to change { User.exists?(id: #user.id) }.to(false)
end
Unless you want to use any_instance_of (which is a code smell) you need to stub the Users::Delete method so that it returns a double and put the expectation on the double:
require 'rails_helper'
describe 'users rake tasks' do
before do
Rake.application.rake_require 'tasks/users'
Rake::Task.define_task(:environment)
end
context 'when remove credential state users who no longer request for confirm otp within 10 minutes' do
let(:user) { create(:user, last_otp_at: Time.now - 11.minutes, state: "credentials") }
let(:run_users_rake_task) do
Rake.application.invoke_task 'users:failed_user_cleaner'
end
let(:double) do
instance_double('Users::Delete')
end
before do
allow(Users::Delete).to receive(:new).and_return(double)
end
it 'calls right service method' do
expect(double).to receive(:destroy_all)
run_users_rake_task
end
end
end
However this really just tells us that the API of the service object is clunky and that you should write a class method which both instanciates and performs:
module Users
class Delete
# ...
def self.destroy_all(users)
new(users).destroy_all
end
end
end
desc 'Remove credential state users who no longer request for confirm otp within 10 minutes'
#...
Users::Delete.destroy_all(#user)
# ...
end
require 'rails_helper'
describe 'users rake tasks' do
# ...
context 'when remove credential state users who no longer request for confirm otp within 10 minutes' do
# ...
it 'calls right service method' do
expect(Users::Delete).to receive(:destroy_all)
run_users_rake_task
end
end
end

Testing that Devise is re-sending confirmation instructions email in a background worker

I want to send Devise confirmation instructions to users a second time if they haven't confirmed within two days of signing up, however I can't seem to get my success case test to pass.
Background worker (runs once a day):
class ResendConfirmationWorker
include Sidekiq::Worker
sidekiq_options queue: :resend_confirmation, retry: false
def perform
d = Time.zone.now - 2.days
users = User.where.not(confirmation_sent_at: nil)
.where(confirmed_at: nil)
.where(created_at: d.beginning_of_day..d.end_of_day)
users.find_each do |user|
user.send_confirmation_instructions
end
end
end
RSpec test:
require 'rails_helper'
describe ResendConfirmationWorker, type: :job do
before do
time = Time.zone.now - 2.days
#user = create :unconfirmed_user,
created_at: time,
confirmation_sent_at: time
end
def run_job
subject.perform
end
it 'resends a confirmation email to people who haven’t accepted it within two days' do
run_job
expect(Devise::Mailer.deliveries.count).to eq 1
end
end
I always get 0 instead of 1. I've also tried looking at Sidekiq::Extensions::DelayedMailer.jobs.size and Devise.mailer.deliveries.count but they also return 0.
Lastly, I put a binding in the worker and run user.send_confirmation_instructions manually:
[1] pry(#<ResendConfirmationWorker>)> user.send_confirmation_instructions
=> #<ActionMailer::DeliveryJob:0x007fc53f05c420
#arguments=
["Devise::Mailer",
"confirmation_instructions",
"deliver_now",
#<User id: 1, email: "unconfirmed1#example.com", created_at: "2017-01-05 04:40:13", updated_at: "2017-01-07 04:40:13", company_id: nil, name: "Unconfirmed User", invitation_token: nil, invitation_created_at: nil, invitation_sent_at: nil, invitation_accepted_at: nil, invitation_limit: nil, invited_by_type: nil, invited_by_id: nil, invitations_count: 0>,
"token1",
{}],
#job_id="9b5f6231-3194-4c57-9a6b-a38368cec603",
#priority=nil,
#queue_name="mailers">
It looks like it's correctly adding a new confirmation instructions email to the mailer queue, so why can't I see it from my test?

Using RSpec to test for correct order of records in a model

I'm new to rails and RSpec and would like some pointers on how to get this test to work.
I want emails to be sorted from newest to oldest and I'm having trouble testing this.
I'm new to Rails and so far I'm having a harder time getting my tests to work then the actual functionality.
Updated
require 'spec_helper'
describe Email do
before do
#email = Email.new(email_address: "user#example.com")
end
subject { #email }
it { should respond_to(:email_address) }
it { should respond_to(:newsletter) }
it { should be_valid }
describe "order" do
#email_newest = Email.new(email_address: "newest#example.com")
it "should have the right emails in the right order" do
Email.all.should == [#email_newest, #email]
end
end
end
Here is the error I get:
1) Email order should have the right emails in the right order
Failure/Error: Email.all.should == [#email_newest, #email]
expected: [nil, #<Email id: nil, email_address: "user#example.com", newsletter: nil, created_at: nil, updated_at: nil>]
got: [] (using ==)
Diff:
## -1,3 +1,2 ##
-[nil,
- #<Email id: nil, email_address: "user#example.com", newsletter: nil, created_at: nil, updated_at: nil>]
+[]
# ./spec/models/email_spec.rb:32:in `block (3 levels) in <top (required)>'
In your code:
it "should have the right emails in the right order" do
Email.should == [#email_newest, #email]
end
You are setting the expectation that the Email model should be equal to the array of emails.
Email is a class. You can't just expect the class to be equal to an array. All emails can be found by using all method on class Email.
You must set the expectation for two arrays to be equal.
it "should have the right emails in the right order" do
Email.order('created_at desc').all.should == [#email_newest, #email]
end
It should work like this.
For newer version of RSpec:
let(:emails) { ... }
it 'returns emails in correct order' do
expect(emails).to eq(['1', '2', '3'])
end

RSpec test custom validator

I have the following validator in my model:
class ContinuumValidator < ActiveModel::Validator
def validate(record)
if !record.end_time.nil? and record.end_time < record.start_time
record.errors[:base] << "An event can not be finished if it did not start yet..."
end
end
end
class Hrm::TimeEvent < ActiveRecord::Base
validates_with ContinuumValidator
end
How can I test it using Rspec?
Here is what I have tried so far: (thanks to zetetic)
describe "validation error" do
before do
#time_event = Hrm::TimeEvent.new(start_time: "2012-10-05 10:00:00", end_time: "2012-10-05 09:00:00", event_type: 2)
end
it "should not be valid if end time is lower than start time" do
#time_event.should_not be_valid
end
it "raises an error if end time is lower than start time" do
#time_event.errors.should include("An event can not be finished if it did not start yet...")
end
end
But I get the following errors:
1) Hrm::TimeEvent validation error raises an error if end time is lower than start time
Failure/Error: #time_event.errors.should include("An event can not be finished if it did not start yet...")
expected #<ActiveModel::Errors:0x007fd1d8e02c50 #base=#<Hrm::TimeEvent id: nil, start_time: "2012-10-05 08:00:00", end_time: "2012-10-05 07:00:00", event_type: 2, employee_id: nil, created_at: nil, updated_at: nil, not_punched: false, validated: false, replace_id: nil>, #messages={}> to include "An event can not be finished if it did not start yet..."
Diff:
## -1,2 +1,5 ##
-["An event can not be finished if it did not start yet..."]
+#<ActiveModel::Errors:0x007fd1d8e02c50
+ #base=
+ #<Hrm::TimeEvent id: nil, start_time: "2012-10-05 08:00:00", end_time: "2012-10-05 07:00:00", event_type: 2, employee_id: nil, created_at: nil, updated_at: nil, not_punched: false, validated: false, replace_id: nil>,
+ #messages={}>
What am I doing wrong? And how can I achieve my goal?
Any help or suggestion would be appreciated.
Thanks.
The problem is that you're expecting #time_event.errors to behave like an array of strings. It doesn't, it returns ActiveModel::Errors. As others pointed out, you also need to trigger the validations with a call to valid?:
it "raises an error if end time is lower than start time" do
#time_event.valid?
#time_event.errors.full_messages.should include("An event can not be finished if it did not start yet...")
end
This solution works for me (using Mongoid):
The model
class OpLog
...
field :from_status, type: String
field :to_status, type: String
...
validate :states_must_differ
def states_must_differ
if self.from_status == self.to_status
errors.add(:from_status, "must differ from 'to_status'")
errors.add(:to_status, "must differ from 'from_status'")
end
end
...
end
The test:
it 'is expected to have different states' do
expect { create(:oplog, from_status: 'created', to_status: 'created').to raise_error(Mongoid::Errors::Validations) }
end
So in your case I'd write a test like this (if using ActiveRecord):
it 'raises an error if end time is lower than start time' do
expect { create(Hrm::TimeEvent.new(start_time: "2012-10-05 10:00:00", end_time: "2012-10-05 09:00:00", event_type: 2)) }.to raise_error(ActiveRecord::Errors)
end
There are no errors because you haven't called an event that triggers the errors. This happens normally when a record is created or saved. You may not want to hit the database in your test though and then you can use the method valid? like this:
it "raises an error if end time is lower than start time" do
#time_event.valid?
#time_event.errors.should include("An event can not be finished if it did not start yet...")
end
Me personally would put these two tests into one since valid? is called in the first case.
Also a minor: if record.end_time is better than if !record.end_time.nil?. (In my opinion at least.... :-) )
I think the record wasnt validated therefore the validatior didn't run and no error was aded. You can see this in the code output. "validated: false"
try:
it "raises an error if end time is lower than start time" do
#time_event.valid?
#time_event.errors.should include("An event can not be finished if it did not start yet...")
end
You have not tested the validation actually, plus i would suggest you to make a single spec.
describe "validation error" do
before { #time_event = Hrm::TimeEvent.new(start_time: "2012-10-05 10:00:00", end_time: "2012-10-05 09:00:00", event_type: 2) }
it "raises an error if end time is lower than start time" do
#time_event.valid?
#time_event.errors.should include("An event can not be finished if it did not start yet...")
end
end
class ContinuumValidator < ActiveModel::Validator
def validate(record)
if record.end_time and record.end_time < record.start_time
record.error.add_to_base << "An event can not be finished if it did not start yet..."
end
end
end

Resources