How to test that a method is called using rspec? - ruby-on-rails

In a model spec, I want to test that certain methods are being called correctly.
#models/object.rb
class Object < ActiveRecord::Base
after_validation :do_this
after_save :enqueue_that
def do_this
# does some stuff, the results of which I don't want to test
end
def enqueue_that
MyWorker.perform_later id
end
end
#spec/models/object.rb
describe Object
describe '#do_this' do
it 'is called on save with passing validations' do
object.save
expect(object).to receive(:do_this)
end
end
describe '#enqueue_that' do
it 'is called after save' do
object.save
expect(MyWorker).to receive(:perform_later).once
end
end
end
The tests are failing with the following
Failure/Error: expect(object).to receive(:do_this).once
(#<Object:0x007fd2101c7160>).do_this(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
Failure/Error: expect(MyWorker).to receive(:perform_later).once
(MyWorker (class)).perform_later(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
Confusingly, these methods appear to be behaving correctly in the dev environment.
Am I using expect().to receive correctly? Or have my tests uncovered a genuine bug?

You just have things in the wrong order...
it 'is called on save with passing validations' do
expect(object).to receive(:do_this)
object.save
end

Related

Rspec: expect "super" to get called

I have a very short function to test:
def my_fn
if some_condition
super(#my_attr)
end
end
And I want my spec to do verify that super gets called, but I can't do the following because :super appears not to be a message that ever gets sent:
expect(subject).to receive(:super)

Rspec: Test an exception which is not handled

In my public method #recalculate, calling the private method1. This method throw exception 'StandardError'. I want to test this scenario, however getting an error.
Note: I don't want to handle an exception.
def recalculate
method_1
end
private
def method_1
## do some calculation
raise StandardError.new("Test")
end
Rspec Test case:
it "Test" do
expect { #product.recalculate.recalculate }.to raise_error(StandardError)
#product.recalculate
end
1) Product.Test
Failure/Error: #product.recalculate
StandardError:
Test
(required)>'
Finished in 1.39 seconds
1 example, 1 failure
According to your example, the second line #product.recalculate raises an actual exception hence the error. Assuming recalculate method is defined in #product object, this should be enough to test it.
it "Test" do
expect { #product.recalculate }.to raise_error(StandardError)
end

RSpec + Sidekiq: NoMethodError: undefined method 'jobs' MyImportJob

I am trying to write some specs for RSpec + Sidekiq in a Rails 4.2.4 app, but am encountering some issues.
My code looks like this:
class MyImportJob
include Sidekiq::Worker
sidekiq_options queue: :default
def perform(params)
# Do magic
end
end
and the spec:
describe MyImportJob, type: :job do
let(:panel) { create(:panel) }
describe '#perform' do
context 'unsuccessfully' do
it 'raises ArgumentError if no panel param was passed' do
expect {subject.perform_async()}.to raise_error(ArgumentError)
end
end
context 'successfully' do
it 'given a panel, it increases the job number' do
expect {
subject.perform_async(panel_id: panel.id)
}.to change(subject.jobs, :size).by(1)
end
end
end
end
But I am receiving the following errors:
Failure/Error: }.to change(subject.jobs, :size).by(1)
NoMethodError:
undefined method `jobs' for #<MyImportJob:0x007f80b74c5c18>
and
Failure/Error: expect {subject.perform_async()}.to raise_error(ArgumentError)
expected ArgumentError, got #<NoMethodError: undefined method `perform_async' for #<MyImportJob:0x007f80b6d73f50>>
I believe perform_async should be provided by default by Sidekiq as long as I include the line include Sidekiq::Worker in my worker, is this correct? The first test passes if I just use perform but I'd expect it to pass with perform_async which is what I'm using in my codebase.
As for the second, I don't understand why there is no method jobs for the test subject. Any clue about that?
My rails_helper.rb file has:
require 'sidekiq/testing'
Sidekiq::Testing.fake!
Thanks in advance!
In case you don't define subject explicitly, rspec will create subject as following rule:
By default, if the first argument to an outermost example group
(describe or context block) is a class, RSpec creates an instance of
that class and assigns it to the subject
ref: What's the difference between RSpec's subject and let? When should they be used or not?
That means it create instance of your worker. So that you can't call perform_async and jobs.
To resolve your issue, define it explicitly as below:
describe MyImportJob, type: :job do
let(:panel) { create(:panel) }
subject { MyImportJob }
describe '#perform' do
context 'unsuccessfully' do
it 'raises ArgumentError if no panel param was passed' do
expect {subject.perform_async()}.to raise_error(ArgumentError)
end
end
context 'successfully' do
it 'given a panel, it increases the job number' do
expect {
subject.perform_async(panel_id: panel.id)
}.to change(subject.jobs, :size).by(1)
end
end
end
end
expected ArgumentError, got #<NoMethodError: undefined method 'perform_async' for #<MyImportJob:0x007f80b6d73f50>>
perform_async is a method on worker class itself.
MyImportJob.perform_async(...)
I don't understand why there is no method jobs for the test subject
The same exact reason. It's a method on the worker class.

Test that Delayed Job is called

I am trying to test that a part of my code is running a DelayedJob.
Here's code:
def start_restream
...
puts 'Here'
Delayed::Job.enqueue(Restream::StartAllJob.new(channel.id))
puts 'After'
...
end
#app/jobs/restream/start_all_job.rb
class Restream::StartAllJob < Struct.new(:channel_id)
def perform
puts "Inside"
...
end
end
In my spec_helper.rb I have Delayed::Worker.delay_jobs = false.
The spec:
it 'runs Delayed::Job::Restream::StartAll' do
post :start_restream, :id => channel.id
expect(Restream::StartAllJob).to receive(:new)
end
It prints out
Here
Inside
After
when running, so I know that it is called. But test fails:
Failure/Error: expect(Restream::StartAllJob).to receive(:new)
(Restream::StartAllJob (class)).new(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
Or, if I use expect_any_instance_of(Restream::StartAllJob).to receive(:perform) it says
Failure/Error: example.run
Exactly one instance should have received the following message(s) but didn't: perform
What am I doing wrong and how can I test this?
It's just the mistake I made in the order:
expect(Restream::StartAllJob).to receive(:new) should be written before post :start_restream, :id => channel.id

Rspec allow and expect the same method with different arguments

I want to test my method which runs method Temp::Service.run two times inside it:
module Temp
class Service
def self.do_job
# first call step 1
run("step1", {"arg1"=> "v1", "arg2"=>"v2"})
# second call step 2
run("step2", {"arg3"=> "v3"})
end
def self.run(name, p)
# do smth
return true
end
end
end
I want to test arguments provided to second call of method :run with first argument 'step2'
while I want to ignore the first call of the same method :run but with first argument 'step1'.
I have the RSpec test
RSpec.describe "My spec", :type => :request do
describe 'method' do
it 'should call' do
# skip this
allow(Temp::Service).to receive(:run).with('step1', anything).and_return(true)
# check this
expect(Temp::Service).to receive(:run) do |name, p|
expect(name).to eq 'step2'
# check p
expect(p['arg3']).not_to be_nil
end
# do the job
Temp::Service.do_job
end
end
end
but I got error
expected: "step2"
got: "step1"
(compared using ==)
How to correctly use allow and expect for the same method ?
Seems like you are missing the .with('step2', anything)
it 'should call' do
allow(Temp::Service).to receive(:run).with('step1', anything).and_return(true)
# Append `.with('step2', anything)` here
expect(Temp::Service).to receive(:run).with('step2', anything) do |name, p|
expect(name).to eq 'step2' # you might not need this anymore as it is always gonna be 'step2'
expect(p['arg3']).not_to be_nil
end
Temp::Service.do_job
end

Resources