+ All Categories
Home > Software > Effectively Testing Services on Rails - Railsconf 2014

Effectively Testing Services on Rails - Railsconf 2014

Date post: 06-May-2015
Category:
Upload: nealkemp
View: 514 times
Download: 2 times
Share this document with a friend
Description:
Testing services with Ruby on Rails for Railsconf 2014. Test SOA or external services with ease.
Popular Tags:
76
Effectively Testing Services Neal Kemp
Transcript
Page 1: Effectively Testing Services on Rails - Railsconf 2014

Effectively

Testing

ServicesNeal Kemp

Page 2: Effectively Testing Services on Rails - Railsconf 2014

$ whoami

Iowa native

Now: Californian

Software Developer

Independent Consultant

Page 3: Effectively Testing Services on Rails - Railsconf 2014

What I Do

Ruby / Rails

Javascript / Angular

HTML, CSS, etc

Page 4: Effectively Testing Services on Rails - Railsconf 2014

what, why & howof testing services

Page 5: Effectively Testing Services on Rails - Railsconf 2014

NOT Building testable services

Page 6: Effectively Testing Services on Rails - Railsconf 2014

NOT Test-driven development

(necessarily)

Page 7: Effectively Testing Services on Rails - Railsconf 2014

… and because I don’t want @dhh to rage

Page 8: Effectively Testing Services on Rails - Railsconf 2014

what

Page 9: Effectively Testing Services on Rails - Railsconf 2014

What is a service?

Internal “SOA”

Page 10: Effectively Testing Services on Rails - Railsconf 2014

Any time you make an HTTP

request to an endpoint in

another repository

Page 11: Effectively Testing Services on Rails - Railsconf 2014

why

Page 12: Effectively Testing Services on Rails - Railsconf 2014

Why are services important?

Build faster

Makes scaling easier

Use them on virtually every application

Increasingly prevalent

Page 13: Effectively Testing Services on Rails - Railsconf 2014

Services are critical to

modern Rails

development

Page 14: Effectively Testing Services on Rails - Railsconf 2014

Why is testing services

important?You (should) test everything else

Services compose crucial features

You may encounter problems…

Page 15: Effectively Testing Services on Rails - Railsconf 2014

Internal API

Sometimes null responses

Inconsistencies

Catastrophe

Page 16: Effectively Testing Services on Rails - Railsconf 2014

Okay? But what about external APIs?

Page 17: Effectively Testing Services on Rails - Railsconf 2014
Page 18: Effectively Testing Services on Rails - Railsconf 2014

{"id": 24}

{"code": "ANA"}

Page 19: Effectively Testing Services on Rails - Railsconf 2014

"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"

}

Page 20: Effectively Testing Services on Rails - Railsconf 2014

No versioning!

Page 21: Effectively Testing Services on Rails - Railsconf 2014
Page 22: Effectively Testing Services on Rails - Railsconf 2014

Snapchat Client

Haphazard documentation

What are the requests?

Bizarre obfuscation

github.com/nneal/snapcat

Page 23: Effectively Testing Services on Rails - Railsconf 2014

how

Page 24: Effectively Testing Services on Rails - Railsconf 2014

What is different about

services?External network requests

You don’t own the code

Page 25: Effectively Testing Services on Rails - Railsconf 2014
Page 26: Effectively Testing Services on Rails - Railsconf 2014

On an airplane…

Failure is bad!

No network requests

Page 27: Effectively Testing Services on Rails - Railsconf 2014

Don’t interact with services from

test environment* **

Page 28: Effectively Testing Services on Rails - Railsconf 2014

* Includes “dummy” APIs

Page 29: Effectively Testing Services on Rails - Railsconf 2014

** Using pre-recorded

responses is okay

Page 30: Effectively Testing Services on Rails - Railsconf 2014

Assuming:

Rails, rspec

Page 31: Effectively Testing Services on Rails - Railsconf 2014

Time to stub!

Page 32: Effectively Testing Services on Rails - Railsconf 2014

Built-in Stubbing

Typhoeus

Faraday

Excon

Page 33: Effectively Testing Services on Rails - Railsconf 2014

Simplify.

Page 34: Effectively Testing Services on Rails - Railsconf 2014

gem 'webmock'

