Rails TDD using a 3rd party mailer? - ruby-on-rails

I have a rails application that I am implementing the Twilio SMS API on, and I am a bit lost on how to test drive my design.
To start I've just made a model that is an SMS mailer that will encapsulate the twilio API and I want to be able to test it and ensure functionality without using up SMS credits or bombarding someone with test text messages.
I know how to implement the API and get it working in the code but what I need help with is actually testing the code to make sure it works and prevent breakage in the future. Could anyone provide some advice?
Thanks!

You could use my gem Twilio.rb, which is already tested, and then mock it out in your tests, e.g. with mocha
Twilio::SMS.expects(:create).with :to => '+19175551234', :from => '+12125551234', :body => 'this is easy!'
Your unit tests should never hit external services, they should always be mocked. This is follows from a general principle of unit testing that tests should not extend the class boundary of the object being tested and collaborator objects should be mocked/stubbed.
Hope this helps!
https://github.com/stevegraham/twilio-rb

My experience with testing, and with testing Twilio applications, is that you test to eliminate risk you add. You'll want to use the Twilio gem rather than rolling your own SMS code against their REST endpoint: this minimizes the amount of risk.
Wrap the API as thinly as possible in your business logic class, and test primarily the business logic. For example, in my system, SMSes get sent out of the Reminder class. The code looks something like this:
class SomeWrapperClass
if (RAILS_ENV == "testing")
##sent_smses = []
cattr_accessor :sent_smses
end
def send_a_message(to, from, message, callback_url = nil)
unless RAILS_ENV == "testing"
Twilio::SMS.message(to, from, message, callback_url)
else
##sent_smses << {:to => to, :from => from, :message => message, :callback_url => callback_url}
end
end
end
This lets me write tests focusing on my business logic, which is the stuff I'm going to screw up. For example, if I want to test some method send_reminder(client) which sends a SMS message:
test "sends reminder to client" do
SomeWrapperClass.sent_smses = []
client = clients(:send_reminder_test_case)
Reminder.send_reminder(client)
sent_message = SomeWrapperClass.sent_smses.last
assert !sent_message.blank?, "Sending a reminder should fire an SMS to client."
assert sent_message.index(client.name) >= 0, "Sending a reminder should fire an SMS with the client's name in it.
...
end
Now I'm testing the actual risk I've added, which is that I'm screwing up Reminder.send_reminder. The wrapper, on the other hand, should be close to risk-free.

Obviously separate as much of the logic as possible. By doing this you can test everything else around as much as possible and then only leave the calls to the external API needing tests.
Working with external API's can be tricky. One option is to mock the response to something that you know will work for you or to the response you would expect, this can obviously be a bit brittle though. Another option is to look at something like VCR. This will record the call to the external API once and play it back again whenever you call it again.

This guy seems to have started solving your problem: https://github.com/arfrank/Fake-Twilio-Api

You probably don't need to test twiliolib's code, but if you don't want to stub twiliolib's methods you could use the FakeWeb gem, where you define the response for specified requests.
Similar to Steve mentioned, I just stub out the request with mocha:
# In Twilio initializer
TWILIO_ACCOUNT = Twilio::RestAccount.new(TWILIO_CONFIG[:sid], TWILIO_CONFIG[:token])
# In a test helper file somewhere
class ActiveSupport::TestCase
# Call this whenever you need to test twilio requests
def stub_twilio_requests
# Stub the actual request to Twilio
TWILIO_ACCOUNT.stubs(:request).returns(Net::HTTPSuccess.new(nil, nil, nil).tap { |n|
n.stubs(:body).returns("<?xml version=\"1.0\"?>\n<TwilioResponse></TwilioResponse>\n")
})
end
end

Related

How to cleanly stub out REST client when in test environment

I have a basic model like the following
class MyModel
def initialize(attrs)
#attrs = attrs
#rest_client = Some::REST::Client.new
end
def do_a_rest_call(some_str)
#rest_client.create_thing(some_str)
end
end
For testing purposes, I don't want #rest_client to make remote calls. Instead, in a test environment, I just want to make sure that #rest_client gets called with a specific some_str when it goes through certain branches of code.
In an ideal world, I'd have an assertion similar to:
expect(my_model_instance).to.receive(do_a_rest_call).with(some_str) where in the test I will pass some_str to make sure it's the right one.
What's the best way to do this using RSpec 3.8 and Rails 5.2.2?
A solution that should work without any additional gems:
let(:rest_client_double) { instance_double(Some::REST::Client, create_thing: response) }
it 'sends get request to the RestClient' do
allow(Some::REST::Client).to receive(:new).and_return(rest_client_double)
MyModel.new(attrs).do_a_rest_call(some_str)
expect(rest_client_duble).to have_received(:create_thing).with(some_str).once
end
Basically, you are creating a double for REST client.
Then, you make sure that when calling Some::REST::Client.new the double will be used (instead of real REST client instance).
Finally, you call a method on your model and check if double received given message.

