how do I test that an instance variable is set in my my mailer with rspec?

pixelearth picture pixelearth · Jan 12, 2014 · Viewed 21.6k times · Source

How do I test that a certain instance variable is set in my my mailer with rspec? assigns is coming back undefined..

require File.dirname(__FILE__) + '/../../spec_helper'

describe UserMailer do

  it "should send the member user password to a User" do
    user = FG.create :user

    user.create_reset_code

    mail = UserMailer.reset_notification(user).deliver

    ActionMailer::Base.deliveries.size.should == 1  

    user.login.should be_present  

    assigns[:person].should == user
    assigns(:person).should == user #both assigns types fail
  end
end

The error returned is:

undefined local variable or method `assigns' for #<RSpec::Core::ExampleGroup::Nested_1:0x007fe2b88e2928>

Answer

Peter Alfvin picture Peter Alfvin · Jan 12, 2014

assigns is only defined for controller specs and that's done via the rspec-rails gem. There is no general mechanism to test instance variables in RSpec, but you can use Kernel's instance_variable_get to access any instance variable you want.

So in your case, if object were the object whose instance variable you were interested in checking, you could write:

expect(object.instance_variable_get(:@person)).to eql(user)

As for getting ahold of the UserMailer instance, I can't see any way to do that. Looking at the method_missing definition inside https://github.com/rails/rails/blob/master/actionmailer/lib/action_mailer/base.rb, a new mailer instance will be created whenever an undefined class method is called with the same name as an instance method. But that instance isn't saved anywhere that I can see and only the value of .message is returned. Here is the relevant code as currently defined on github:

Class methods:

  def respond_to?(method, include_private = false) #:nodoc:
    super || action_methods.include?(method.to_s)
  end

  def method_missing(method_name, *args) # :nodoc:
    if respond_to?(method_name)
      new(method_name, *args).message
    else
      super
    end
  end

Instance methods:

attr_internal :message

# Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
# will be initialized according to the named method. If not, the mailer will
# remain uninitialized (useful when you only need to invoke the "receive"
# method, for instance).
def initialize(method_name=nil, *args)
  super()
  @_mail_was_called = false
  @_message = Mail.new
  process(method_name, *args) if method_name
end

def process(method_name, *args) #:nodoc:
  payload = {
    mailer: self.class.name,
    action: method_name
  }

  ActiveSupport::Notifications.instrument("process.action_mailer", payload) do
    lookup_context.skip_default_locale!

    super
    @_message = NullMail.new unless @_mail_was_called
  end
end