I'm implemented a condition that if a user's email is not confirmed, they do not receive emails. My logic is, if the email isn't verified/ confirmed, return an instance of ActionMailer::Base::NullMail.new.
I wrote this test
if #user.verified?
next
else
ActionMailer::Base::NullMail.new
end
expect(TestMailer.test_mail(expired_account)).to be_an_instance_of(ActionMailer::Base::NullMail)
and the error I'm getting is
expected #<ActionMailer::MessageDelivery(#<ActionMailer::Base::NullMail:0x00007fb0f2f24090>)> to be a kind of ActionMailer::Base::NullMail
The question is, how can I re-write my test so that I can test that if the emails aren't verified they should return an instance of ActionMailer::Base::NullMail.new
The error is telling you what the problem is right? You expect an instance of a mail type object, so change it to this. Also you should be able to use be_a method in place of be_an_instance_of in Rspec see docs
expect(TestMailer.test_mail(expired_account).message)
.to be_an(ActionMailer::Base::NullMail)
Related
I have an ActiveRecord object User. In the app I am making I am using a single sign on gem, but I need to save some of the user data in our databse. My ApplicationController has this code:
def create_user
User.create(name: current_user['name'], email: current_user['email'], company_id: current_user['id'])
end
I need an RSpec test that mocks out the actual create call. I have tried
allow_any_instance_of(User).to receive(:create).with(any_args).and_return(user)
which returns an error saying "User does not implement create".
jvillian is correct that the problem is that create is implemented by User, not by an instance of User. The simple fix is just to stub the method directly on User (i.e. use allow instead of allow_any_instance_of):
allow(User).to receive(:create).with(any_args).and_return(user)
Also, .with(any_args) is a no-op, so this is equivalent:
allow(User).to receive(:create).and_return(user)
I'm thinking that allow_any_instance_of is expecting an instance of User to implement create. create, however, is a class method. So, I believe the error messages is saying that instances of User don't implement create.
I'd suggest seeing if class_double works for your use case. Check out Rspec Mocks and this SO post from Myron Marston.
In an action called via a post request I'm creating a resource call RequestOffer and send an email with ActionMailer using the created resource as a parameter:
#request_offer = RequestOffer.new(request_offer_params)
if #request_offer.save
RequestOfferMailer.email_team(#request_offer).deliver_later
end
When my controller spec, I want to test that my RequestOfferMailer is called using the method email_team with the resource #request_offer as a parameter.
When I want to user expect(XXX).to receive(YYY).with(ZZZ), the only way I found was to declare my expectation before making the POST request. However, ZZZ is created by this POST request, so I have no way to set my expectation before.
# Set expectation first
message_delivery = instance_double(ActionMailer::MessageDelivery)
# ZZZ used in .with() does not exist yet, so it won't work
expect(RequestOfferMailer).to receive(:email_team).with(ZZZ).and_return(message_delivery)
expect(message_delivery).to receive(:deliver_later)
# Make POST request that will create ZZZ
post :create, params
Any idea how to solve this problem?
If this is a functional test then I would isolate the controller test from the DB. You can do this by using instance_doubles and let statements. Here's an example that you may like to extend for your purposes
describe '/request_offers [POST]' do
let(:request_offer) { instance_double(RequestOffer, save: true) }
before do
allow(RequestOffer).to receive(:new).
with(...params...).
and_return(request_offer)
end
it 'should instantiate a RequestOffer with the params' do
expect(RequestOffer).to receive(:new).
with(...params...).
and_return(request_offer)
post '/request_offers', {...}
end
it 'should email the request offer via RequestOfferMailer' do
mailer = instance_double(ActionMailer::MessageDelivery)
expect(RequestOfferMailer).to receive(:email_team).
with(request_offer).and_return(mailer)
post '/request_offers', {...}
end
end
The key to this is using 'let' to declare an instance double of the model that you intend to create. By setting expectations on the class you can inject your instance double into the test and isolate from the DB. Note that the 'allow' call in the before block is there to serve the later specs that set expectations on the mailer object; the 'expect' call in the first test will still be able to make assertions about the call.
Would it be enough to make sure the argument is an instance of RequestOffer? Then you could use the instance_of matcher. For example:
expect(RequestOfferMailer).to receive(:email_team).with(instance_of(RequestOffer)).and_return(message_delivery)
I found this option in the Rspec 3.0 docs: https://relishapp.com/rspec/rspec-mocks/v/3-0/docs/setting-constraints/matching-arguments
The last argument of the with method is a block. You can open up the arguments and do anything you like there.
expect(RequestOfferMailer)
.to receive(:email_team)
.with(instance_of(RequestOffer)) do |request_offer|
expect(request_offer.total).to eq(100) # As one example of what you can to in this block
end.and_return(message_delivery)
You can also set the instance_of matcher to be anything if you're not even sure what object type you're expecting.
In my rails 4.2.5 (ruby 2.2.1) app I have an actionmailer class that is so simple. It doesn't even send mail, just does a printf:
class UserTommail < ActionMailer::Base
def joe
printf("\n***** In Emails.joe")
end
end
But when my controller calls this function, it never does the printf!
def contact_us
printf("\n***** TOMS EMAILLER")
UserTommail.joe()
redirect_to(root_path(), :notice => "Your Contact Us message has been successfully sent.")
printf("\n**** TOMS END")
end
The two printfs in the controller actually print their message, but the one in joe() never does. No errors or anything.
If I sabotage joe() to say joe() in the file user_tommail.rb, I get an error that the function can't be found, so I know the controller knows about it.
What am I doing wrong?
You should call deliver_now to fire the mailer to send the email:
UserTommail.joe.deliver_now
See the full list of available methods in docs.
For Rails 4 to send a an email
UserTommail.joe.deliver_now
or
UserTommail.joe.deliver_later #Enqueues the email to be delivered through Active Job. When the job runs it will send the email using deliver_now.
For Rails 3 to send a an email
UserTommail.joe.deliver
The problem here is that ActionMailer tries to be "clever" about what it does, and won't actually call your mailer method until its return value is needed.
Your mailer method joe will return an ActionMailer::MessageDelivery object, which wraps a Mail::Message object (even though you haven't specifically said you want to send an email). The Mail::Message gets lazily evaluated, meaning it won't be instantiated (and your method won't be called) until it's needed.
One way of forcing the evaluation would be to try and send the returned email with deliver_now or deliver_later, but another way would simply be to inspect the message.
if you had my_email = UserTommail.joe() and then called my_email.message, it would force the method to be run and you would see your printf in the console.
I have an RSpec test like this:
it "should ..." do
# mailer = mock
# mailer.should_receive(:deliver)
Mailer.should_receive(:notification_to_sender)#.and_return(mailer)
visit transactions_path
expect do
page.should_not have_css("table#transactions_list tbody tr")
find('#some_button').click
page.should have_css("table#transactions_list tbody tr", :count => 1)
end.to change{Transaction.count}.by(1)
end
If I remove the commented pieces at the top, the test passes. But with the commented sections in place (how I'd expect to write it) the test fails.
I got the commented pieces off some of googling around the net, but I don't really understand what it's doing or why this fixes it. It seems like there should be a cleaner way to test emails without this.
Can anyone shed some light? Thanks!
I'm using rails 3 and rspec-rails 2.10.1
I think you want an instance of Mailer to receive notification_to_sender not the class. From the Rails API
You never instantiate your mailer class. Rather, your delivery instance methods are automatically wrapped in class methods that start with the word deliver_ followed by the name of the mailer method that you would like to deliver. The signup_notification method defined above is delivered by invoking Notifier.deliver_signup_notification.
Therefore I would use
Mailer.any_instance.should_receive(:notification_to_sender)
Also, if you need to get the last delivered message, use
ActionMailer::Base.deliveries.last
I think that should solve your problem.
You're likely calling Mailer.notification_to_sender.deliver in your controller, or better yet, a background job. I'm guessing notification_to_sender probably takes a parameter as well.
Anyways, when you call the notification_to_sender method on Mailer you're getting back an instance of Mail::Message that has the deliver method on it. If you were simply doing Mailer.notification_to_sender without also calling deliver, you could run what you have there with the comments and all would be fine. I would guess you're also calling deliver though.
In that case your failure message would be something like
NoMethodError:
undefined method `deliver' for nil:NilClass
That is because nil is Ruby's default return value much of the time, which Rails also inherits. Without specifying the mailer = mock and .and_return(mailer) parts, when the controller executes in context of the test then notification_to_sender will return nil and the controller will try to call deliver on that nil object.
The solution you have commented out is to mock out notification_to_sender's return value (normally Mail::Message) and then expect that deliver method to be called on it.
Assume this ruby code:
class User
def self.failed_login!(email)
user = User.find_by_email(email)
if user
user.failed_login_count = user.failed_login_count + 1
user.save
end
end
end
I want to write a test that tests that user.save is never called when an invalid email is given. E.g.:
it "should not increment failed login count" do
User.expects(:save).never()
User.failed_login!("doesnotexist")
end
This test currently passes, but it also passes when I provide a valid email address.
How do I set up the expectation using Mocha? (or any other mocking framework) such that it tests the save method of any User instance is never called?
(preferably without stubbing/mocking the find_by_email method, as the implementation of how to get the user might change in the future)
Cheers
For others that might have stumbled unto this, I found the answer in another post that was dealing with RR as the mocking framework... in Mocha you can do this:
User.any_instance.expects(:save).never()
alternatively you could do something like
user = mock
User.expects(:find).returns user
user.expects(:save).never