mock_server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +18 -0
- data/README.md +79 -0
- data/lib/mock_server/playback.rb +69 -0
- data/lib/mock_server/record.rb +40 -0
- data/lib/mock_server/spec/helpers.rb +158 -0
- data/lib/mock_server/utils.rb +65 -0
- data/lib/mock_server.rb +3 -0
- metadata +51 -0
data/LICENSE
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2011 Charles Barbier <http://github.com/unixcharles>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# MockServer
|
2
|
+
|
3
|
+
So you have spec for your models and controller for your API.
|
4
|
+
Now you're building a neat javascript application that consumer that JSON.
|
5
|
+
And you're going to test it with something like capybara?
|
6
|
+
The thing is that its going to be slow! And you're testing testing all you're application logic again!
|
7
|
+
|
8
|
+
MockServer allow you to record your interaction with the server and give you a way to match
|
9
|
+
the record request with the real request. Its like a rack [VCR](https://github.com/myronmarston/vcr) for your own API!
|
10
|
+
|
11
|
+
# Recording mode
|
12
|
+
|
13
|
+
Create mount the rack application, in rails
|
14
|
+
|
15
|
+
require 'mock_server/record'
|
16
|
+
config.middleware.use MockServer::Record, { :path => 'fixtures/records', :filename => 'uploads'
|
17
|
+
:routes => [ '/api/*/**', '/api/*/**' ] }
|
18
|
+
|
19
|
+
And you're ready to record. Run the test, build so seed by clicking around. whatever.
|
20
|
+
|
21
|
+
# Playback mode
|
22
|
+
|
23
|
+
require 'mock_server/playback'
|
24
|
+
config.middleware.use MockServer::Playback, { :path => 'fixtures/records' ] }
|
25
|
+
|
26
|
+
Recording your own playback? Noes, don't use the two Rack middleware at the same time.
|
27
|
+
|
28
|
+
# Rspec
|
29
|
+
|
30
|
+
require 'mock_server/spec/helpers'
|
31
|
+
RSpec.configure do |config|
|
32
|
+
config.include MockServer::Spec::Helpers
|
33
|
+
end
|
34
|
+
|
35
|
+
In your spec
|
36
|
+
|
37
|
+
before :each do
|
38
|
+
|
39
|
+
# Set the filename where you store the records
|
40
|
+
mock_server_use_record 'uploads'
|
41
|
+
|
42
|
+
# From now on, those path belong to MockServer.
|
43
|
+
#
|
44
|
+
# if we can't match to a record, the server return a 404 and populate the errors stack.
|
45
|
+
mock_server_enable_routes '**/uploads/*', '**/uploads', '**/folders'
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
after :each do
|
50
|
+
mock_server_disable_all_routes!
|
51
|
+
mock_server_clear_matchers!
|
52
|
+
mock_server_response_stack_clear!
|
53
|
+
end
|
54
|
+
|
55
|
+
scenario "Some json api fun" do
|
56
|
+
mock_server_get('/api/2/projects')
|
57
|
+
mock_server_post('/api/2/projects') do |request, recorded_request|
|
58
|
+
|
59
|
+
# I use the should helpers because it return the method
|
60
|
+
# but it won't raise a failure in the test, since it happen
|
61
|
+
# inside a proc ouside the scenario. But It will populate the
|
62
|
+
# error stack if can't match anything.
|
63
|
+
|
64
|
+
recorded_request.body.name.should == recorded_request.body.name and
|
65
|
+
recorded_request.body.name.should == 'MockServer'
|
66
|
+
|
67
|
+
# Its a normal detect block, so it has to return true to match
|
68
|
+
# the record response.
|
69
|
+
}
|
70
|
+
|
71
|
+
... fun stuff ...
|
72
|
+
|
73
|
+
mock_server_success_stack.should include('/api/2/projects')
|
74
|
+
mock_server_errors_stack.should be_empty
|
75
|
+
end
|
76
|
+
|
77
|
+
# Pull request?
|
78
|
+
|
79
|
+
Yes.
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'mock_server/utils'
|
2
|
+
|
3
|
+
module MockServer
|
4
|
+
class Playback
|
5
|
+
include MockServer::Utils
|
6
|
+
|
7
|
+
def initialize(app, options = {})
|
8
|
+
@options = options
|
9
|
+
@app = app
|
10
|
+
$mock_server_options ||= options
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
return @app.call(env) unless $mock_server_options and
|
15
|
+
$mock_server_options[:routes] and
|
16
|
+
lazy_match $mock_server_options[:routes], env["PATH_INFO"]
|
17
|
+
|
18
|
+
@options.merge!($mock_server_options)
|
19
|
+
|
20
|
+
@request = Rack::Request.new(env)
|
21
|
+
@data = load_data
|
22
|
+
|
23
|
+
record = match_request
|
24
|
+
|
25
|
+
if record
|
26
|
+
$mock_server_options[:success_stack] ||= []
|
27
|
+
$mock_server_options[:success_stack] << @request.path
|
28
|
+
|
29
|
+
response = record[:response]
|
30
|
+
[response[:status], response[:headers], [response[:body]]]
|
31
|
+
else
|
32
|
+
$mock_server_options[:errors_stack] ||= []
|
33
|
+
error = { @request.path => "Couldn't match #{@request.request_method} #{@request.path}" }
|
34
|
+
$mock_server_options[:errors_stack] << error
|
35
|
+
[404, {}, ['RECORD NOT FOUND!']]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def match_request
|
42
|
+
request = Hashie::Mash.new hashified_request
|
43
|
+
|
44
|
+
# Filter out data records by path and method
|
45
|
+
@data.select! { |record|
|
46
|
+
record[:request][:path] == request[:path] and record[:request][:method] == request[:method]
|
47
|
+
}
|
48
|
+
|
49
|
+
# Filter out matchers by path and method
|
50
|
+
matchers = @options[:matchers].select { |match|
|
51
|
+
request[:method].to_s.upcase == match[:method].to_s.upcase and request[:path] == match[:path]
|
52
|
+
}
|
53
|
+
|
54
|
+
# Match the request with a record by validating against the matcher if any.
|
55
|
+
@data.detect { |entry|
|
56
|
+
recorded_request = Hashie::Mash.new entry[:request]
|
57
|
+
|
58
|
+
matchers.detect { |matcher|
|
59
|
+
if matcher[:matcher]
|
60
|
+
matcher[:matcher].call(request, recorded_request) rescue false
|
61
|
+
else
|
62
|
+
true
|
63
|
+
end
|
64
|
+
}
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'mock_server/utils'
|
2
|
+
|
3
|
+
module MockServer
|
4
|
+
class Record
|
5
|
+
include MockServer::Utils
|
6
|
+
|
7
|
+
def initialize(app, options = {})
|
8
|
+
@options = options
|
9
|
+
@app = app
|
10
|
+
$mock_server_options ||= options
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
return @app.call(env) unless $mock_server_options and
|
15
|
+
$mock_server_options[:routes] and
|
16
|
+
lazy_match $mock_server_options[:routes], env["PATH_INFO"]
|
17
|
+
|
18
|
+
@options.merge!($mock_server_options)
|
19
|
+
|
20
|
+
@request = Rack::Request.new(env)
|
21
|
+
@data = load_data
|
22
|
+
|
23
|
+
@app.call(env).tap do |response|
|
24
|
+
record_response(response)
|
25
|
+
response
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def record_response(response)
|
32
|
+
request = hashified_request
|
33
|
+
@data.reject! { |record| record[:request] == request }
|
34
|
+
|
35
|
+
@data << { :request => request, :response => hashify_response(response) }
|
36
|
+
save_data(@data)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# The purpose of the helper is to manpulate MockServer global var
|
2
|
+
# in an rspec way. $mock_server_options
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# The basic:
|
6
|
+
#
|
7
|
+
# $mock_server_options = { :path => 'fixtures/records', :filename => 'uploads' }
|
8
|
+
#
|
9
|
+
#
|
10
|
+
# $mock_server_options tell you application which URL to do its magic for both,
|
11
|
+
# record and playback mode.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# $mock_server_options[:routes] = [ '**/uploads.json', '**/uploads/*',
|
16
|
+
# '**/folders.json', '**/folders', '**/folders' ]
|
17
|
+
#
|
18
|
+
# $mock_server_options[:matchers] is an array of path, url, and matchers proc
|
19
|
+
#
|
20
|
+
# Example:
|
21
|
+
#
|
22
|
+
# $mock_server_options[:matchers] = [
|
23
|
+
# { :method => :get, :path => '/api/2/projects/1/uploads.json',
|
24
|
+
# :matcher => Proc.new { |request, recorded_request|
|
25
|
+
# request.query == recorded_request.query and
|
26
|
+
# request.query == 'count=0'
|
27
|
+
# }
|
28
|
+
# },
|
29
|
+
# { :method => :put, :path => '/api/2/uploads/20',
|
30
|
+
# :matcher => Proc.new { |request, recorded_request|
|
31
|
+
# request.query == recorded_request.query and
|
32
|
+
# request.query == 'count=0'
|
33
|
+
# }
|
34
|
+
# },
|
35
|
+
# { :method => :delete, :path => '/api/2/uploads/20' },
|
36
|
+
# { :method => :get, :path => '/api/2/projects/1/folders.json' },
|
37
|
+
# { :method => :post, :path => '/api/2/folders',
|
38
|
+
# :matcher => Proc.new { |request, recorded_request|
|
39
|
+
# request.body.project_id == recorded_request.body.project_id and
|
40
|
+
# request.body.project_id == 1 and
|
41
|
+
# recorded_request.body.name == recorded_request.body.name and
|
42
|
+
# recorded_request.body.name == 'Mockup'
|
43
|
+
# }
|
44
|
+
# },
|
45
|
+
# { :method => :put, :path => '/api/2/uploads/20',
|
46
|
+
# :matcher => Proc.new { |request, recorded_request|
|
47
|
+
# request.body.name == recorded_request.body.name
|
48
|
+
# }
|
49
|
+
# },
|
50
|
+
# { :method => :delete, :path => '/api/2/folders/3' }
|
51
|
+
# ]
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
module MockServer
|
56
|
+
module Spec
|
57
|
+
module Helpers
|
58
|
+
|
59
|
+
# Inspect
|
60
|
+
def mock_server_inspect
|
61
|
+
$mock_server_options.inspect
|
62
|
+
end
|
63
|
+
|
64
|
+
# Configuration
|
65
|
+
def mock_server_use_record(filename)
|
66
|
+
mock_server_config_set(:filename, filename)
|
67
|
+
end
|
68
|
+
|
69
|
+
def mock_server_set_fixture_path(path)
|
70
|
+
mock_server_config_set(:path, path)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Active path config
|
74
|
+
def mock_server_enable_routes(*arguments)
|
75
|
+
$mock_server_options ||= {}
|
76
|
+
$mock_server_options[:routes] ||= []
|
77
|
+
$mock_server_options[:routes] << arguments
|
78
|
+
$mock_server_options[:routes].flatten!
|
79
|
+
end
|
80
|
+
|
81
|
+
def mock_server_disable_path(path)
|
82
|
+
return unless $mock_server_options and $mock_server_options[:routes]
|
83
|
+
$mock_server_options[:routes] = if path.is_an? Array
|
84
|
+
path unless path.empty?
|
85
|
+
else
|
86
|
+
$mock_server_options[:routes] - [path]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def mock_server_disable_all_routes!
|
91
|
+
$mock_server_options[:routes] = nil
|
92
|
+
end
|
93
|
+
|
94
|
+
# Matchers helpers
|
95
|
+
def mock_server_get(path, &block)
|
96
|
+
mock_server_request :get, path, block
|
97
|
+
end
|
98
|
+
|
99
|
+
def mock_server_post(path, &block)
|
100
|
+
mock_server_request :post, path, block
|
101
|
+
end
|
102
|
+
|
103
|
+
def mock_server_put(path, &block)
|
104
|
+
mock_server_request :put, path, block
|
105
|
+
end
|
106
|
+
|
107
|
+
def mock_server_delete(path, &block)
|
108
|
+
mock_server_request :delete, path, block
|
109
|
+
end
|
110
|
+
|
111
|
+
def mock_server_clear_matchers!
|
112
|
+
mock_server_config_set(:matchers, [])
|
113
|
+
end
|
114
|
+
|
115
|
+
# Errors / Success stack
|
116
|
+
|
117
|
+
def mock_server_errors_stack
|
118
|
+
$mock_server_options[:errors_stack] || []
|
119
|
+
end
|
120
|
+
|
121
|
+
def mock_server_errors_stack_clear!
|
122
|
+
$mock_server_options[:errors_stack] = []
|
123
|
+
end
|
124
|
+
|
125
|
+
def mock_server_success_stack
|
126
|
+
$mock_server_options[:success_stack] || []
|
127
|
+
end
|
128
|
+
|
129
|
+
def mock_server_success_stack_clear!
|
130
|
+
$mock_server_options[:success_stack] = []
|
131
|
+
end
|
132
|
+
|
133
|
+
def mock_server_response_stack_clear!
|
134
|
+
mock_server_success_stack_clear!
|
135
|
+
mock_server_errors_stack_clear!
|
136
|
+
end
|
137
|
+
|
138
|
+
protected
|
139
|
+
# Configuration
|
140
|
+
def mock_server_config_set(key, value)
|
141
|
+
$mock_server_options ||= {}
|
142
|
+
$mock_server_options[key] = value
|
143
|
+
end
|
144
|
+
|
145
|
+
# Matchers helpers
|
146
|
+
def mock_server_request(method, path, matcher)
|
147
|
+
add_mock_server_matcher({ :method => method, :path => path, :matcher => matcher })
|
148
|
+
end
|
149
|
+
|
150
|
+
def add_mock_server_matcher(matcher)
|
151
|
+
$mock_server_options ||= {}
|
152
|
+
$mock_server_options[:matchers] ||= []
|
153
|
+
$mock_server_options[:matchers] << matcher
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module MockServer
|
2
|
+
module Utils
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def lazy_match(strings, path)
|
7
|
+
regexps = strings.map { |str|
|
8
|
+
escaped = Regexp.escape(str)
|
9
|
+
escaped.gsub!('\\*\\*', '[\w|\/]+')
|
10
|
+
escaped.gsub!('\\*', '[\w]+')
|
11
|
+
Regexp.new("^#{escaped}$")
|
12
|
+
}
|
13
|
+
|
14
|
+
regexps.any? { |regex| regex.match(path) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def hashified_request
|
18
|
+
body = JSON.parse(@request.body.to_a.join) rescue ''
|
19
|
+
{
|
20
|
+
:method => @request.request_method,
|
21
|
+
:path => @request.path,
|
22
|
+
:query => @request.query_string,
|
23
|
+
:body => body
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def hashify_response(response)
|
28
|
+
status = response[0]
|
29
|
+
headers = response[1]
|
30
|
+
body = response[2].body rescue ''
|
31
|
+
|
32
|
+
{
|
33
|
+
:method => @request.request_method,
|
34
|
+
:path => @request.path,
|
35
|
+
:status => status,
|
36
|
+
:headers => headers,
|
37
|
+
:body => body
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
# File utils
|
42
|
+
def records_path
|
43
|
+
File.join( @options[:path], @options[:filename] + '.yml' )
|
44
|
+
end
|
45
|
+
|
46
|
+
def load_data
|
47
|
+
FileUtils.mkdir_p(@options[:path]) unless File.exists? @options[:path]
|
48
|
+
|
49
|
+
data = YAML.load_file(records_path) rescue []
|
50
|
+
|
51
|
+
if data.is_a? Array
|
52
|
+
data
|
53
|
+
else
|
54
|
+
[]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def save_data(data)
|
59
|
+
File.open(records_path, 'w') do |f|
|
60
|
+
YAML.dump(data, f)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
data/lib/mock_server.rb
ADDED
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mock_server
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Charles Barbier
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-03 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description:
|
15
|
+
email: unixcharles@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- README.md
|
21
|
+
- LICENSE
|
22
|
+
- lib/mock_server.rb
|
23
|
+
- lib/mock_server/record.rb
|
24
|
+
- lib/mock_server/playback.rb
|
25
|
+
- lib/mock_server/utils.rb
|
26
|
+
- lib/mock_server/spec/helpers.rb
|
27
|
+
homepage: http://www.github.com/unixcharles/mock_server
|
28
|
+
licenses: []
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
requirements: []
|
46
|
+
rubyforge_project:
|
47
|
+
rubygems_version: 1.8.10
|
48
|
+
signing_key:
|
49
|
+
specification_version: 3
|
50
|
+
summary: Mock you're entire application
|
51
|
+
test_files: []
|