How Would You Test Observers with Rspec in a Ruby on Rails Application

How would you test observers with rSpec in a Ruby on Rails application?

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.

Rails 3.2.9.Testing observer with RSpec(trouble with should_receive)

should_receive prevents the actual method from being called.

You should create two separate tests. One to check that the log is added to the todo, and one to check that the log is created.

describe 'after_create' do
it "should add a log to the todo" do
todo = Todo.new(@attr_todo)
todo.should_receive(:add_log).with('creating')
todo.save!
end

it "should create a new logdata" do
todo = Todo.new(@attr_todo)
expect {
todo.save!
}.to change {Logdata.count}.by(1)
end
end

How to test Mongoid::Observer with rspec

I have got a working stand-alone adaptation of your code (see below). I had to make three small changes to get it working the way you were expecting.

To get the Observer working at all you have to instantiate it. In my example I needed to add the lines:

Mongoid.observers = CommentBadgeObserver
Mongoid.instantiate_observers

In Rails you can achieve the same thing adding this to config/application.rb (according to the docs):

config.mongoid.observers = :comment_badge_observer

I think there is also a small logic error in CommentBadge.check_conditions_for, the > 1 should be > 0.

Finally I changed the User#award method to save the badge rather than the user, because the 'foreign key' field that stores the relationship is on the badge side.

class Comment
include Mongoid::Document
field :name
belongs_to :user
end

class CommentBadgeObserver < Mongoid::Observer
observe :comment

def after_create(comment)
CommentBadge.check_conditions_for(comment.user)
end
end

class Badge
include Mongoid::Document
field :title
belongs_to :user
end

class CommentBadge < Badge
def self.check_conditions_for(user)
if user.comments.size > 0
badge = CommentBadge.create!(:title => "Comment badge")
user.award(badge)
end
end
end

class User
include Mongoid::Document
field :first_name
has_many :comments
has_many :badges

def award(badge)
self.badges << badge
badge.save!
end
end

Factory.define(:user) do |u|
u.first_name 'Bob'
end

Factory.define(:comment) do |c|
c.name 'Some comment...'
end

# Observers need to be instantiated
Mongoid.observers = CommentBadgeObserver
Mongoid.instantiate_observers

describe CommentBadgeObserver do
it 'should create badges' do
@user = Factory.build(:user)
@comment = Factory(:comment, :user => @user)
@user.badges.count.should == 1
@user.badges[0].title.should == "Comment badge"
end
end

How can I use Ruby RSpec to test a class that adds an observer?

You should be able to check message expectations on an instance of MyTask.
For example,

it "is observable" do
task = MyTask.new
task.should_receive :add_observer
task.should_receive :changed
task.should_receive :notify_observers

task.perform
end

Ideally, I wouldn't do it this way since a lot of message expectations on the object under test are being made. Instead, I might check some external state that is a result of calling perform.

EDIT
If you want to check instance methods on MyObserver, you could do something like this, before calling perform:

observer = mock
MyObserver.stub(:new).and_return(observer)
observer.should_receive(:some_method) #replace `some_method` with whatever instance method you want

Rspec feature test with sidekiq mail delivery, how to wait for mail observer?

Actions in Capybara (click_link, etc) know nothing about any behaviors they trigger. Because of this there is no guarantee as to what the app will have done after your click_link line returns, other than the link will have been clicked, and the browser will have started to perform whatever that action triggers. Your test then immediately checks 'MyModel.first.done?` while the browser could still be submitting a request (This is one reason why directly checking database records in feature tests is generally frowned upon).

The solution to that (and end up with tests that will work across multiple drivers reliably is to check for a visual change on the page that indicates the action has completed. You also need to set up ActiveJob properly for testing so you can make sure the jobs are executed. To do this you will need to include ActiveJob::TestHelper, which can be done in your RSpec config or individual scenarios, and you will need to make sure ActiveJob::Base.queue_adapter = :test is set (can be done in config/environment/tests.rb file if wanted). Then assuming your app shows a message "Mail sent!" on screen when the action has completed you would do

include ActiveJob::TestHelper 
ActiveJob::Base.queue_adapater = :test
...
perform_enqueued_jobs do
click_link "Go!"
expect(page).to have_text('Mail sent!') # This will wait for the message to appear, which guarantees the action has completed and enqueued the job
end # when this returns any jobs enqueued during the block will have been executed

expect(MyModel.first.done?).to be true

Rails observers are not firing when running Capybara integration tests

This isn't truly an answer to my question, but ain't nobody got time to wait around. No one cares about observers anymore because they were removed form Rails 4. There are next to no google topics related to them and capybara.

So, in order move on and get integration test coverage on the code I need, I'm refactoring my code to use concerns that include callbacks. This eliminates the observer dependency, and I was actually able to abstract my code even further this way since many of my observers were doing pretty much the same thing, and I had the core of what my observers were calling in service objects anyway.

Integration tests have no problem triggering these.

RIP Observers.

https://stackoverflow.com/a/18581715/2202674



Related Topics



Leave a reply



Submit