├── .travis.yml ├── lib ├── mock5 │ ├── version.rb │ └── api.rb └── mock5.rb ├── Rakefile ├── Gemfile ├── .gitignore ├── mock5.gemspec ├── LICENSE.txt ├── spec ├── mock5_api_spec.rb └── mock5_spec.rb └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 1.9 3 | - 2.0 4 | - 2.1 5 | - 2.2 6 | -------------------------------------------------------------------------------- /lib/mock5/version.rb: -------------------------------------------------------------------------------- 1 | module Mock5 2 | VERSION = "1.0.8".freeze 3 | end 4 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new 5 | 6 | task default: :spec 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Specify your gem's dependencies in mock5.gemspec 4 | gemspec 5 | 6 | gem "bundler", "~> 1.6" 7 | gem "rspec", "~> 3.1" 8 | gem "rake", "~> 10.3" 9 | gem "pry" 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | .ruby-* 19 | -------------------------------------------------------------------------------- /mock5.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require File.expand_path("../lib/mock5/version", __FILE__) 3 | 4 | Gem::Specification.new do |spec| 5 | spec.name = "mock5" 6 | spec.version = Mock5::VERSION 7 | spec.authors = ["Pavel Pravosud"] 8 | spec.email = ["pavel@pravosud.com"] 9 | spec.summary = "Mock APIs using Sinatra" 10 | spec.description = "Create and manage API mocks with Sinatra" 11 | spec.homepage = "https://github.com/rwz/mock5" 12 | spec.license = "MIT" 13 | spec.files = Dir["LICENSE.text", "README.md", "lib/**/**"] 14 | spec.require_path = "lib" 15 | spec.required_ruby_version = ">= 1.9.3" 16 | 17 | spec.add_dependency "webmock", "~> 1.15" 18 | spec.add_dependency "sinatra", "~> 1.4" 19 | end 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Pavel Pravosud 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /spec/mock5_api_spec.rb: -------------------------------------------------------------------------------- 1 | require "mock5/api" 2 | require "rack/mock" 3 | 4 | describe Mock5::Api do 5 | describe "#endpoint" do 6 | it "matches all by default" do 7 | expect(subject.endpoint).to eq(/.*/) 8 | end 9 | 10 | it "can be specified as a regex" do 11 | api = described_class.new(/foo/) 12 | expect(api.endpoint).to eq(/foo/) 13 | end 14 | 15 | it "can be specified as a valid url without path" do 16 | api = described_class.new("http://example.com") 17 | expect(api.endpoint).to eq(%r(\Ahttp://example\.com/.*\z)) 18 | end 19 | 20 | it "can not be specified as a valid url with path" do 21 | expect{ described_class.new("http://example.com/foo") } 22 | .to raise_error(ArgumentError, "Endpoint URL should not include path") 23 | end 24 | 25 | it "can not be specified as an invalid url string" do 26 | expect{ described_class.new("foo") } 27 | .to raise_error(ArgumentError, "Endpoint should be a valid URL") 28 | end 29 | 30 | it "can not be specified by anything else" do 31 | [false, :foo, 123].each do |invalid_endpoint| 32 | expect{ described_class.new(invalid_endpoint) } 33 | .to raise_error(ArgumentError, "Endpoint should be string or regexp") 34 | end 35 | end 36 | end 37 | 38 | describe "#app" do 39 | it "is a Class" do 40 | expect(subject.app).to be_kind_of(Class) 41 | end 42 | 43 | it "is a Sinatra Rack app" do 44 | expect(subject.app.superclass).to eq(Sinatra::Base) 45 | end 46 | 47 | describe "configuration" do 48 | subject do 49 | described_class.new do 50 | get "/hello/:what" do |what| 51 | "Hello, #{what.capitalize}" 52 | end 53 | end 54 | end 55 | 56 | let(:server){ Rack::Server.new(app: subject.app) } 57 | let(:mock_request){ Rack::MockRequest.new(server.app) } 58 | 59 | it "can be configures by a block" do 60 | response = mock_request.get("/hello/world") 61 | expect(response.body.to_s).to eq("Hello, World") 62 | end 63 | end 64 | end 65 | 66 | describe "#request_stub" do 67 | it "returns a request stub" do 68 | expect(subject.request_stub).to be_kind_of(WebMock::RequestStub) 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/mock5/api.rb: -------------------------------------------------------------------------------- 1 | require "uri" 2 | require "sinatra" 3 | require "webmock" 4 | 5 | module Mock5 6 | # A class representing an API mock 7 | class Api 8 | 9 | # @return [Sinatra::Base] a Sinatra app mocking the API 10 | attr_reader :app 11 | 12 | # @return [Regexp] a regexp to match the API request urls 13 | attr_reader :endpoint 14 | 15 | # Returns an instance of +Mock5::Api+ 16 | # 17 | # @example 18 | # my_mock_api = Mock5::Api.new("http://example.com") do 19 | # get "posts" do 20 | # [ 21 | # {id: 1, body: "a posy body"}, 22 | # {id: 2, body: "another post body"} 23 | # ].to_json 24 | # end 25 | # 26 | # post "posts" do 27 | # halt 201, "The post was created successfully" 28 | # end 29 | # end 30 | # 31 | # @param endpoint [String, Regexp] a url of the API service to 32 | # endpoint to mock. Can only contain schema and hostname, path 33 | # should be empty. 34 | # 35 | # @yield a block passed to Sinatra to initialize an app 36 | # 37 | # @return [Mock5::Api] 38 | def initialize(endpoint=nil, &block) 39 | @app = Sinatra.new(&block) 40 | @endpoint = normalize_endpoint(endpoint) 41 | end 42 | 43 | # Returns webmock request stub built with Sinatra app and enpoint url 44 | # 45 | # @return [WebMock::RequestStub] 46 | def request_stub 47 | @request_stub ||= WebMock::RequestStub.new(:any, endpoint).tap{ |s| s.to_rack(app) } 48 | end 49 | 50 | private 51 | 52 | def normalize_endpoint(endpoint) 53 | case endpoint 54 | when nil 55 | /.*/ 56 | when String 57 | normalize_string_endpoint(endpoint) 58 | when Regexp 59 | endpoint 60 | else 61 | raise ArgumentError, "Endpoint should be string or regexp" 62 | end 63 | end 64 | 65 | def normalize_string_endpoint(endpoint) 66 | uri = URI.parse(endpoint) 67 | 68 | if uri.scheme !~ /\Ahttps?/ 69 | raise ArgumentError, "Endpoint should be a valid URL" 70 | elsif uri.path != ?/ && !uri.path.empty? 71 | raise ArgumentError, "Endpoint URL should not include path" 72 | end 73 | 74 | uri.path = "" 75 | endpoint = Regexp.escape(uri.to_s) 76 | 77 | Regexp.new("\\A#{endpoint}\/#{app_paths_regex}\\z") 78 | end 79 | 80 | def app_paths_regex 81 | regexes = app.routes.values.flatten.select{ |v| Regexp === v } 82 | paths = regexes.map{ |regex| regex.source[3..-3] } 83 | 84 | return ".*" if paths.empty? 85 | 86 | paths = paths.one?? paths.first : %{(?:#{paths.join("|")})} 87 | 88 | "#{paths}(?:\\?.*)?" 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/mock5.rb: -------------------------------------------------------------------------------- 1 | require "set" 2 | 3 | # The main module of the gem, exposing all API management methods. 4 | # Can be included into class. 5 | module Mock5 6 | extend self 7 | 8 | autoload :VERSION, "mock5/version" 9 | autoload :Api, "mock5/api" 10 | 11 | # Returns a set of currently mounted APIs 12 | # 13 | # @return [Set] a list of currently mounted APIs 14 | def mounted_apis 15 | @_mounted_apis ||= Set.new 16 | end 17 | 18 | # Generates a new API 19 | # 20 | # @example 21 | # my_mock_api = Mock5.mock("http://example.com") do 22 | # get "posts" do 23 | # [ 24 | # {id: 1, body: "a post body"}, 25 | # {id: 2, body: "another post body"} 26 | # ].to_json 27 | # end 28 | # 29 | # post "posts" do 30 | # halt 201, "The post was created successfully" 31 | # end 32 | # end 33 | # 34 | # @param endpoint [String] a url of the API service endpoint to mock. 35 | # Should only include hostname and schema. 36 | # 37 | # @yield a block to define behavior using Sinatra API 38 | # 39 | # @return [Mock5::Api] a mountable API object 40 | def mock(endpoint=nil, &block) 41 | Api.new(endpoint, &block) 42 | end 43 | 44 | # Mounts given list of APIs. Returns a list of APIs that were actually 45 | # mounted. The APIs that were already mounted when the method is called 46 | # are not included in the return value. 47 | # 48 | # @param apis [Enum #to_set] a list of APIs to mount 49 | # 50 | # @return [Set] a list of APIs actually mounted 51 | def mount(*apis) 52 | apis.to_set.subtract(mounted_apis).each do |api| 53 | check_api api 54 | mounted_apis.add api 55 | registry.register_request_stub api.request_stub 56 | end 57 | end 58 | 59 | # Unmount given APIs. Returns only the list of APIs that were actually 60 | # unmounted. If the API wasn't mounted when the method is called, it won't be 61 | # included in the return value. 62 | # 63 | # @param apis [Enum #to_set] a list of APIs to unmount 64 | # 65 | # @return [Set] a list of APIs actually unmounted 66 | def unmount(*apis) 67 | mounted_apis.intersection(apis).each do |api| 68 | mounted_apis.delete api 69 | if registry.request_stubs.include?(api.request_stub) 70 | registry.remove_request_stub api.request_stub 71 | end 72 | end 73 | end 74 | 75 | # Returns true if all given APIs are mounted. false otherwise. 76 | # 77 | # @param apis [Enum #to_set] a list of APIs to check 78 | # 79 | # @return [Boolean] true if all given APIs are mounted, false otherwise 80 | def mounted?(*apis) 81 | apis.to_set.subset?(mounted_apis) 82 | end 83 | 84 | # Mounts a list of given APIs, executes block and then unmounts them back. 85 | # Useful for wrapping around RSpec tests. It only unmounts APIs that were 86 | # not mounted before. Any API that was mounted before the method was 87 | # called remains mounted. 88 | # 89 | # @example 90 | # my_api = Mock5.mock("http://example.com") do 91 | # get "index.html" do 92 | # "