Rails - framework for sending test emails and disable DB write

I have a website with several types of customer (eg Admin, Manager, Marketing, etc.)
Using Rails, I am asked to send test emails to those people so they can preview the emails on their own email client + firewall restrictions + see if the email go into the promotion folder or not. I need to be able to send a specific set of emails to each user type (such tests are very seldom, but ultimately any of our company admins should be able to send test emails using the frontend interface).
What I'm going for, it to write one class per user type, that would register the emails that this user is likely to receive at some point, and use (for example FactoryGirl) in build-only mode (no Writes to DB !!) to build the models needed to send the emails using deliver_now (so I avoid serialization/deserialization issues). I was hoping to be able to run this system in my real production environment (so that I can use my REAL email reputation, signatures, etc.)
Is there an easy way to disable DB writes (so I make sure all my example models are destroyed after their use to send an email ?) ? An easy option would be to boot up the server using readonly database credentials but maybe there is something safe that would avoid too much trouble.
Here is how my code looks like
module Testing
module Emails
class UserTypeAdmin < Base
attr_accessor, :new_user, :admin
register_email :created_new_user, type: :user_management do
UserManagementMailer.user_created(new_user, creator: admin)
end
def prepare_models
self.admin = FactoryGirl.build(:admin)
self.new_user = FactoryGirl.build(:user)
end
end
end
end
module Testing
module Emails
class Base
class < self
# my logic to register emails, definitions of #register_email, etc.
end
def initialize(tester_emails, ccs = [])
#tester_emails = tester_emails
#ccs = ccs
prepare_models
end
def send_email(email_name)
email = instance_eval(registered_emails(email_name))
email.to = #tester_emails
email.cc = #ccs
email.deliver_now
end
My FactoryGirls factories are quite messy and although I am using the :build methods, some factories were written using associations with the :create strategy so just to make sure, I'd like to lock the DB writes so I can easily prevent bad noise on my Database (I am using Mongoid so I don't have an easy transaction mechanism to cancel all my writes)
So one very simple solution is to write a spec that checks nothing is written to the DB. Using this I was able to debug a few cases where one model was persisted.
require 'rails_helper'
describe Testing::Email::UserTypeAdmin do
let(:tos) { ['admin#example.com'] }
let(:ccs) { ['adminmjg#example.com'] }
let(:tested_models) {[
User, Admin,
Conversation, Message, # etc.
]}
subject do
described_class.new(tos, ccs)
end
context 'testing all emails' do
it 'does nothing with the DB' do
subject.send_all_emails
aggregate_failures 'no persistence' do
tested_models.each do |model|
expect(model.count).to eq(0), "#{model.name} was persisted"
end
end
end
end
end
I'm still on the lookout for better solutions :-)

Rails - Logging events with tags or types and getting an email alert - best practices?

Wondering if someone could offer some best practice on logging techniques they use and maybe tagging certain events using airbrake or newrelic or loggly, or something of that nature?
For example, lets say I have events that should never hit my controller in theory, because they are protected on the front end - like a regular user being able to manage an admin. I prevent these controls from being outputted on the front end using ruby if statements. So if my controller gets hit with a request like this, then I know that either that code isn't working right, or someone is doing some hacking with the request being sent.
Either way I want to know. I just found myself writing:
#TODO: Log this. Either the front end is broken or the user is sending hacked requests
Wondering if anyone can offer some insight as to how they handle it, maybe with a third party logging tool, maybe with some sort of tag that I could set up email alerts with said tool?
Morgan from Airbrake here. We don't have tagging for Airbrake Exceptions.
One way to solve this with our service is to send a custom error to Airbrake from your controller.
The error will trigger an Airbrake notification email and you will be notified.
For Example:
# find me in app/controllers/some_controller.rb
# custom error
class ControllerAccessError < StandardError
end
def action
...
msg = 'front end is broken or the user is sending hacked request'
e = ControllerAccessError.new(msg)
Airbrake.notify_or_ignore(e)
end
Here is some more info on manually sending errors to airbrake with ruby:
https://github.com/airbrake/airbrake/wiki/Using-Airbrake-with-plain-Ruby
Jason from Loggly here. You can also setup a new Logger and then log anything you like. This uses pure Ruby code without any proprietary libraries. For example:
logger.warn("Regular user should not have access")
Even better, you can use JSON fields to make for easy reporting and filtering. I'm not a Ruby programmer, but I think it'd look something like this?
logger.warn({:username => username, :type => "Access Violation", :message => "Regular user should not have access"}.to_json);
In Loggly, you can setup alerts to be sent over email whenever you get a message matching this search
json.type:"Access Violation"

