Search code examples
ruby-on-railsrubyrspecmongoidobserver-pattern

How to test Mongoid::Observer with rspec


On a simple mongoid data model with a user that has many comments, I want to award the user with a specific badge when he writes at least 1 comment. So I set up an observer like this :

class CommentBadgeObserver < Mongoid::Observer
  observe :comment

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

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

The user.award method :

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

The following test fails (but I guess it is normal because observers are executed in background ?)

it 'should award the user with comment badge' do
    @comment = Factory(:comment, :user => @user)
    @user.badges.count.should == 1
    @user.badges[0].title.should == "Comment badge"
end

What could be the best way to validate this behavior ?


Solution

  • 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