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>
If you want to set a controller or view's instance variables in your RSpec test, then call assign either in a before block or at the start of an example group. The first argument is the name of the instance variable while the second is the value you want to assign to it.
Using isinstance() function, we can test whether an object/variable is an instance of the specified type or class such as int or list. In the case of inheritance, we can checks if the specified class is the parent class of an object. For example, isinstance(x, int) to check if x is an instance of a class int .
Running tests by their file or directory names is the most familiar way to run tests with RSpec. RSpec can take a file name or directory name and run the file or the contents of the directory. So you can do: rspec spec/jobs to run the tests found in the jobs directory.
An instance variable in ruby has a name starting with @ symbol, and its content is restricted to whatever the object itself refers to. Two separate objects, even though they belong to the same class, are allowed to have different values for their instance variables.
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
I don't think this is possible to test unless Rails changes its implementation so that it actually provides access to the ActionMailer (controller) object and not just the Mail object that is generated.
As Peter Alfvin pointed out, the problem is that it returns the 'message' here:
new(method_name, *args).message
instead of just returning the mailer (controller) like this:
new(method_name, *args)
This post on the rspec-rails list might also be helpful:
Seems reasonable, but unlikely to change. Here's why. rspec-rails provides wrappers around test classes provided by rails. Rails functional tests support the three questions you pose above, but rails mailer tests are different. From http://guides.rubyonrails.org/action_mailer_basics.html: "Testing mailers normally involves two things: One is that the mail was queued, and the other one that the email is correct."
To support what you'd like to see in mailer specs, rspec-rails would have to provide it's own ExampleGroup (rather than wrap the rails class), which would have to be tightly bound to rails' internals. I took great pains in rspec-rails-2 to constrain coupling to public APIs, and this has had a big payoff: we've only had one case where a rails 3.x release required a release of rspec-rails (i.e. there was a breaking change). With rails-2, pretty much every release broke rspec-rails because rspec-rails was tied to internals (rspec-rails' fault, not rails).
If you really want to see this change, you'll need to get it changed in rails itself, at which point rspec-rails will happily wrap the new and improved MailerTestCase.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With