Page 35: Effectively Testing Services on Rails - Railsconf 2014

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

Page 36: Effectively Testing Services on Rails - Railsconf 2014
Page 37: Effectively Testing Services on Rails - Railsconf 2014

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

Page 38: Effectively Testing Services on Rails - Railsconf 2014

require 'facebook_wrapper'

config/intializers/facebook_wrapper.rb

Page 39: Effectively Testing Services on Rails - Railsconf 2014

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

Page 40: Effectively Testing Services on Rails - Railsconf 2014

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

Page 41: Effectively Testing Services on Rails - Railsconf 2014

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

Page 42: Effectively Testing Services on Rails - Railsconf 2014

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

Page 43: Effectively Testing Services on Rails - Railsconf 2014

Even Better

No network requests

Fast!

No intermittent failure

Page 44: Effectively Testing Services on Rails - Railsconf 2014

Mock-Services

AWS

FB graph mock

OmniAuth

Etc…

Page 45: Effectively Testing Services on Rails - Railsconf 2014

gem 'fb_graph-mock'

Page 46: Effectively Testing Services on Rails - Railsconf 2014

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

Page 47: Effectively Testing Services on Rails - Railsconf 2014

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

Page 48: Effectively Testing Services on Rails - Railsconf 2014

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

Page 49: Effectively Testing Services on Rails - Railsconf 2014

Even Better

Already stubbed for you

Pre-recorded responses (sometimes)

Don’t need to know API endpoints

Page 50: Effectively Testing Services on Rails - Railsconf 2014

gem 'sham_rack'

Page 51: Effectively Testing Services on Rails - Railsconf 2014

gem 'sinatra'

Page 52: Effectively Testing Services on Rails - Railsconf 2014

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

Page 53: Effectively Testing Services on Rails - Railsconf 2014

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

Page 54: Effectively Testing Services on Rails - Railsconf 2014

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

Page 55: Effectively Testing Services on Rails - Railsconf 2014

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

Page 56: Effectively Testing Services on Rails - Railsconf 2014

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

Page 57: Effectively Testing Services on Rails - Railsconf 2014

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

Page 58: Effectively Testing Services on Rails - Railsconf 2014

Even Better

Dynamic

Expressive

Readable

Page 59: Effectively Testing Services on Rails - Railsconf 2014

gem 'vcr'

Page 60: Effectively Testing Services on Rails - Railsconf 2014

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

Page 61: Effectively Testing Services on Rails - Railsconf 2014

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

Page 62: Effectively Testing Services on Rails - Railsconf 2014

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

Page 63: Effectively Testing Services on Rails - Railsconf 2014

Even Better

Record API automatically

Replay responses without network

Verify responses

Page 64: Effectively Testing Services on Rails - Railsconf 2014

Additional Build Process

Runs outside normal test mode

Rechecks cassettes for diffs

Avoids versioning issues

Page 65: Effectively Testing Services on Rails - Railsconf 2014

gem 'puffing-billy'

Page 66: Effectively Testing Services on Rails - Railsconf 2014

Puffing-Billy

Built for in-browser requests

Allowed to record and reuse (like VCR)

Page 67: Effectively Testing Services on Rails - Railsconf 2014

Be brave, venture out

of ruby

Page 68: Effectively Testing Services on Rails - Railsconf 2014

I also like…

Page 69: Effectively Testing Services on Rails - Railsconf 2014

Chrome Dev Tools

Page 70: Effectively Testing Services on Rails - Railsconf 2014

Postman

Page 71: Effectively Testing Services on Rails - Railsconf 2014

HTTPie

Page 72: Effectively Testing Services on Rails - Railsconf 2014

Charles

Page 73: Effectively Testing Services on Rails - Railsconf 2014

Additional Readingmartinfowler.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

Page 74: Effectively Testing Services on Rails - Railsconf 2014

Bringing it all together

Testing services is crucial

If in doubt, stub it out

Determine the flexibility you want

Record responses to save time

Page 75: Effectively Testing Services on Rails - Railsconf 2014

Next Up

Eliminating Inconsistent Test Failures

with Austin Putman

Page 76: Effectively Testing Services on Rails - Railsconf 2014

Thank you!

[email protected]

(I like emails)

@neal_kemp

(I tweet)


Recommended