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
Related
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.
I have the following action:
def create
binding.pry
#finding.save
respond_with #project, #finding
end
When running the following test...
it 'balances the consecutive numbers', focus: true do
expect {
post :create, params: {...}
}.to change { Finding.count }.from(0).to 1
end
...I first have the binding.pry console displayed (proving that the #create action actually was executed), and then the spec passes:
Finished in 4.24 seconds (files took 5.31 seconds to load)
1 example, 0 failures
Now when I add an expect(controller).to receive(:create)...
it 'balances the consecutive numbers', focus: true do
expect(controller).to receive(:create) # This is new!
expect {
post :create, params: {...}}
}.to change { Finding.count }.from(0).to 1
end
...and run the test again, I immediately have this spec failure result shown:
expected result to have changed from 0 to 1, but did not change
When removing the change { ... } expectation...
it 'balances the consecutive numbers', focus: true do
expect(controller).to receive(:create)
post :create, params: {project_id: #project.id, finding: {requirement_id: #requirement.id}}
end
...it passes again:
1 example, 0 failures
But still, binding.pry in #create isn't called!
So what's going on here? Somehow expect(controller).to receive(:create) seems to prevent the actual #create action from being executed! This is not what I want. I want it to execute as always.
What am I doing wrong here?
When you use expect().to receive() the real method is supressed... It just verify if something was called as expected...
We usually use it when we want test if something we don't control the answer or we want mock the answer was called.
If you want check if something was called, and also run the original, you should use:
expect(something).to receive(:some_method).and_call_original
https://relishapp.com/rspec/rspec-mocks/v/3-5/docs/configuring-responses/calling-the-original-implementation
describe 'Feature' do
before do
setup
end
describe 'Success' do
before do
setup_for_success
end
specify 'It works' do
...
end
end
end
RSpec will always run the setup before setup_for_success. It there a way to run setup_for_success first?
You can do this by scoping a before(:all) to run before a before(:each) try this:
describe 'Feature' do
before(:each) do
puts "second"
end
describe 'Success' do
before(:all) do
puts "first"
end
specify 'It works' do
...
end
end
end
# =>
10:29:54 - INFO - Running: spec
Run options: include {:focus=>true}
first
second
.
Finished in 0.25793 seconds (files took 2.52 seconds to load)
1 example, 0 failures
EDIT:
In Rspec 2, the actions run in this order:
before suite
before all
before each
after each
after all
after suite
Here's a link to the docs showing the order that the methods are called in: https://www.relishapp.com/rspec/rspec-core/v/2-2/docs/hooks/before-and-after-hooks#before/after-blocks-are-run-in-order
Apparently in Rspec 3.5, the before block calls have a different naming that also works. They run in this order:
before :suite
before :context
before :example
after :example
after :context
after :suite
describe 'Feature' do
before(:example) do
puts "second"
end
describe 'Success' do
before(:context) do
puts "first"
end
specify 'It works' do
...
end
end
end
10:59:45 - INFO - Running: spec
Run options: include {:focus=>true}
first
second
.
Finished in 0.06367 seconds (files took 2.57 seconds to load)
1 example, 0 failures
Here's the newer docs:
http://www.relishapp.com/rspec/rspec-core/v/3-5/docs/hooks/before-and-after-hooks
before filters are appended in the order they're specified. Since RSpec 2.10.0, you can prepend them instead by making them prepend_before filters.
Likewise, after filters are prepended by default, but you can append_after them instead.
Your code would end up as follows (compacted for brevity):
describe 'Feature' do
before { setup }
describe 'Success' do
prepend_before { setup_for_success }
it 'works' { ... }
end
end
It seems a little weird you put in a nested context what you need in the outer one. I suspect that you don't need that setup in all of the nested contexts. If that's the case you need to filtering your hooks.
RSpec.describe 'Feature' do
before :each, success: true do
setup_for_success
end
before :each do
setup
end
describe 'Success', success: true do
specify 'It works' do
...
end
end
describe 'Fail' do
specify 'Won´t work' do
...
end
end
end
You can do this without nesting:
RSpec.describe 'Feature' do
before :each, success: true do
setup_for_success
end
before :each do
setup
end
specify 'It works', success: true do
...
end
specify 'Won´t work' do
...
end
end
Here is the link to the docs:
https://relishapp.com/rspec/rspec-core/docs/hooks/filters
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
Help me make this test pass:
Here is an example of some rspec code,
class User
attr_accessor :count
def initialize
#count = 0
end
# sometimes raises
def danger
puts "IO can be dangerous..."
rescue IOError => e
#count += 1
end
#always raises
def danger!
raise IOError.new
rescue IOError => e
#count += 1
end
end
describe User do
describe "#danger!" do
it "its rescue block always increases the counter by one" do
allow(subject).to receive(:'danger!')
expect {
subject.danger!
}.to change(subject, :count).by(1)
end
end
describe "#danger" do
context "when it rescues an exception" do
it "should increase the counter" do
allow(subject).to receive(:danger).and_raise(IOError)
expect {
subject.danger
}.to change(subject, :count).by(1)
end
end
end
end
I've also created a fiddle with these tests in it, so you can just make them pass. Please help me test the rescue block of a method!
Background:
My original question went something like this:
I have a method, like the following:
def publish!(resource)
published_resource = resource.publish!(current_project)
resource.update(published: true)
if resource.has_comments?
content = render_to_string partial: "#{ resource.class.name.tableize }/comment", locals: { comment: resource.comment_content_attributes }
resource.publish_comments!(current_project, published_resource.id, content)
end
true
rescue Bcx::ResponseError => e
resource.errors.add(:base, e.errors)
raise e
end
And I want to test that resource.errors.add(:base, e.errors) is, in fact, adding an error to the resource. More generally, I want to test the rescue block in a method.
So I'd like to write code like,
it "collects errors" do
expect{
subject.publish!(training_event.basecamp_calendar_event)
}.to change(training_event.errors.messages, :count).by(1)
end
Of course, this raises an error because I am re-raising in the rescue block.
I've seen a few answers that use the old something.stub(:method_name).and_raise(SomeException), but rspec complains that this syntax is deprecated. I would like to use Rspec Mocks 3.3 and the allow syntax, but I'm having a hard time.
allow(something).to receive(:method_name).and_raise(SomeException)
would be the new allow syntax. Check out the docs for reference.
I was misunderstanding what the allow syntax is actually for. So to make my example specs pass, I needed to do this:
describe "#danger" do
context "when it rescues an exception" do
it "should increase the counter" do
allow($stdout).to receive(:puts).and_raise(IOError) # <----- here
expect {
subject.danger
}.to change(subject, :count).by(1)
end
end
end
This thing that I'm stubing is not the method, or the subject, but the object that might raise. In this case I stub $stdout so that puts will raise.
Here is another fiddle in which the specs are passing.