2
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)>'

2 Answers 2

1

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
Sign up to request clarification or add additional context in comments.

1 Comment

can you like my question? :-)
0

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

2 Comments

If I use Users::Delete(@user).destroy_all, I can't delete multiple users at once due to causing issue. so I am going to call .destroy_call method at the end of @users. like this User.with_state("credentials").with_last_otp_at(Time.now - 10.minutes).destroy_all from .rake file. in this case, How can I write the unit test? I hope your help for it
@CaTs excellent answer has an example of testing the actual behavior instead of the implementation. Use it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.