Testing this rails controller - While making API Calls?

I am no stranger to testing. I pride my self on have 97% - 100% test coverage. In fact anything below 95% is poor (but thats off topic). I have the following rails controller:
module Api
module Internal
class TwitterController < Api::V1::BaseController
# Returns you 5 tweets with tons of information.
#
# We want 5 specific tweets with the hash of #AisisWriter.
def fetch_aisis_writer_tweets
tweet_array = [];
tweet = twitter_client.search("#AisisWriter").take(5).each do |tweet|
tweet_array.push(tweet)
end
render json: tweet_array
end
private
# Create a twitter client connection.
def twitter_client
client = Twitter::REST::Client.new do |config|
config.consumer_key = ENV['CONSUMER_KEY']
config.consumer_secret = ENV['CONSUMER_SECRET_KEY']
config.access_token = ENV['ACCESS_TOKEN']
config.access_token_secret = ENV['ACCESS_TOKEN_SECRET']
end
end
end
end
end
It's extremely basic to see whats going on. Now I could write the rspec tests to say call this action, I expect json['bla']['text'] to eql bla.
But there is a couple issues. In order to effectively test this you need twitter API credentials. Thats coupling my code with another service that I am hoping is up and running.
In fact my controller is essentially coupled to twitter.
So - My question is, with out having to mock a web service or a api call (I have seen some blog posts out there on this, and for this piece of code, I feel they are over kill) - How would you test this?
Some people have suggested VCR. Any thoughts on testing API calls like this?
I've found VCR to be a great tool for tests like this - where you don't need a ton of control over what the external service returns, because you don't have a lot of cases to test. You just want to eliminate test flakiness based on whether or not the service is up, and you want to make sure that you get exactly the same fake response every time. I wouldn't say VCR is overkill at all, it's very simple to use - you just wrap your test in a use_cassette block, run your test, and VCR records the actual response from the service and uses it as the mocked response from then on.
I will say that the "cassettes" that VCR uses to store the mocked responses are fairly complex YAML, and they're not super readable/easy to edit. If you want to be able to easily manipulate the data that's returned so that you can test several code paths, and easily read it so that your mocked data can serve as documentation of the code, I'd look into something more like HttpMock.
One other option, of course, would be to just stub out the private method that calls the external service and have it return your mock data directly. Usually I'd avoid that, so that you can refactor your private method and still be covered, but it might be an option in some cases where the private method is dead simple and unlikely to change, and stubbing it out makes for significantly cleaner tests.

Is stubbing/mocking necessary with 3rd party api

I have an action in my controller that connects to a third party api and does some work. If the code succeeds then my user is saved. If the third party api fails, then the user is not saved.
def create
if ThirdPartyApp.connect.eql? true
User.create(params[:user])
else
redirect home_path
end
end
I want to test the User.create code without having to worry about the third party app code. I'm aware of stubbing and mocking and understand the difference in that one tests instance methods and the other class methods. I'm unsure how to use this though in my rspec test as certain conditions can't be met in the test that are required for the third party app to work. Any help on how I can write this test with stubbing, mocking, or some other method would be great.
There are a few solutions here:
First, you could use a tool like VCR (Ryan Bates has a great screencast on this!) That way, you run the test the first time with the response from the API being recorded so that from the second time onwards it just 'replays' the 'recording' to use their terms.
Using stubbing/mocking on a request that is so dependant on an API response can be hard. If it were me (and I am guessing at how you are testing this controller action)
it "creates user" do
ThirdPartyApp.any_instance.stub(:connect) { true }
# fill in form or submit params as required
User.count.should == 1 # or some other assertion
end
Then you can test the fail being:
it "fails from the api" do
ThirdPartyApp.any_instance.stub(:connect) { false }
# fill in form or submit params as required
current_path.should == '/whatever' # or some other assertion
end
Others may argue a different solution, and if I had the choice in this circumstance I would go with VCR.

Resources