I have a presenter:
class MyPresenter < Decorator
. . .
def items
. . .
end
# a method being tested which uses the above method
def saved_items
items.reject { |m| m.new_record? }
end
end
and its test:
describe MyPresenter do
. . .
describe "#saved_items" do
subject { MyPresenter.new(container) }
it "doesn't include unsaved items" do
# I want to stub items method:
subject.should_receive(:items).and_return([])
subject.saved_items.should == []
end
end
end
For some reason, this test fails with the following error:
1) MyPresenter#saved_items doesn't include unsaved items
Failure/Error: subject.saved_items.should == []
Double received unexpected message :items with (no args)
# ./app/presenters/my_presenter.rb:35:in `items'
# ./app/presenters/my_presenter.rb:42:in `saved_items'
# ./spec/presenters/my_presenter_spec.rb:78:in `block (3 levels) in <top (required)>'
Why does it fail? Why it calls the items method although I have stubbed it?
Actually I had the same issue and fixed it this way:
Instead of:
subject.should_receive
I put:
MyPresenter.any_instance.should_receive
Why don't you just not use the subject block:
it "doesn't include unsaved items" do
my_presenter = MyPresenter.new(container)
my_presenter.should_receive(:items).and_return([])
my_presenter.saved_items.should == []
end
In case you find duplication in your tests, you could extract out the object instantiation in a before block. Often times I find subject to be extremely useful, but sometimes simpler tests can result by not using it!
Related
I am trying to write a class in my code to wrap some of the RSpec calls. However, whenever I try to access rspec things, my class simply doesn't see the methods.
I have the following file defined in spec/support/helper.rb
require 'rspec/mocks/standalone'
module A
class Helper
def wrap_expect(dbl, func, args, ret)
expect(dbl).to receive(func).with(args).and_return(ret)
end
end
end
I get a NoMethodError: undefined method 'expect', despite requiring the correct module. Note that if I put calls to rspec functions before the module, everything is found correctly.
I've tried adding the following like to my spec_helper.rb:
config.requires << 'rspec/mocks/standalone'
But to no avail.
I managed to use class variables in my class and passing the functions through from the global context, but that solution seems quite extreme. Also I was able to pass in the test context itself and storing it, but I'd rather not have to do that either.
expect functions by default is associated with only rspec-core methods like it before . If you need to have expect inside a method, you can try adding the Rspec matcher class in the helper file.
include RSpec::Matchers
that error because the self which call expect is not the current rspec context RSpec::ExampleGroups, you could check by log the self
module A
class Helper
def wrap_expect(dbl, func, args, ret)
puts self
expect(dbl).to receive(func).with(args).and_return(ret)
end
end
end
# test case
A::Helper.new.wrap_expect(...) # log self: A::Helper
so obviously, A::Helper does not support expect
now you have 2 options to build a helper: (1) a module or (2) a class which init with the current context of test cases:
(1)
module WrapHelper
def wrap_expect(...)
puts self # RSpec::ExampleGroups::...
expect(...).to receive(...)...
end
end
# test case
RSpec.describe StackOverFlow, type: :model do
include WrapHelper
it "...." do
wrap_expect(...) # call directly
end
end
(2)
class WrapHelper
def initialize(spec)
#spec = spec
end
def wrap_expect(...)
puts #spec # RSpec::ExampleGroups::...
#spec.expect(...).to #spec.receive(...)...
end
end
# test case
RSpec.describe StackOverFlow, type: :model do
let!(:helper) {WrapHelper.new(self)}
it "...." do
helper.wrap_expect(...)
end
end
I'm running into a weird error:
Class:
module AnimalSanctuary
module AnimalInspector
class AnimalPicker
def initialize(number_of_animals, ids)
#number_of_animals = number_of_animals
#ids = ids
end
...
def pick_animals(animal)
end
end
end
test:
require 'rails_helper'
RSpec.describe AnimalSanctuary::AnimalInspector::AnimalPicker do
describe ".pick_animals" do
context "pick an available animal" do
it "returns animal name" do
expect(AnimalSanctuary::AnimalInspector::AnimalPicker.pick_animals("Dog")).to eq("Dog")
end
end
end
end
I get the following error:
NoMethodError:
undefined method `pick_animals' for AnimalSanctuary::AnimalInspector::AnimalPicker:Class
Rspec calls the class but not the method which has stumped me. Am I doing something wrong?
The definition of pick_animals is an instance method.
To call it, you will need to instantiate an object of the class using the new method as shown below. I have passed in random values to your initializer (1, [1,2]) however you can set them as you like.:
number_of_animals = 1
ids = [1,2]
AnimalSanctuary::AnimalInspector::AnimalPicker.new(number_of_animals, ids).pick_animals("Dog")
Otherwise, to call it the way you are calling it, you will need to redefine it as a class method by using self.pick_animals as shown below:
module AnimalSanctuary
module AnimalInspector
class AnimalPicker
...
def self.pick_animals(animal)
end
end
end
yeah pick_animals is an instance method.
you can use the following in your rspec
expect_any_instance_of(nimalSanctuary::AnimalInspector::AnimalPicker).to receive(:pick_animals).with("dogs").to_eq("Dog")
Hope this helps
I'm new to testing with RSpec and FactoryBot so any help would be appreciated. I've encountered an odd case with the following code/tests.
Here are my models:
class Foo < ActiveRecord::Base
has_many :bars, dependent: :destroy
def update_baz_count(baz_count)
most_recent_bar.update_current_baz_count(baz_count)
end
def most_recent_bar
bars.last
end
end
class Bar < ActiveRecord::Base
belongs_to :foo
def update_current_baz_count(new_baz_count)
self.baz_count = new_baz_count
self.save
end
end
And here are my tests:
describe Foo do
# This test passes
describe "#most_recent_bar" do
let!(:foo) { create(:foo) }
let!(:bar) { create(:bar, foo: foo) }
it 'should return the most recent bar' do
expect(foo.most_recent_bar).to eq(bar)
end
end
describe '#update_baz_count' do
let!(:foo) { create(:foo) }
let!(:bar) { create(:bar, foo: foo) }
it 'should call update_current_bar_count on the storage history' do
## Test will NOT pass unless following line is uncommented:
# expect(foo).to receive(:most_recent_bar).and_return(bar)
expect(bar).to receive(:update_current_baz_count).with(1)
foo.update_baz_count(1)
end
end
end
The issue is that in my #update_baz_count test passing is contingent on setting an expectation regarding the #most_recent_bar method. As noted above, my test for #most_recent_bar passes, and it feels redundant to be making assertions about the performance of that method outside of its dedicated test.
So, why is the success of my test contingent on the line expect(foo).to receive(:most_recent_bar).and_return(bar)?
The problem is that you set up the mocking behavior on the object available in specs:
expect(bar).to receive(:update_current_baz_count).with(1)
But! In your production code, the same row will be fetched from db:
bars.last
And AR will create a new object for you, which have no idea that you mocked it in your specs.
You can check it out like this:
expect(bar.object_id).to eq foo.most_recent_bar.object_id
Which will fail.
If you want to do it without mocking, do something like this:
it 'should update update_current_bar_count on the storage history' do
expect{ foo.update_baz_count(1) }
.to change { bar.reload.field_that_baz_count_updates}.from(0).to(1)
end
So instead of checking that the method was called, check the effect the calling of the method had on the "world".
I am writing a spec for an after_create callback. The spec looks like this:
it 'broadcasts creation' do
message = Message.create(body: 'foo')
expect(Redis.any_instance).to have_received(:publish)
end
My Message model looks like this:
class Message < ActiveRecord::Base
after_create -> { publish(:create) }
private
def publish(name)
Redis.new.publish(
self.class.inferred_channel_name,
json(action)
)
Redis.new.publish(
inferred_channel_name_for_single_record,
json(action)
)
puts 'published!'
end
end
I know that the callback runs because I am printing 'published' at the end, and I have verified that Redis indeed publishes something twice.
Nonetheless, my spec fails with the following message:
1) Message Methods #entangle without options broadcasts creation
Failure/Error: expect(Redis.any_instance).to have_received(:publish)
unstubbed, expected exactly once, not yet invoked: #<AnyInstance:Redis>.publish(any_parameters)
# ./spec/models/message_spec.rb:20:in `block (5 levels) in <top (required)>'
I am using bourne with mocha to use the have_received matcher.
How can I get this test to pass?
Create a mock for Redis and stub out the class and instance methods — new and publish, respectively.
it "broadcasts creation" do
redis = stub_redis
Message.create(body: "foo")
expect(redis).to have_received(:publish).twice
end
def stub_redis
mock("redis").tap do |redis|
redis.stubs(:publish)
Redis.stubs(:new).returns(redis)
end
end
You could try using the expect_any_instance_of mock.
it 'broadcasts creation' do
expect(Redis.any_instance).to receive(:publish).twice
message = Message.create(body: 'foo')
end
https://www.relishapp.com/rspec/rspec-mocks/v/3-2/docs/working-with-legacy-code/any-instance
My methods :
Shoulda::Context.send(:include, Module.new do
def when_reminder_sent(n, &blk)
context "when reminder #{n} was sent" do
setup { subject.free_trial_notice_sent = n }
blk.bind(self).call
end
end
def when_payment_was_received(&blk)
context 'when payment has been received' do
setup { subject.last_payment_received_at = 1.day.ago }
blk.bind(self).call
end
end
end)
It injects and returns true. So I know its in there. But when I do :
context 'on day 15' do
when_reminder_sent(15) { should('be nil') { assert_nil subject.reminder_to_send }}
It returns :
NoMethodError: undefined method `when_reminder_sent' for EnterpriseRegistrationTest:Class
Ah so I see its trying to use this method on my tested Model.. how can I ensure that it is using the method within its original Shoulda context?
*edit : fixed some typos *
dyld: DYLD_ environment variables being ignored because main executable (/bin/ps) is setuid or setgid
/Users/elephanttrip/.rvm/gems/ruby-1.9.3-p125#website/gems/activesupport-3.2.14/lib/active_support/inflector/methods.rb:230: Use RbConfig instead of obsolete and deprecated Config.
/Users/elephanttrip/.rvm/gems/ruby-1.9.3-p125#website/gems/shoulda-context-1.1.6/lib/shoulda/context/context.rb:474:in `method_missing': undefined method `when_reminder_sent' for EnterpriseRegistrationTest:Class (NoMethodError)
from /Users/elephanttrip/Sites/website/test/unit/enterprise_registration_test.rb:578:in `block (5 levels) in <class:EnterpriseRegistrationTest>'
from /Users/elephanttrip/.rvm/gems/ruby-1.9.3-p125#website/gems/shoulda-context-1.1.6/lib/shoulda/context/context.rb:322:in `instance_exec'
from /Users/elephanttrip/.rvm/gems/ruby-1.9.3-p125#website/gems/shoulda-context-1.1.6/lib/shoulda/context/context.rb:322:in `merge_block'
So I just rewrote this and threw it at the beginning of the test. Evidently this module injection isn't properly coded for the upgrade.
Winning Answer
class MyMassiveObjectTest < ActiveSupport::TestCase
def self.when_reminder_sent(n, &blk)
context "when reminder #{n} was sent" do
setup { subject.free_trial_notice_sent = n }
blk.bind(self).call
end
end
def self.when_payment_was_received(&blk)
context 'when payment has been received' do
setup { subject.last_payment_received_at = 1.day.ago }
blk.bind(self).call
end
end