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 prevale...
Services are critical to
modern Rails development
Why is testing services important?
You (should) test everything else
Services compose crucial features
You may encounter p...
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",	
"s...
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'	
requ...
module FacebookWrapper	
def self.user_id(username)	
user_data(username)['id']	
end	
	
def self.user_data(username)	
JSON.p...
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...
require 'spec_helper'	
	
describe FacebookWrapper, '.user_link' do	
it 'retrieves user link' do	
stub_request(:get, 'https...
require 'spec_helper'	
	
describe FacebookWrapper, '.user_link' do	
it 'retrieves user link' do	
stub_request(:get, 'https...
require 'spec_helper'	
	
describe FacebookWrapper, '.user_link' do	
it 'retrieves user link' do	
stub_request(:get, 'https...
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'	
requ...
describe FacebookWrapper, '.user_link' do	
it 'retrieves user link' do	
mock_graph :get, 'arjun', 'users/arjun_public' do	...
describe FacebookWrapper, '.user_link' do	
it 'retrieves user link' do	
mock_graph :get, 'arjun', 'users/arjun_public' do	...
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'	
requ...
ShamRack.at('graph.facebook.com', 443).sinatra do	
get '/:username' do	
%Q|{	
"id": "7901103",	
"name": "Arjun Banker",	
"...
ShamRack.at('graph.facebook.com', 443).sinatra do	
get '/:username' do	
%Q|{	
"id": "7901103",	
"name": "Arjun Banker",	
"...
ShamRack.at('graph.facebook.com', 443).sinatra do	
get '/:username' do	
%Q|{	
"id": "7901103",	
"name": "Arjun Banker",	
"...
ShamRack.at('graph.facebook.com', 443).sinatra do	
get '/:username' do	
%Q|{	
"id": "7901103",	
"name": "Arjun Banker",	
"...
describe FacebookWrapper, '.user_link' do	
it 'retrieves user link' do	
user_id = FacebookWrapper.user_id('arjun')	
	
expe...
Even Better
Dynamic
Expressive
Readable
gem 'vcr'
ENV['RAILS_ENV'] ||= 'test'	
require File.expand_path('../../config/environment', __FILE__)	
require 'rspec/autorun'	
requ...
describe FacebookWrapper, '.user_link' do	
it 'retrieves user link' do	
VCR.use_cassette('fb_user_arjun') do	
user_id = Fa...
describe FacebookWrapper, '.user_link' do	
it 'retrieves user link' do	
VCR.use_cassette('fb_user_arjun') do	
user_id = Fa...
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...
Bringing it all together
Testing services is crucial
If in doubt, stub it out
Determine the flexibility you want
Record res...
Next Up
Eliminating Inconsistent Test Failures
with Austin Putman
Thank you!
me@nealke.mp
(I like emails)
@neal_kemp
(I tweet)
Effectively Testing Services on Rails - Railsconf 2014
Effectively Testing Services on Rails - Railsconf 2014
Effectively Testing Services on Rails - Railsconf 2014
Effectively Testing Services on Rails - Railsconf 2014
Upcoming SlideShare
Loading in...5
×

Effectively Testing Services on Rails - Railsconf 2014

736
-1

Published on

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

Published in: Software, Technology
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
736
On Slideshare
0
From Embeds
0
Number of Embeds
6
Actions
Shares
0
Downloads
7
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide
  • 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

    1. 1. Effectively Testing Services Neal Kemp
    2. 2. $ whoami Iowa native Now: Californian Software Developer Independent Consultant
    3. 3. What I Do Ruby / Rails Javascript / Angular HTML, CSS, etc
    4. 4. what,why&how of testing services
    5. 5. NOT Building testable services
    6. 6. NOT Test-driven development (necessarily)  
    7. 7. … and because I don’t want @dhh to rage
    8. 8. what
    9. 9. What is a service? Internal “SOA”
    10. 10. Any time you make an HTTP request to an endpoint in another repository
    11. 11. why
    12. 12. Why are services important? Build faster Makes scaling easier Use them on virtually every application Increasingly prevalent
    13. 13. Services are critical to modern Rails development
    14. 14. Why is testing services important? You (should) test everything else Services compose crucial features You may encounter problems…
    15. 15. Internal API Sometimes null responses Inconsistencies Catastrophe
    16. 16. Okay? But what about external APIs?
    17. 17. {"id": 24} {"code": "ANA"}
    18. 18. "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" }
    19. 19. No versioning!
    20. 20. Snapchat Client Haphazard documentation What are the requests? Bizarre obfuscation github.com/nneal/snapcat
    21. 21. how
    22. 22. What is different about services? External network requests You don’t own the code
    23. 23. On an airplane… Failure is bad! No network requests
    24. 24. Don’t interact with services from test environment* **
    25. 25. * Includes “dummy” APIs
    26. 26. ** Using pre-recorded responses is okay
    27. 27. Assuming: Rails, rspec
    28. 28. Timetostub!
    29. 29. Built-in Stubbing Typhoeus Faraday Excon
    30. 30. Simplify.
    31. 31. gem 'webmock'
    32. 32. 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
    33. 33. 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
    34. 34. require 'facebook_wrapper' config/intializers/facebook_wrapper.rb
    35. 35. 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
    36. 36. 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
    37. 37. 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
    38. 38. 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
    39. 39. Even Better No network requests Fast! No intermittent failure
    40. 40. Mock-Services AWS FB graph mock OmniAuth Etc…
    41. 41. gem 'fb_graph-mock'
    42. 42. 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
    43. 43. 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
    44. 44. 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
    45. 45. Even Better Already stubbed for you Pre-recorded responses (sometimes) Don’t need to know API endpoints
    46. 46. gem 'sham_rack'
    47. 47. gem 'sinatra'
    48. 48. 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
    49. 49. 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
    50. 50. 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
    51. 51. 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
    52. 52. 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
    53. 53. 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
    54. 54. Even Better Dynamic Expressive Readable
    55. 55. gem 'vcr'
    56. 56. 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
    57. 57. 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
    58. 58. 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
    59. 59. Even Better Record API automatically Replay responses without network Verify responses
    60. 60. Additional Build Process Runs outside normal test mode Rechecks cassettes for diffs Avoids versioning issues
    61. 61. gem 'puffing-billy'
    62. 62. Puffing-Billy Built for in-browser requests Allowed to record and reuse (like VCR)
    63. 63. Be brave, venture out of ruby
    64. 64. I also like…
    65. 65. Chrome Dev Tools
    66. 66. Postman
    67. 67. HTTPie
    68. 68. Charles
    69. 69. 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
    70. 70. Bringing it all together Testing services is crucial If in doubt, stub it out Determine the flexibility you want Record responses to save time
    71. 71. Next Up Eliminating Inconsistent Test Failures with Austin Putman
    72. 72. Thank you! me@nealke.mp (I like emails) @neal_kemp (I tweet)
    1. A particular slide catching your eye?

      Clipping is a handy way to collect important slides you want to go back to later.

    ×