In my Rails application I am testing a instance method that sends a SMS message out. It looks like this:
group.rb
def send_notifications
twilio_sms = Twilio::REST::Client.new('mykey', 'mykey')
begin
twilio_sms.account.messages.create(
from: "+188888888",
to: "+1#{phone_number}",
body: "Message from Company: New item uploaded to group"
)
rescue
puts 'Invalid number'
end
end
I'm having trouble figuring out how to test the twilio_sms.account.messages.create part.
My spec looks something like this so far:
group_spec.rb
describe Group do
context 'instance methods' do
describe '.send_notifictions' do
it 'calls the create method on the twilio messages object' do
twilio_messages = instance_double("Twilio::REST::Messages")
expect(twilio_messages).to receive(:create)
group = create(:group_with_notifications)
group.send_notifications
end
end
end
end
This obviously isn't working or I wouldn't be asking this question. What is the proper way to test that the create message was sent properly? Or am I approaching this the wrong way? Any help would be appreciated, thanks!
Try this
describe Group do
describe 'instance methods' do
describe '.send_notifictions' do
it 'creates a new message' do
twilio_sms = double(:twilio_sms)
twilio_messages = double(:twilio_messages)
expect(Twilio::REST::Client).to receive(:new).with('mykey', 'mykey').and_return(twilio_sms)
expect(twilio_sms).to receive(:account).and_return(twilio_messages)
expect(twilio_messages).to receive(:messages).and_return(twilio_messages)
expect(twilio_messages).to receive(:create).with({:from => "+188888888", :to => "+15555555", :body => "Message from Company: New item uploaded to group"}).and_return(true)
group = Group.create(some_attributes: 'foo')
group.send_notifications
end
end
end
end
Related
I have a SubscriptionHandler class with a call method that creates a pending subscription, attempts to bill the user and then error out if the billing fails. The pending subscription is created regardless of whether or not the billing fails
class SubscriptionHandler
def initialize(customer, stripe_token)
#customer = customer
#stripe_token = stripe_token
end
def call
create_pending_subscription
attempt_charge!
upgrade_subscription
end
private
attr_reader :stripe_token, :customer
def create_pending_subscription
#subscription = Subscription.create(pending: true, customer_id: customer.id)
end
def attempt_charge!
StripeCharger.new(stripe_token).charge! #raises FailedPaymentError
end
def upgrade_subscription
#subscription.update(pending: true)
end
end
Here is what my specs look like:
describe SubscriptionHandler do
describe "#call" do
it "creates a pending subscription" do
customer = create(:customer)
token = "token-xxx"
charger = StripeCharger.new(token)
allow(StripeCharger).to receive(:new).and_return(charger)
allow(charger).to receive(:charge!).and_raise(FailedPaymentError)
handler = SubscriptionHandler.new(customer, token)
expect { handler.call }.to change { Subscription.count }.by(1) # Fails with FailedPaymentError
end
end
end
But this does not change the subscription count, it fails with the FailedPaymentError. Is there a way to check that the subscription count increases without the spec blowing up with FailedPaymentError.
You should be able to use Rspec compound expectations for this
https://relishapp.com/rspec/rspec-expectations/docs/compound-expectations
So I'll re-write your expectation to something like this:
expect { handler.call }.
to raise_error(FailedPaymentError).
and change { Subscription.count }.by(1)
It can be done like this
expect{ handler.call }.to raise_error FailedPaymentError
Should work.
If you don't want to raise error at all then you can remove this line, and return a valid response instead
allow(charger).to receive(:charge!).and_raise(FailedPaymentError)
More info - How to test exception raising in Rails/RSpec?
Official RSpec docs
https://relishapp.com/rspec/rspec-expectations/v/2-0/docs/matchers/expect-error
Hi I am new to rspec (and unit testing in general) and want to test the following method:
class HelloController < ApplicationController
def hello_world
user = User.find(4)
#subscription = 10.00
render :text => "Done."
end
end
I am trying to use Rspec like so:
Describe HelloController, :type => :controller do
describe "get hello_world" do
it "should render the text 'done'" do
get :hello_world
expect(response.body).to include_text("Done.")
end
end
end
I would like to simply test that the method works properly and renders the test "done". I get the following error when I run the test:
Failure/Error: user = User.find(4)
ActiveRecord::RecordNotFound:
Couldn't find User with 'id'=4
But how do I properly create a user with that id before executing it? I have tried the following based on other tutorials and questions but it doesn't work:
describe "get hello_world" do
let(:user) {User.create(id: 4)}
it "should render the text 'done'" do
get :hello_world
expect(response.body).to include_text("Done.")
end
end
Thank you in advance.
Hey so really no action (e.g. def hello_world) should rely on a specific id. So a simple alternative could be to use user = User.last or to find the user by name user = User.find_by(name: "name"). Then in the test you would create any user if you using User.last in the action.
describe "get hello_world" do
let(:user) {User.create!}
it "should render the text 'done'" do
get :hello_world
expect(response.body).to include_text("Done.")
end
end
or if you are searching by name you can make a user with that name;
describe "get hello_world" do
let(:user) {User.create!(name: "name")}
it "should render the text 'done'" do
get :hello_world
expect(response.body).to include_text("Done.")
end
end
Hope this helps, questions welcome.
Do you really mean to use 'user = User.find(4)'? If you really meant to do that, you should stub the User's find method and return a user object.
it "should render the text 'done'" do
u = User.new #a new user, your test database is empty, so there's no user with id 4
User.stub(find: u) #stub the User's find method to return that new user
get :hello_world
expect(response.body).to include_text("Done.")
end
Another option is to send the user_id via params
it "should render the text 'done'" do
u = User.create(.... your user params)
get :hello_world, user_id: u.id
expect(response.body).to include_text("Done.")
end
and
def hello_world
user = User.find(params[:user_id])
#subscription = 10.00
render :text => "Done."
end
Anyway, I don't think you should be doing that, a hardcoded id is a bad sign. If you need to control users registrations and logins you can use something like Devise, and you may need to create an login a user before the spec.
i am using rails and want to write a test for password reset in Rspec. i am quite new to testing.
this is what i have done so far:
require 'rails_helper'
describe UsersController, type: :controller do
describe 'post #reset_password' do
let(:user) { create(:user) }
context "reset password" do
def do_request
patch :update_password
end
before { do_request }
it { expect(ActionMailer::Base.deliveries.count(1) }
end
end
end
every time i run this it gives ma an syntax error in
"it { expect(ActionMailer::Base.deliveries.count(1) } ".
i want to check whether the email successfully sent of not and if the user have key in the email.
Thanks!
1) you miss ) at last here so got syntax error
it { expect(ActionMailer::Base.deliveries.count(1) }
to
it { expect(ActionMailer::Base.deliveries.count(1)) }
2)
If you want to check total deliveries. you can try
it 'should send an email' do
ActionMailer::Base.deliveries.count.should == 1
end
also check sender
it 'renders the sender email' do
ActionMailer::Base.deliveries.first.from.should == ['notifications#domain.com']
end
Also check subject line
it 'should set the subject to the correct subject' do
ActionMailer::Base.deliveries.first.subject.should == 'Here Is Your Story!'
end
The problems you're having will most likely be fixed by writing better tests.
Here's generally how you would write tests for something like this.
Lets suppose in your routes file you have a post route that looks something like this
# config/routes.rb
post "/user/:id/reset_password", to: "users#reset_password"
And your User controller looks something like this
# app/controllers/users_controller.rb
class UsersController
...
def reset_password
user = User.find(params[:id])
user.reset_password!
SomeMailClass.email_reset_instructions(user)
end
end
and your User.rb model looks something like this
# app/models/user.rb
class User < ActiveRecord::Base
def reset_password!
update!(password: nil) # or whatever way you want/need to reset the password
end
end
and you have some type of mailing class to send your email
# app/models/some_mail_class.rb
class SomeMailClass
def self.email_reset_instructions(user)
# do something to send email...
end
end
The way you would go about testing this in the controller would be
# spec/controllers/users_controller_spec.rb
require 'rails_helper'
describe UsersController, type: :controller do
it "#reset_password" do
user_id = double(:user_id)
user = double(:user)
expect(User).to receive(:find).with(user_id).and_return(user)
expect(user).to receive(:reset_password!).and_return(true)
expect(SomeMailClass).to receive(:email_reset_instructions).with(user)
post :reset_password, id: user_id
end
end
But you shouldn't stop there. Because the implementation of the newly made method reset_password! and the SomeMailClass has yet to be tested. So you would write model/unit tests like this for them
# spec/models/user_spec.rb
require "rails_helper"
describe User do
it ".reset_password!" do
user = User.create(password: "foo")
expect(user.password).to eq "foo"
user.reset_password!
expect(user.password).to eq nil
end
end
Then you might install vcr and factory_girl gems and use them like so to test your mailer
# spec/models/some_mail_class_spec.rb
require "rails_helper"
describe SomeMailClass do
VCR.use_cassette "email_reset_instructions" do |cassette|
it ".email_reset_instructions" do
user = FactoryGirl.create(:user)
SomeMailClass.email_reset_instructions(user)
# you can write some expectations on the cassette obj to test.
# or you can write whatever expectations you need/desire
end
end
end
And in the end if there was something happening on the front end that a user would click that made this post request you would write a feature test for it as well.
Hope this helps!
I have the following Rspec code which I think can be DRY'ed up quite a bit, specifically the check_creation_email_sent and check_rename_email_sent methods, I just don't know how to do it.
I tried doing it with shared_examples and it_behaves_like but got the error undefined method it_behaves_like... so I'm hoping someone can shed some light for me.
require 'spec_helper'
describe CreateTopicFolder do
let(:ids) { [123,456] }
def clear_bucket(topic_id)
subject.find_folder(topic_id).each { |obj| obj.delete } unless subject.find_folder(topic_id).nil?
end
def check_folder_and_object(topic_id, permalink, old_permalink=nil)
folder = subject.find_folder(topic_id)
folder.should_not be_empty
folder.size.should == 1
object = folder.first
object.key.should include(permalink)
object.key.should_not include(old_permalink) unless old_permalink.nil?
end
def check_creation_email_sent(email, permalink)
ActionMailer::Base.deliveries.should_not be_empty
email.subject.should match("Folder for '#{permalink}' created")
email.body.should include("(You can reply to this e-mail to contact Engineering if something doesn't look right)")
end
def check_rename_email_sent(email, permalink, old_permalink)
ActionMailer::Base.deliveries.should_not be_empty
email.subject.should match("Folder for '#{old_permalink}' renamed to '#{permalink}'")
email.body.should include("(You can reply to this e-mail to contact Engineering if something doesn't look right)")
end
before(:all) do
ids.each { |folder| clear_bucket(folder) }
permalink = "123-How-to-Have-Fun"
subject.create_or_rename_folder(permalink)
end
it 'should find an existing folder in the S3 bucket if it exists' do
subject.find_folder(123).should_not be_empty
end
it 'should create a new folder with the topic permalink as name if none already exists' do
subject.find_folder(456).should be_nil
permalink = "456-How-to-Not-Have-Any-Fun"
subject.create_or_rename_folder(permalink)
email = subject.instance_variable_get(:#email)
check_creation_email_sent(email, permalink)
check_folder_and_object(456, permalink)
end
it 'should rename an existing folder with new permalink' do
subject.find_folder(456).should_not be_empty
permalink = "456-How-to-Have-Fun-Again"
old_permalink = subject.instance_variable_get(:#old_permalink)
subject.create_or_rename_folder(permalink)
email = subject.instance_variable_get(:#email)
check_rename_email_sent(email, permalink, old_permalink)
check_folder_and_object(456, permalink, old_permalink)
end
end
Thanks in advance for any help!
You're probably looking for shared_examples_for("a thing") and it_should_behave_like("a thing").
I'm trying to test some mailers with rspec but deliveries are always empty. Here is my rspec test:
require "spec_helper"
describe "AccountMailer", :type => :helper do
before(:each) do
ActionMailer::Base.delivery_method = :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = []
end
it "should send welcome email to account email" do
account = FactoryGirl.create :account
account.send_welcome_email
ActionMailer::Base.deliveries.empty?.should be_false
ActionMailer::Base.deliveries.last.to.should == account.email
end
end
It fails with:
1) AccountMailer should send welcome email to account email
Failure/Error: ActionMailer::Base.deliveries.empty?.should be_false
expected true to be false
My send_welcome_email function looks like this ( that's my model ):
def send_welcome_email
AccountMailer.welcome self
end
And my mailer:
class AccountMailer < ActionMailer::Base
default from: APP_CONFIG['email']['from']
def welcome data
if data.locale == 'es'
template = 'welcome-es'
else
template = 'welcome-en'
end
mail(:to => data.email, :subject => I18n.t('welcome_email_subject'), :template_name => template)
end
end
Any ideas? I'm not sure about how to proceed.
Have you tested that it's working when you're actually running the app? Perhaps your test is correct to be failing.
I noticed that you're never calling deliver when you create the mail, so I suspect that the test is failing because email is, in fact, not getting sent. I would expect your send_welcome_email method to look more like
def send_welcome_email
AccountMailer.welcome(self).deliver
end