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
Related
Trying to test a module. It works when executed in rails console, but not when written as a test. Suppose the following:
MyModel
a) has_many :my_other_model
MyOtherModel
a) belongs to :my_model
Module example:
module MyModule
def self.doit
mine = MyModel.first
mine.my_other_models.create!(attribute: 'Me')
end
end
Now test:
require 'test_helper'
class MyModuleTest < ActiveSupport::TestCase
test "should work" do
assert MyModule.doit
end
end
Returns:
NoMethodError: NoMethodError: undefined method `my_other_models' for nil:NilClass
Now try the same thing in the console:
rails c
MyModule.doit
Works just fine. But why not as a test?
Your test database is empty when you run this test, so calling MyModel.first is going to return nil, then you try to chain an unknown method to nil. What you'll probably want for your test suite is a fixture, which is just sample data. For now, you you can just create the first instance to get the test to work.
test "should work" do
MyModel.create #assuming the model is not validated
assert MyModule.doit
end
You could also refactor your module. Adding if mine will only try to create the other models if mine is not nil. That would get the test to pass, but negates the purpose of your test.
def self.doit
mine = MyModel.first
mine.my_other_models.create!(attribute: 'Me') if mine
end
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
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!
I'm running a strange problem sending emails. I'm getting this exception:
ArgumentError (wrong number of arguments (1 for 0)):
/usr/lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active_record/base.rb:642:in `initialize'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active_record/base.rb:642:in `new'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active_record/base.rb:642:in `create'
/usr/lib/ruby/gems/1.8/gems/ar_mailer-1.3.1/lib/action_mailer/ar_mailer.rb:92:in `perform_delivery_activerecord'
/usr/lib/ruby/gems/1.8/gems/ar_mailer-1.3.1/lib/action_mailer/ar_mailer.rb:91:in `each'
/usr/lib/ruby/gems/1.8/gems/ar_mailer-1.3.1/lib/action_mailer/ar_mailer.rb:91:in `perform_delivery_activerecord'
/usr/lib/ruby/gems/1.8/gems/actionmailer-2.1.1/lib/action_mailer/base.rb:508:in `__send__'
/usr/lib/ruby/gems/1.8/gems/actionmailer-2.1.1/lib/action_mailer/base.rb:508:in `deliver!'
/usr/lib/ruby/gems/1.8/gems/actionmailer-2.1.1/lib/action_mailer/base.rb:383:in `method_missing'
/app/controllers/web_reservations_controller.rb:29:in `test_email'
In my web_reservations_controller I have a simply method calling
TestMailer.deliver_send_email
And my TesMailer is something like:
class TestMailer < ActionMailer::ARMailer
def send_email
#recipients = "xxx#example.com"
#from = "xxx#example.com"
#subject = "TEST MAIL SUBJECT"
#body = "<br>TEST MAIL MESSAGE"
#content_type = "text/html"
end
end
Do you have any idea?
Thanks!
Roberto
The problem is with the model that ar_mailer is using to store the message. You can see in the backtrace that the exception is coming from ActiveRecord::Base.create when it calls initialize. Normally an ActiveRecord constructor takes an argument, but in this case it looks like your model doesn't. ar_mailer should be using a model called Email. Do you have this class in your app/models directory? If so, is anything overridden with initialize? If you are overriding initialize, be sure to give it arguments and call super.
class Email < ActiveRecord::Base
def initialize(attributes)
super
# whatever you want to do
end
end
Check that email_class is set correctly: http://seattlerb.rubyforge.org/ar_mailer/classes/ActionMailer/ARMailer.html#M000002
Also don't use instance variables. Try:
class TestMailer < ActionMailer::ARMailer
def send_email
recipients "roberto.druetto#gmail.com"
from "roberto.druetto#gmail.com"
subject "TEST MAIL SUBJECT"
content_type "text/html"
end
end
From the docs: the body method has special behavior. It takes a hash which generates an instance variable named after each key in the hash containing the value that that key points to.
So something like this added to the method above:
body :user => User.find(1)
Will allow you to use #user in the template.
Suppose you have an ActiveRecord::Observer in one of your Ruby on Rails applications - how do you test this observer with rSpec?
You are on the right track, but I have run into a number of frustrating unexpected message errors when using rSpec, observers, and mock objects. When I am spec testing my model, I don't want to have to handle observer behavior in my message expectations.
In your example, there isn't a really good way to spec "set_status" on the model without knowledge of what the observer is going to do to it.
Therefore, I like to use the "No Peeping Toms" plugin. Given your code above and using the No Peeping Toms plugin, I would spec the model like this:
describe Person do
it "should set status correctly" do
#p = Person.new(:status => "foo")
#p.set_status("bar")
#p.save
#p.status.should eql("bar")
end
end
You can spec your model code without having to worry that there is an observer out there that is going to come in and clobber your value. You'd spec that separately in the person_observer_spec like this:
describe PersonObserver do
it "should clobber the status field" do
#p = mock_model(Person, :status => "foo")
#obs = PersonObserver.instance
#p.should_receive(:set_status).with("aha!")
#obs.after_save
end
end
If you REALLY REALLY want to test the coupled Model and Observer class, you can do it like this:
describe Person do
it "should register a status change with the person observer turned on" do
Person.with_observers(:person_observer) do
lambda { #p = Person.new; #p.save }.should change(#p, :status).to("aha!)
end
end
end
99% of the time, I'd rather spec test with the observers turned off. It's just easier that way.
Disclaimer: I've never actually done this on a production site, but it looks like a reasonable way would be to use mock objects, should_receive and friends, and invoke methods on the observer directly
Given the following model and observer:
class Person < ActiveRecord::Base
def set_status( new_status )
# do whatever
end
end
class PersonObserver < ActiveRecord::Observer
def after_save(person)
person.set_status("aha!")
end
end
I would write a spec like this (I ran it, and it passes)
describe PersonObserver do
before :each do
#person = stub_model(Person)
#observer = PersonObserver.instance
end
it "should invoke after_save on the observed object" do
#person.should_receive(:set_status).with("aha!")
#observer.after_save(#person)
end
end
no_peeping_toms is now a gem and can be found here: https://github.com/patmaddox/no-peeping-toms
If you want to test that the observer observes the correct model and receives the notification as expected, here is an example using RR.
your_model.rb:
class YourModel < ActiveRecord::Base
...
end
your_model_observer.rb:
class YourModelObserver < ActiveRecord::Observer
def after_create
...
end
def custom_notification
...
end
end
your_model_observer_spec.rb:
before do
#observer = YourModelObserver.instance
#model = YourModel.new
end
it "acts on the after_create notification"
mock(#observer).after_create(#model)
#model.save!
end
it "acts on the custom notification"
mock(#observer).custom_notification(#model)
#model.send(:notify, :custom_notification)
end