Effectively Testing Services on Rails - Railsconf 2014
Upcoming SlideShare
Loading in...5
×
 

Effectively Testing Services on Rails - Railsconf 2014

on

  • 338 views

Testing services with Ruby on Rails for Railsconf 2014. Test SOA or external services with ease.

Testing services with Ruby on Rails for Railsconf 2014. Test SOA or external services with ease.

Statistics

Views

Total Views
338
Views on SlideShare
331
Embed Views
7

Actions

Likes
1
Downloads
2
Comments
0

2 Embeds 7

http://www.slideee.com 5
https://twitter.com 2

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • Includes: things like Stripe, or an internal API, or an iPhone app calling into an API exposed by your rails app
  • LA kings checkingchicagoblackhawks
  • Can back with yaml
  • Can back with yaml
  • Can back with yaml
  • Can back with yaml
  • Re-writing web proxyAllowed to record and reuse (like VCR)
  • Can back with yaml
  • Ubiquitous PowerfulIn-browser (so easy!)
  • Easily send requestsEasy-to-use GUI
  • Postman without the GUICould run small scripts around it
  • re-writing web proxyTest mobile as well as desktopGood for collecting a lot of responsesGood for testing things that aren’t specific page loads in ChromeGood when you don’t know what’s even being requested!

Effectively Testing Services on Rails - Railsconf 2014 Effectively Testing Services on Rails - Railsconf 2014 Presentation Transcript

  • Effectively Testing Services Neal Kemp
  • $ whoami Iowa native Now: Californian Software Developer Independent Consultant
  • What I Do Ruby / Rails Javascript / Angular HTML, CSS, etc
  • what,why&how of testing services
  • NOT Building testable services
  • NOT Test-driven development (necessarily)  
  • … and because I don’t want @dhh to rage
  • what
  • What is a service? Internal “SOA”
  • Any time you make an HTTP request to an endpoint in another repository
  • why
  • Why are services important? Build faster Makes scaling easier Use them on virtually every application Increasingly prevalent
  • Services are critical to modern Rails development
  • Why is testing services important? You (should) test everything else Services compose crucial features You may encounter problems…
  • Internal API Sometimes null responses Inconsistencies Catastrophe
  • Okay? But what about external APIs?
  • {"id": 24} {"code": "ANA"}
  • "goals":[ { "per":"1", "ta":"CGY", "et":"14:11", "st":"Wrist Shot" }, { "per":"2", "ta":"ANA", "et":"11:12", "st":"Backhand" } ] "goals": { "per":"1", "ta":"CGY", "et":"14:11", "st":"Wrist Shot" }
  • No versioning!
  • Snapchat Client Haphazard documentation What are the requests? Bizarre obfuscation github.com/nneal/snapcat
  • how
  • What is different about services? External network requests You don’t own the code
  • On an airplane… Failure is bad! No network requests
  • Don’t interact with services from test environment* **
  • * Includes “dummy” APIs
  • ** Using pre-recorded responses is okay
  • Assuming: Rails, rspec
  • Timetostub!
  • Built-in Stubbing Typhoeus Faraday Excon
  • Simplify.
  • gem 'webmock'
  • ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __FILE__) require 'rspec/autorun' require 'rspec/rails’ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration) RSpec.configure do |config| config.infer_base_class_for_anonymous_controllers = false config.order = 'random’ end WebMock.disable_net_connect! spec/spec_helper.rb
  • module FacebookWrapper def self.user_id(username) user_data(username)['id'] end def self.user_data(username) JSON.parse( open("https://graph.facebook.com/#{username}").read ) end end lib/facebook_wrapper.rb
  • require 'facebook_wrapper' config/intializers/facebook_wrapper.rb
  • require 'spec_helper' describe FacebookWrapper, '.user_link' do it 'retrieves user link' do stub_request(:get, 'https://graph.facebook.com/arjun'). to_return( status: 200, headers: {}, body: '{ "id": "7901103","first_name": "Arjun", "locale": "en_US","username": "Arjun" }' ) user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end spec/lib/facebook_wrapper_spec.rb
  • require 'spec_helper' describe FacebookWrapper, '.user_link' do it 'retrieves user link' do stub_request(:get, 'https://graph.facebook.com/arjun'). to_return( status: 200, headers: {}, body: '{ "id": "7901103","first_name": "Arjun", "locale": "en_US","username": "Arjun" }' ) user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end spec/lib/facebook_wrapper_spec.rb
  • require 'spec_helper' describe FacebookWrapper, '.user_link' do it 'retrieves user link' do stub_request(:get, 'https://graph.facebook.com/arjun'). to_return( status: 200, headers: {}, body: '{ "id": "7901103","first_name": "Arjun", "locale": "en_US","username": "Arjun" }' ) user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end spec/lib/facebook_wrapper_spec.rb
  • require 'spec_helper' describe FacebookWrapper, '.user_link' do it 'retrieves user link' do stub_request(:get, 'https://graph.facebook.com/arjun'). to_return( status: 200, headers: {}, body: '{ "id": "7901103","first_name": "Arjun", "locale": "en_US","username": "Arjun" }' ) user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end spec/lib/facebook_wrapper_spec.rb
  • Even Better No network requests Fast! No intermittent failure
  • Mock-Services AWS FB graph mock OmniAuth Etc…
  • gem 'fb_graph-mock'
  • ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __FILE__) require 'rspec/autorun' require 'rspec/rails’ require 'fb_graph/mock' Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration) RSpec.configure do |config| config.infer_base_class_for_anonymous_controllers = false config.order = 'random' config.include FbGraph::Mock end WebMock.disable_net_connect! spec/spec_helper.rb
  • describe FacebookWrapper, '.user_link' do it 'retrieves user link' do mock_graph :get, 'arjun', 'users/arjun_public' do user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end end spec/lib/facebook_wrapper_spec.rb
  • describe FacebookWrapper, '.user_link' do it 'retrieves user link' do mock_graph :get, 'arjun', 'users/arjun_public' do user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end end spec/lib/facebook_wrapper_spec.rb
  • Even Better Already stubbed for you Pre-recorded responses (sometimes) Don’t need to know API endpoints
  • gem 'sham_rack'
  • gem 'sinatra'
  • ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __FILE__) require 'rspec/autorun' require 'rspec/rails’ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration) RSpec.configure do |config| config.infer_base_class_for_anonymous_controllers = false config.order = 'random’ end WebMock.disable_net_connect! spec/spec_helper.rb
  • ShamRack.at('graph.facebook.com', 443).sinatra do get '/:username' do %Q|{ "id": "7901103", "name": "Arjun Banker", "first_name": "Arjun", "last_name": "Banker", "link": "http://www.facebook.com/#{params[:username]}", "location": { "id": 114952118516947, "name": "San Francisco, California" }, "gender": "male" }| end end spec/support/fake_facebook.rb
  • ShamRack.at('graph.facebook.com', 443).sinatra do get '/:username' do %Q|{ "id": "7901103", "name": "Arjun Banker", "first_name": "Arjun", "last_name": "Banker", "link": "http://www.facebook.com/#{params[:username]}", "location": { "id": 114952118516947, "name": "San Francisco, California" }, "gender": "male" }| end end spec/support/fake_facebook.rb
  • ShamRack.at('graph.facebook.com', 443).sinatra do get '/:username' do %Q|{ "id": "7901103", "name": "Arjun Banker", "first_name": "Arjun", "last_name": "Banker", "link": "http://www.facebook.com/#{params[:username]}", "location": { "id": 114952118516947, "name": "San Francisco, California" }, "gender": "male" }| end end spec/support/fake_facebook.rb
  • ShamRack.at('graph.facebook.com', 443).sinatra do get '/:username' do %Q|{ "id": "7901103", "name": "Arjun Banker", "first_name": "Arjun", "last_name": "Banker", "link": "http://www.facebook.com/#{params[:username]}", "location": { "id": 114952118516947, "name": "San Francisco, California" }, "gender": "male" }| end end spec/support/fake_facebook.rb
  • describe FacebookWrapper, '.user_link' do it 'retrieves user link' do user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103’ end end spec/lib/facebook_wrapper_spec.rb
  • Even Better Dynamic Expressive Readable
  • gem 'vcr'
  • ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../../config/environment', __FILE__) require 'rspec/autorun' require 'rspec/rails’ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration) RSpec.configure do |config| config.infer_base_class_for_anonymous_controllers = false config.order = 'random’ end WebMock.disable_net_connect! VCR.configure do |c| c.cassette_library_dir = 'spec/fixtures/vcr_cassettes' c.hook_into :webmock end spec/spec_helper.rb
  • describe FacebookWrapper, '.user_link' do it 'retrieves user link' do VCR.use_cassette('fb_user_arjun') do user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end end spec/lib/facebook_wrapper_spec.rb
  • describe FacebookWrapper, '.user_link' do it 'retrieves user link' do VCR.use_cassette('fb_user_arjun') do user_id = FacebookWrapper.user_id('arjun') expect(user_id).to eq '7901103' end end end spec/lib/facebook_wrapper_spec.rb
  • Even Better Record API automatically Replay responses without network Verify responses
  • Additional Build Process Runs outside normal test mode Rechecks cassettes for diffs Avoids versioning issues
  • gem 'puffing-billy'
  • Puffing-Billy Built for in-browser requests Allowed to record and reuse (like VCR)
  • Be brave, venture out of ruby
  • I also like…
  • Chrome Dev Tools
  • Postman
  • HTTPie
  • Charles
  • Additional Reading martinfowler.com/bliki/IntegrationContractTest.html robots.thoughtbot.com/how-to-stub-external-services-in-tests joblivious.wordpress.com/2009/02/20/handling-intermittence-how-to- survive-test-driven-development railscasts.com/episodes/291-testing-with-vcr
  • Bringing it all together Testing services is crucial If in doubt, stub it out Determine the flexibility you want Record responses to save time
  • Next Up Eliminating Inconsistent Test Failures with Austin Putman
  • Thank you! me@nealke.mp (I like emails) @neal_kemp (I tweet)