Hello world!

" 93 | # end 94 | # end 95 | # 96 | # another_api = Mock5.mock("http://foobar.com") do 97 | # get "hello/:what" do 98 | # "

Hello #{params["what"]}

" 99 | # end 100 | # end 101 | # 102 | # Mock5.with_mounted my_api, another_api do 103 | # Net::HTTP.get("example.com", "/index.html") # => "

Hello world!

" 104 | # Net::HTTP.get("foobar.com", "/hello/bar") # => "

Hello, bar

" 105 | # end 106 | # 107 | # @param apis [Enum #to_set] a list of APIs to mount before executing the 108 | # block 109 | # 110 | # @yield the block to execute with given APIs being mounted 111 | def with_mounted(*apis) 112 | mounted = mount(*apis) 113 | yield 114 | ensure 115 | unmount *mounted 116 | end 117 | 118 | # Unmounts all currently mounted APIs and returns them 119 | # 120 | # @return [Set] a list of unmounted APIs 121 | def unmount_all! 122 | unmount *mounted_apis 123 | end 124 | 125 | alias_method :reset!, :unmount_all! 126 | 127 | private 128 | 129 | def registry 130 | WebMock::StubRegistry.instance 131 | end 132 | 133 | def check_api(api) 134 | fail ArgumentError, "expected an instance of Mock5::Api" unless Api === api 135 | end 136 | end 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mock5 2 | [![Gem Version](https://img.shields.io/gem/v/mock5.svg)](https://rubygems.org/gems/mock5) 3 | [![Build Status](https://img.shields.io/travis/rwz/mock5.svg)](http://travis-ci.org/rwz/mock5) 4 | [![Code Climate](https://img.shields.io/codeclimate/github/rwz/mock5.svg)](https://codeclimate.com/github/rwz/mock5) 5 | [![Inline docs](http://inch-ci.org/github/rwz/mock5.svg)](http://inch-ci.org/github/rwz/mock5) 6 | 7 | Mock5 allows to mock external APIs with simple Sinatra Rack apps. 8 | 9 | ## Installation 10 | 11 | This gem could be useful for testing, and maybe development purposes. 12 | Add it to the relevant groups in your Gemfile. 13 | 14 | ```ruby 15 | gem "mock5", groups: [:test, :development] 16 | ``` 17 | 18 | and run `bundle`. 19 | 20 | ## Usage 21 | 22 | ### mock 23 | Use this method to describe API you're trying to mock. 24 | 25 | ```ruby 26 | weather_api = Mock5.mock("http://weather-api.com") do 27 | get "/weather.json" do 28 | MultiJson.dump( 29 | location: "Philadelphia, PA", 30 | temperature: "60F", 31 | description: "Sunny" 32 | ) 33 | end 34 | end 35 | ``` 36 | 37 | ### mount 38 | Use this method to enable API mocks you've defined previously. 39 | 40 | ```ruby 41 | Mock5.mount weather_api, some_other_api 42 | Net::HTTP.get("weather-api.com", "/weather.json") # => "{\"location\":... 43 | ``` 44 | 45 | ### unmount 46 | Unmounts passed APIs if thery were previously mounted 47 | 48 | ```ruby 49 | Mock5.unmount some_other_api # [, and_another_api... ] 50 | ``` 51 | 52 | ### mounted_apis 53 | This method returns a Set of all currently mounted APIs 54 | 55 | ```ruby 56 | Mock5.mounted_apis # => { weather_api } 57 | Mock5.mount another_api 58 | Mock5.mounted_apis # => { weather_api, another_api } 59 | ``` 60 | 61 | ### with_mounted 62 | Executes the block with all given APIs mounted, and then unmounts them. 63 | 64 | ```ruby 65 | Mock5.mounted_apis # => { other_api } 66 | Mock5.with_mounted weather_api, other_api do 67 | Mock5.mounted_apis # => { other_api, weather_api } 68 | run_weather_api_test_suite! 69 | end 70 | Mock5.mounted_apis # => { other_api } 71 | ``` 72 | 73 | ## Example 74 | 75 | Say you're writing a nice wrapper around remote user management REST API. 76 | You want your library to handle any unexpected situation aproppriately and 77 | show a relevant error message, or schedule a retry some time later. 78 | 79 | Obviously, you can't rely on a production API to test all these codepaths. You 80 | probably want a way to emulate all these situations locally. Enter Mock5: 81 | 82 | ```ruby 83 | # user registers successfully 84 | SuccessfulRegistration = Mock5.mock("http://example.com") do 85 | post "/users" do 86 | MultiJson.dump( 87 | first_name: "Zapp", 88 | last_name: "Brannigan", 89 | email: "zapp@planetexpress.com" 90 | ) 91 | end 92 | end 93 | 94 | # registration returns validation error 95 | UnsuccessfulRegistration = Mock5.mock("http://example.com") do 96 | post "/users" do 97 | halt 406, MultiJson.dump( 98 | first_name: ["is too lame"], 99 | email: ["is not unique"] 100 | ) 101 | end 102 | end 103 | 104 | # remote api is down for some reason 105 | RegistrationUnavailable = Mock5.mock("http://example.com") do 106 | post "/users" do 107 | halt 503, "Service Unavailable" 108 | end 109 | end 110 | 111 | # remote api times takes long time to respond 112 | RegistrationTimeout = Mock5.mock("http://example.com") do 113 | post "/users" do 114 | sleep 15 115 | end 116 | end 117 | 118 | describe MyApiWrapper do 119 | describe "successfull" do 120 | around do |example| 121 | Mock5.with_mounted(SuccessfulRegistration, &example) 122 | end 123 | 124 | it "allows user registration" do 125 | expect{ MyApiWrapper.register_user }.not_to raise_error 126 | end 127 | end 128 | 129 | describe "validation errors" do 130 | around do |example| 131 | Mock5.with_mounted(UnsuccessfulRegistration, &example) 132 | end 133 | 134 | it "raises a valiation error" do 135 | expect{ MyApiWrapper.register_user }.to raise_error(MyApiWrapper::ValidationError) 136 | end 137 | end 138 | 139 | describe "service is unavailable" do 140 | around do |example| 141 | Mock5.with_mounted(RegistrationUnavailable, &example) 142 | end 143 | 144 | it "raises a ServiceUnavailable error" do 145 | expect{ MyApiWrapper.register_user }.to raise_error(MyApiWrapper::ServiceUnavailable) 146 | end 147 | end 148 | 149 | describe "timeout" do 150 | around do |example| 151 | Mock5.with_mounted(RegistrationTimeout, &example) 152 | end 153 | 154 | it "raises timeout error" do 155 | expect{ MyApiWrapper.register_user }.to raise_error(Timeout::Error) 156 | end 157 | end 158 | end 159 | ``` 160 | 161 | ## Contributing 162 | 163 | 1. Fork it 164 | 2. Create your feature branch (`git checkout -b my-new-feature`) 165 | 3. Commit your changes (`git commit -am 'Add some feature'`) 166 | 4. Push to the branch (`git push origin my-new-feature`) 167 | 5. Create new Pull Request 168 | -------------------------------------------------------------------------------- /spec/mock5_spec.rb: -------------------------------------------------------------------------------- 1 | require "mock5" 2 | 3 | describe Mock5 do 4 | describe ".mock" do 5 | it "creates an Api" do 6 | expect(described_class::Api).to receive(:new).with(/foo/).and_yield 7 | described_class.mock /foo/ do 8 | # mock definition goes here 9 | end 10 | end 11 | 12 | it "returns an Api" do 13 | expect(described_class.mock).to be_kind_of(described_class::Api) 14 | end 15 | end 16 | 17 | describe "API mgmt" do 18 | before do 19 | described_class.instance_exec do 20 | if instance_variable_defined?(:@_mounted_apis) 21 | remove_instance_variable :@_mounted_apis 22 | end 23 | end 24 | end 25 | 26 | let(:mounted_apis){ described_class.mounted_apis } 27 | let(:mounted_apis_qty){ mounted_apis.size } 28 | let(:api){ described_class.mock } 29 | let(:another_api){ described_class.mock } 30 | 31 | describe ".mount" do 32 | it "raises ArgumentError when passed an invalid argument" do 33 | action = ->{ described_class.mount nil } 34 | message = "expected an instance of Mock5::Api" 35 | expect(&action).to raise_error(ArgumentError, message) 36 | end 37 | it "mounts an api" do 38 | described_class.mount api 39 | expect(mounted_apis).to include(api) 40 | end 41 | 42 | it "mounts an api only once" do 43 | 10.times{ described_class.mount api } 44 | expect(mounted_apis_qty).to eq(1) 45 | end 46 | 47 | it "mounts several APIs at once" do 48 | described_class.mount api, another_api 49 | expect(mounted_apis).to include(api) 50 | expect(mounted_apis).to include(another_api) 51 | end 52 | 53 | it "returns the list of mounted apis" do 54 | expect(described_class.mount(api)).to eq([api].to_set) 55 | expect(described_class.mount(api, another_api)).to eq([another_api].to_set) 56 | end 57 | end 58 | 59 | describe ".unmount" do 60 | before{ described_class.mount api } 61 | 62 | it "unmounts mounted api" do 63 | described_class.unmount api 64 | expect(mounted_apis).to be_empty 65 | end 66 | 67 | it "unmounts api only once" do 68 | 10.times{ described_class.unmount api } 69 | expect(mounted_apis).to be_empty 70 | end 71 | 72 | it "unmounts several APIs at once" do 73 | described_class.mount another_api 74 | expect(mounted_apis_qty).to eq(2) 75 | described_class.unmount api, another_api 76 | expect(mounted_apis).to be_empty 77 | end 78 | 79 | it "only unmount specified api" do 80 | described_class.mount another_api 81 | described_class.unmount api 82 | expect(mounted_apis).to include(another_api) 83 | end 84 | 85 | it "returns the list of unmounted apis" do 86 | expect(described_class.unmount(another_api)).to be_empty 87 | expect(described_class.unmount(api, another_api)).to eq([api].to_set) 88 | end 89 | end 90 | 91 | describe ".unmount_all!" do 92 | before do 93 | 3.times{ described_class.mount described_class.mock } 94 | end 95 | 96 | it "unmounts all currently mounted apis" do 97 | expect(mounted_apis_qty).to eq(3) 98 | described_class.unmount_all! 99 | expect(mounted_apis).to be_empty 100 | end 101 | 102 | it "has .reset! alias" do 103 | expect(mounted_apis_qty).to eq(3) 104 | described_class.reset! 105 | expect(mounted_apis).to be_empty 106 | end 107 | end 108 | 109 | describe ".mounted?" do 110 | before{ described_class.mount api } 111 | 112 | it "returns true if api is currently mounted" do 113 | expect(described_class.mounted?(api)).to be_truthy 114 | end 115 | 116 | it "returns false if api is currently not mounted" do 117 | expect(described_class.mounted?(another_api)).to be_falsy 118 | end 119 | 120 | it "returns true only when ALL api are mounted" do 121 | action = ->{ described_class.mount another_api } 122 | result = ->{ described_class.mounted? api, another_api } 123 | expect(&action).to change(&result).from(false).to(true) 124 | end 125 | end 126 | 127 | describe ".with_mounted" do 128 | it "temporary mounts an API" do 129 | action = -> do 130 | described_class.with_mounted api do 131 | expect(mounted_apis).to include(api) 132 | end 133 | end 134 | 135 | expect(mounted_apis).to be_empty 136 | expect(&action).not_to change(mounted_apis, :empty?) 137 | end 138 | 139 | it "doesn't unmount api, that was mounted before" do 140 | described_class.mount api 141 | 142 | described_class.with_mounted api, another_api do 143 | expect(mounted_apis).to include(another_api) 144 | end 145 | 146 | expect(mounted_apis).to include(api) 147 | expect(mounted_apis).not_to include(another_api) 148 | end 149 | end 150 | 151 | describe "stubbing" do 152 | def get(url) 153 | Net::HTTP.get(URI(url)) 154 | end 155 | 156 | def post(url, params={}) 157 | Net::HTTP.post_form(URI(url), params).body 158 | end 159 | 160 | let(:api) do 161 | described_class.mock "http://example.com" do 162 | get "/index.html" do 163 | "index.html" 164 | end 165 | 166 | post "/submit/here" do 167 | "submit" 168 | end 169 | end 170 | end 171 | 172 | let(:another_api) do 173 | described_class.mock "http://example.com" do 174 | post "/foo/:foo" do 175 | params["foo"] 176 | end 177 | 178 | get "/bar/:bar" do 179 | params["bar"] 180 | end 181 | end 182 | end 183 | 184 | context "#mount" do 185 | before{ described_class.mount api, another_api } 186 | 187 | it "stubs remote apis" do 188 | expect(get("http://example.com/index.html?foo=bar")).to eq("index.html") 189 | expect(post("http://example.com/submit/here?foo=bar")).to eq("submit") 190 | expect(post("http://example.com/foo/bar?fizz=buzz")).to eq("bar") 191 | expect(get("http://example.com/bar/foo")).to eq("foo") 192 | end 193 | end 194 | 195 | context "#with_mounted" do 196 | around do |example| 197 | described_class.with_mounted api, another_api, &example 198 | end 199 | 200 | it "stubs remote apis" do 201 | expect(get("http://example.com/index.html?foo=bar")).to eq("index.html") 202 | expect(post("http://example.com/submit/here?foo=bar")).to eq("submit") 203 | expect(post("http://example.com/foo/bar?fizz=buzz")).to eq("bar") 204 | expect(get("http://example.com/bar/foo")).to eq("foo") 205 | end 206 | end 207 | end 208 | end 209 | end 210 | --------------------------------------------------------------------------------