mimic 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +19 -0
- data/README.md +32 -1
- data/Rakefile +16 -9
- data/lib/mimic.rb +53 -23
- data/lib/mimic/api.rb +91 -0
- data/lib/mimic/fake_host.rb +30 -5
- metadata +58 -12
data/CHANGES
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
## 0.4.0
|
2
|
+
|
3
|
+
* Mimic can now be run in it's own process and configured externally using a REST API.
|
4
|
+
* Mimic can be run using the Daemons gem safely (pass :fork => false to disable built-in forking).
|
5
|
+
* All existing stubs can be cleared.
|
6
|
+
|
7
|
+
## 0.3.0
|
8
|
+
|
9
|
+
* All verb methods (get, post etc.) can take blocks
|
10
|
+
|
11
|
+
## 0.2.0
|
12
|
+
|
13
|
+
* Added support for using Rack middleware
|
14
|
+
* Removed host file modification feature
|
15
|
+
* Refactor code top build on top of Sinatra::Base
|
16
|
+
|
17
|
+
## 0.1.0
|
18
|
+
|
19
|
+
* Initial release
|
data/README.md
CHANGED
@@ -18,7 +18,7 @@ Registering to a single request stub:
|
|
18
18
|
|
19
19
|
And the result, using RestClient:
|
20
20
|
|
21
|
-
$ RestClient.get("http://www.example.com:
|
21
|
+
$ RestClient.get("http://www.example.com:11988/some/path") # => 200 | hello world
|
22
22
|
|
23
23
|
Registering multiple request stubs; note that you can stub the same path with different HTTP methods separately.
|
24
24
|
|
@@ -46,6 +46,37 @@ Finally, because Mimic is built on top of Sinatra for the core request handling,
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
+
## Using Mimic with non-Ruby processes
|
50
|
+
|
51
|
+
Mimic has a built-in REST API that lets you configure your request stubs over HTTP. This makes it possible to use Mimic from other processes that can perform HTTP requests.
|
52
|
+
|
53
|
+
First of all, you'll need to run Mimic as a daemon. You can do this with a simple Ruby script:
|
54
|
+
|
55
|
+
#!/usr/bin/env ruby
|
56
|
+
Mimic.daemonize({:port => 11988, :remote_configuration_path => '/api'})
|
57
|
+
|
58
|
+
Give the script executable permissions and then start it:
|
59
|
+
|
60
|
+
$ your_mimic_script.rb start (or run)
|
61
|
+
|
62
|
+
The remote configuration path is where the API endpoints will be mounted - this is configurable as you will not be able this path or any paths below it in your stubs, so choose one that doesn't conflict with the paths you need to stub.
|
63
|
+
|
64
|
+
The API supports both JSON and Plist payloads, defaulting to JSON. Set the request Content-Type header to application/plist for Plist requests.
|
65
|
+
|
66
|
+
For the following Mimic configuration (using the Ruby DSL):
|
67
|
+
|
68
|
+
Mimic.mimic.get("/some/path").returning("hello world")
|
69
|
+
|
70
|
+
The equivalent stub can be configured using the REST API as follows:
|
71
|
+
|
72
|
+
$ curl -d'{"path":"/some/path", "body":"hello world"}' http://localhost:11988/api/get
|
73
|
+
|
74
|
+
Likewise, a POST request to the same path could be stubbed like so:
|
75
|
+
|
76
|
+
$ curl -d'{"path":"/some/path", "body":"hello world"}' http://localhost:11988/api/post
|
77
|
+
|
78
|
+
The end-point of the API is the HTTP verb you are stubbing, the path, response body, code and headers are specified in the POST data (a hash in JSON or Plist format). See the HTTP API Cucumber features for more examples.
|
79
|
+
|
49
80
|
## Contributors
|
50
81
|
|
51
82
|
* [James Fairbairn](http://github.com/jfairbairn)
|
data/Rakefile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'cucumber'
|
3
3
|
require 'cucumber/rake/task'
|
4
|
-
require '
|
4
|
+
require 'rspec/core/rake_task'
|
5
5
|
|
6
6
|
desc "Run all Cucumber features"
|
7
7
|
Cucumber::Rake::Task.new(:features) do |t|
|
@@ -9,12 +9,11 @@ Cucumber::Rake::Task.new(:features) do |t|
|
|
9
9
|
end
|
10
10
|
|
11
11
|
desc "Run all specs"
|
12
|
-
|
13
|
-
t.spec_files = FileList['spec/**/*.rb']
|
12
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
14
13
|
end
|
15
14
|
|
16
|
-
task :default => :
|
17
|
-
task :all => [:
|
15
|
+
task :default => :spec
|
16
|
+
task :all => [:spec, :features]
|
18
17
|
|
19
18
|
require "rake/gempackagetask"
|
20
19
|
require "rake/rdoctask"
|
@@ -28,7 +27,7 @@ spec = Gem::Specification.new do |s|
|
|
28
27
|
|
29
28
|
# Change these as appropriate
|
30
29
|
s.name = "mimic"
|
31
|
-
s.version = "0.
|
30
|
+
s.version = "0.4.0"
|
32
31
|
s.summary = "A Ruby gem for faking external web services for testing"
|
33
32
|
s.authors = "Luke Redpath"
|
34
33
|
s.email = "luke@lukeredpath.co.uk"
|
@@ -39,18 +38,21 @@ spec = Gem::Specification.new do |s|
|
|
39
38
|
s.rdoc_options = %w(--main README.md)
|
40
39
|
|
41
40
|
# Add any extra files to include in the gem
|
42
|
-
s.files = %w(LICENSE Rakefile README.md) + Dir.glob("{spec,lib/**/*}")
|
41
|
+
s.files = %w(LICENSE CHANGES Rakefile README.md) + Dir.glob("{spec,lib/**/*}")
|
43
42
|
s.require_paths = ["lib"]
|
44
43
|
|
45
44
|
# If you want to depend on other gems, add them here, along with any
|
46
45
|
# relevant versions
|
47
46
|
s.add_dependency("rack")
|
48
47
|
s.add_dependency("sinatra")
|
48
|
+
s.add_dependency("json")
|
49
|
+
s.add_dependency("plist")
|
49
50
|
|
50
51
|
# If your tests use any gems, include them here
|
51
|
-
s.add_development_dependency("rspec")
|
52
|
+
s.add_development_dependency("rspec", "~> 2.4.0")
|
52
53
|
s.add_development_dependency("cucumber")
|
53
54
|
s.add_development_dependency("mocha")
|
55
|
+
s.add_development_dependency("rest-client")
|
54
56
|
end
|
55
57
|
|
56
58
|
# This task actually builds the gem. We also regenerate a static
|
@@ -85,6 +87,11 @@ task :clean => [:clobber_rdoc, :clobber_package] do
|
|
85
87
|
end
|
86
88
|
|
87
89
|
task 'Release if all specs pass'
|
88
|
-
task :release => [:
|
90
|
+
task :release => [:clean, :bundle, :spec, :features, :package] do
|
89
91
|
system("gem push pkg/#{spec.name}-#{spec.version}.gem")
|
90
92
|
end
|
93
|
+
|
94
|
+
desc 'Install all gem dependencies'
|
95
|
+
task :bundle => :gemspec do
|
96
|
+
system("bundle")
|
97
|
+
end
|
data/lib/mimic.rb
CHANGED
@@ -5,19 +5,37 @@ require 'logger'
|
|
5
5
|
|
6
6
|
module Mimic
|
7
7
|
MIMIC_DEFAULT_PORT = 11988
|
8
|
-
|
8
|
+
|
9
|
+
MIMIC_DEFAULT_OPTIONS = {
|
10
|
+
:hostname => 'localhost',
|
11
|
+
:port => MIMIC_DEFAULT_PORT,
|
12
|
+
:remote_configuration_path => nil,
|
13
|
+
:fork => true
|
14
|
+
}
|
15
|
+
|
9
16
|
def self.mimic(options = {}, &block)
|
10
|
-
options =
|
11
|
-
|
12
|
-
FakeHost.new(options[:hostname]).tap do |host|
|
17
|
+
options = MIMIC_DEFAULT_OPTIONS.merge(options)
|
18
|
+
|
19
|
+
host = FakeHost.new(options[:hostname], options[:remote_configuration_path]).tap do |host|
|
13
20
|
host.instance_eval(&block) if block_given?
|
14
|
-
Server.instance.serve(host, options[:port])
|
21
|
+
Server.instance.serve(host, options[:port], options[:fork])
|
15
22
|
end
|
23
|
+
add_host(host)
|
16
24
|
end
|
17
|
-
|
25
|
+
|
18
26
|
def self.cleanup!
|
19
27
|
Mimic::Server.instance.shutdown
|
20
28
|
end
|
29
|
+
|
30
|
+
def self.reset_all!
|
31
|
+
@hosts.each { |h| h.clear }
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def self.add_host(host)
|
37
|
+
host.tap { |h| (@hosts ||= []) << h }
|
38
|
+
end
|
21
39
|
|
22
40
|
class Server
|
23
41
|
include Singleton
|
@@ -26,28 +44,40 @@ module Mimic
|
|
26
44
|
@logger ||= Logger.new(StringIO.new)
|
27
45
|
end
|
28
46
|
|
29
|
-
def serve(host_app, port)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
47
|
+
def serve(host_app, port, should_fork)
|
48
|
+
if should_fork
|
49
|
+
@thread = Thread.fork do
|
50
|
+
start_service(host_app, port)
|
51
|
+
end
|
52
|
+
|
53
|
+
wait_for_service(host_app.hostname, port)
|
54
|
+
|
55
|
+
else
|
56
|
+
start_service(host_app, port)
|
37
57
|
end
|
38
|
-
|
39
|
-
wait_for_service(host_app.hostname, port)
|
40
58
|
end
|
41
|
-
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
59
|
+
|
60
|
+
def start_service(host_app, port)
|
61
|
+
Rack::Handler::WEBrick.run(host_app.url_map, {
|
62
|
+
:Port => port,
|
63
|
+
:Logger => logger,
|
64
|
+
:AccessLog => logger,
|
65
|
+
|
66
|
+
}) do |server|
|
67
|
+
@server = server
|
68
|
+
|
69
|
+
trap("TERM") { @server.shutdown }
|
70
|
+
trap("INT") { @server.shutdown }
|
46
71
|
end
|
47
72
|
end
|
48
|
-
|
73
|
+
|
74
|
+
def shutdown
|
75
|
+
Thread.kill(@thread) if @thread
|
76
|
+
@server.shutdown if @server
|
77
|
+
end
|
78
|
+
|
49
79
|
# courtesy of http://is.gd/eoYho
|
50
|
-
|
80
|
+
|
51
81
|
def listening?(host, port)
|
52
82
|
begin
|
53
83
|
socket = TCPSocket.new(host, port)
|
data/lib/mimic/api.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'plist'
|
3
|
+
|
4
|
+
module Mimic
|
5
|
+
class API < Sinatra::Base
|
6
|
+
class << self
|
7
|
+
attr_accessor :host
|
8
|
+
end
|
9
|
+
|
10
|
+
def host
|
11
|
+
self.class.host
|
12
|
+
end
|
13
|
+
|
14
|
+
%w{get post put delete head}.each do |verb|
|
15
|
+
post "/#{verb}" do
|
16
|
+
api_request = APIRequest.from_request(request, verb)
|
17
|
+
api_request.setup_stubs_on(host)
|
18
|
+
[201, {}, api_request.to_s]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
post "/multi" do
|
23
|
+
api_request = APIRequest.from_request(request)
|
24
|
+
api_request.setup_stubs_on(host)
|
25
|
+
[201, {}, api_request.to_s]
|
26
|
+
end
|
27
|
+
|
28
|
+
post "/clear" do
|
29
|
+
self.host.clear
|
30
|
+
[200, {}, ""]
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
class APIRequest
|
36
|
+
def initialize(data, method = nil)
|
37
|
+
@data = data
|
38
|
+
@method = (method || "GET")
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
@data.inspect
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.from_request(request, method = nil)
|
46
|
+
case request.content_type
|
47
|
+
when /json/
|
48
|
+
data = JSON.parse(request.body.string)
|
49
|
+
when /plist/
|
50
|
+
data = Plist.parse_xml(request.body.string)
|
51
|
+
else
|
52
|
+
data = JSON.parse(request.body.string)
|
53
|
+
end
|
54
|
+
new(data, method)
|
55
|
+
end
|
56
|
+
|
57
|
+
def setup_stubs_on(host)
|
58
|
+
(@data["stubs"] || [@data]).each do |stub_data|
|
59
|
+
Stub.new(stub_data, stub_data['method'] || @method).on(host)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Stub
|
64
|
+
def initialize(data, method = nil)
|
65
|
+
@data = data
|
66
|
+
@method = method
|
67
|
+
end
|
68
|
+
|
69
|
+
def on(host)
|
70
|
+
host.send(@method.downcase.to_sym, path).returning(body, code, headers)
|
71
|
+
end
|
72
|
+
|
73
|
+
def path
|
74
|
+
@data['path'] || '/'
|
75
|
+
end
|
76
|
+
|
77
|
+
def body
|
78
|
+
@data['body'] || ''
|
79
|
+
end
|
80
|
+
|
81
|
+
def code
|
82
|
+
@data['code'] || 200
|
83
|
+
end
|
84
|
+
|
85
|
+
def headers
|
86
|
+
@data['headers'] || {}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/mimic/fake_host.rb
CHANGED
@@ -1,17 +1,21 @@
|
|
1
1
|
require 'sinatra/base'
|
2
|
+
require 'mimic/api'
|
2
3
|
|
3
4
|
module Mimic
|
4
5
|
class FakeHost
|
5
|
-
attr_reader :hostname
|
6
|
+
attr_reader :hostname, :url_map
|
6
7
|
|
7
|
-
def initialize(hostname)
|
8
|
+
def initialize(hostname, remote_configuration_path = nil)
|
8
9
|
@hostname = hostname
|
9
10
|
@stubs = []
|
10
11
|
@app = Class.new(Sinatra::Base)
|
11
|
-
|
12
|
+
@remote_configuration_path = remote_configuration_path
|
13
|
+
|
12
14
|
@app.not_found do
|
13
15
|
[404, {}, ""]
|
14
16
|
end
|
17
|
+
|
18
|
+
build_url_map!
|
15
19
|
end
|
16
20
|
|
17
21
|
def get(path, &block)
|
@@ -34,17 +38,27 @@ module Mimic
|
|
34
38
|
request("HEAD", path, &block)
|
35
39
|
end
|
36
40
|
|
41
|
+
def import(path)
|
42
|
+
if File.exists?(path)
|
43
|
+
instance_eval(File.read(path))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
37
47
|
def call(env)
|
38
48
|
@stubs.each(&:build)
|
39
49
|
@app.call(env)
|
40
50
|
end
|
41
51
|
|
42
|
-
def
|
43
|
-
@
|
52
|
+
def clear
|
53
|
+
@stubs.clear
|
44
54
|
end
|
45
55
|
|
46
56
|
private
|
47
57
|
|
58
|
+
def method_missing(method, *args, &block)
|
59
|
+
@app.send(method, *args, &block)
|
60
|
+
end
|
61
|
+
|
48
62
|
def request(method, path, &block)
|
49
63
|
if block_given?
|
50
64
|
@app.send(method.downcase, path, &block)
|
@@ -54,6 +68,17 @@ module Mimic
|
|
54
68
|
end
|
55
69
|
end
|
56
70
|
|
71
|
+
def build_url_map!
|
72
|
+
routes = {'/' => self}
|
73
|
+
|
74
|
+
if @remote_configuration_path
|
75
|
+
API.host = self
|
76
|
+
routes[@remote_configuration_path] = API
|
77
|
+
end
|
78
|
+
|
79
|
+
@url_map = Rack::URLMap.new(routes)
|
80
|
+
end
|
81
|
+
|
57
82
|
class StubbedRequest
|
58
83
|
def initialize(app, method, path)
|
59
84
|
@method, @path = method, path
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mimic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 15
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 4
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.4.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Luke Redpath
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-01-13 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -47,7 +47,7 @@ dependencies:
|
|
47
47
|
type: :runtime
|
48
48
|
version_requirements: *id002
|
49
49
|
- !ruby/object:Gem::Dependency
|
50
|
-
name:
|
50
|
+
name: json
|
51
51
|
prerelease: false
|
52
52
|
requirement: &id003 !ruby/object:Gem::Requirement
|
53
53
|
none: false
|
@@ -58,10 +58,10 @@ dependencies:
|
|
58
58
|
segments:
|
59
59
|
- 0
|
60
60
|
version: "0"
|
61
|
-
type: :
|
61
|
+
type: :runtime
|
62
62
|
version_requirements: *id003
|
63
63
|
- !ruby/object:Gem::Dependency
|
64
|
-
name:
|
64
|
+
name: plist
|
65
65
|
prerelease: false
|
66
66
|
requirement: &id004 !ruby/object:Gem::Requirement
|
67
67
|
none: false
|
@@ -72,12 +72,28 @@ dependencies:
|
|
72
72
|
segments:
|
73
73
|
- 0
|
74
74
|
version: "0"
|
75
|
-
type: :
|
75
|
+
type: :runtime
|
76
76
|
version_requirements: *id004
|
77
77
|
- !ruby/object:Gem::Dependency
|
78
|
-
name:
|
78
|
+
name: rspec
|
79
79
|
prerelease: false
|
80
80
|
requirement: &id005 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 31
|
86
|
+
segments:
|
87
|
+
- 2
|
88
|
+
- 4
|
89
|
+
- 0
|
90
|
+
version: 2.4.0
|
91
|
+
type: :development
|
92
|
+
version_requirements: *id005
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: cucumber
|
95
|
+
prerelease: false
|
96
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
81
97
|
none: false
|
82
98
|
requirements:
|
83
99
|
- - ">="
|
@@ -87,7 +103,35 @@ dependencies:
|
|
87
103
|
- 0
|
88
104
|
version: "0"
|
89
105
|
type: :development
|
90
|
-
version_requirements: *
|
106
|
+
version_requirements: *id006
|
107
|
+
- !ruby/object:Gem::Dependency
|
108
|
+
name: mocha
|
109
|
+
prerelease: false
|
110
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
hash: 3
|
116
|
+
segments:
|
117
|
+
- 0
|
118
|
+
version: "0"
|
119
|
+
type: :development
|
120
|
+
version_requirements: *id007
|
121
|
+
- !ruby/object:Gem::Dependency
|
122
|
+
name: rest-client
|
123
|
+
prerelease: false
|
124
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
hash: 3
|
130
|
+
segments:
|
131
|
+
- 0
|
132
|
+
version: "0"
|
133
|
+
type: :development
|
134
|
+
version_requirements: *id008
|
91
135
|
description:
|
92
136
|
email: luke@lukeredpath.co.uk
|
93
137
|
executables: []
|
@@ -98,8 +142,10 @@ extra_rdoc_files:
|
|
98
142
|
- README.md
|
99
143
|
files:
|
100
144
|
- LICENSE
|
145
|
+
- CHANGES
|
101
146
|
- Rakefile
|
102
147
|
- README.md
|
148
|
+
- lib/mimic/api.rb
|
103
149
|
- lib/mimic/fake_host.rb
|
104
150
|
- lib/mimic.rb
|
105
151
|
has_rdoc: true
|
@@ -133,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
133
179
|
requirements: []
|
134
180
|
|
135
181
|
rubyforge_project:
|
136
|
-
rubygems_version: 1.
|
182
|
+
rubygems_version: 1.4.1
|
137
183
|
signing_key:
|
138
184
|
specification_version: 3
|
139
185
|
summary: A Ruby gem for faking external web services for testing
|