cross-stub 0.1.4 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.txt +12 -0
- data/README.rdoc +85 -29
- data/Rakefile +8 -1
- data/VERSION +1 -1
- data/cross-stub.gemspec +37 -19
- data/lib/cross-stub.rb +63 -24
- data/lib/cross-stub/arguments.rb +21 -0
- data/lib/cross-stub/arguments/array.rb +16 -0
- data/lib/cross-stub/arguments/hash.rb +17 -0
- data/lib/cross-stub/arguments/proc.rb +73 -0
- data/lib/cross-stub/cache.rb +36 -0
- data/lib/cross-stub/stores.rb +4 -0
- data/lib/cross-stub/stores/base.rb +38 -0
- data/lib/cross-stub/stores/file.rb +39 -0
- data/lib/cross-stub/stores/memcache.rb +40 -0
- data/lib/cross-stub/stores/redis.rb +41 -0
- data/lib/cross-stub/stubber.rb +132 -0
- data/rails_generators/cross_stub/cross_stub_generator.rb +1 -1
- data/rails_generators/cross_stub/templates/config/initializers/cross-stub.rb +9 -1
- data/rails_generators/cross_stub/templates/features/support/cross-stub.rb +16 -2
- data/spec/arguments/proc_spec.rb +689 -0
- data/spec/includes.rb +103 -0
- data/spec/integration/clearing_instance_stubs_spec.rb +119 -0
- data/spec/integration/clearing_stubs_spec.rb +118 -0
- data/spec/integration/creating_instance_stubs_spec.rb +91 -0
- data/spec/integration/creating_stubs_spec.rb +95 -0
- data/spec/integration/shared_spec.rb +35 -0
- data/spec/integration/stubbing_error_spec.rb +69 -0
- data/spec/service.rb +114 -0
- data/spec/spec_helper.rb +1 -41
- metadata +58 -26
- data/lib/cross-stub/cache_helpers.rb +0 -48
- data/lib/cross-stub/pseudo_class.rb +0 -82
- data/lib/cross-stub/setup_helpers.rb +0 -13
- data/lib/cross-stub/stub_helpers.rb +0 -64
- data/spec/cross-stub/clearing_stubs_spec.rb +0 -112
- data/spec/cross-stub/creating_stubs_spec.rb +0 -110
- data/spec/cross-stub/stubbing_error_spec.rb +0 -38
- data/spec/helpers.rb +0 -125
data/HISTORY.txt
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
=== 0.2.0 2010-07-16
|
2
|
+
|
3
|
+
= New Features
|
4
|
+
* added support for cross-stubbing instances [#liangzan]
|
5
|
+
* added support for alternative cache stores :memcache & :redis [#ngty]
|
6
|
+
* officially support MRI-1.8.7, MRI-1.9.1, JRUBY-1.5.1 & REE-1.8.7 [#ngty]
|
7
|
+
|
8
|
+
= House-Keeping
|
9
|
+
|
10
|
+
* dropped ParseTree dependency, use RubyParser instead [#ngty]
|
11
|
+
* complete rewrite to have cleaner implementation (hopefully) [#ngty]
|
12
|
+
|
1
13
|
=== 0.1.4 2010-04-03
|
2
14
|
|
3
15
|
= Bugfixes
|
data/README.rdoc
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
== Introduction
|
4
4
|
|
5
|
-
Existing mocking/stubbing frameworks support only stubbing in the current process. This is OK most of the time. However, when running cucumber integration test suite in
|
5
|
+
Existing mocking/stubbing frameworks support only stubbing in the current process. This is OK most of the time. However, when running cucumber integration test suite in another process, these in-process stubbing frameworks simply doesn't help. Eg. I want Time.now to always return a timing that should be a Sunday, how do I do that when running cucumber using selenium, culerity, steam, blah, blah driver? It doesn't seem straight-forward me.
|
6
6
|
|
7
7
|
(Let's not argue whether stubbing should be encouraged. It is an itch, the poor itch needs to be scratched.)
|
8
8
|
|
@@ -14,77 +14,133 @@ It's hosted on gemcutter.org.
|
|
14
14
|
|
15
15
|
== Setting Up
|
16
16
|
|
17
|
-
#1.
|
17
|
+
=== #1. Rails:
|
18
18
|
|
19
19
|
$ ./script/generate cucumber
|
20
20
|
$ ./script/generate cross_stub
|
21
21
|
|
22
|
-
#2.
|
22
|
+
=== #2. Others (back to basics):
|
23
23
|
|
24
24
|
# In the test setup method:
|
25
|
-
CrossStub.setup :file => <
|
25
|
+
CrossStub.setup :file => <CACHE_FILE>
|
26
26
|
|
27
27
|
# In the test teardown method:
|
28
28
|
CrossStub.clear
|
29
29
|
|
30
30
|
# Find an entry point in your target application, eg. in a server, the
|
31
31
|
# point where all request handling starts:
|
32
|
-
CrossStub.refresh :file => <
|
32
|
+
CrossStub.refresh :file => <CACHE_FILE>
|
33
|
+
|
34
|
+
For a full list of available cache stores, scroll down to take a look at the 'Cache Stores' section.
|
33
35
|
|
34
36
|
== Using It
|
35
37
|
|
36
|
-
|
38
|
+
Cross-stubbing is simple:
|
39
|
+
|
40
|
+
=== #1. Simple returning of nil or non-nil value:
|
37
41
|
|
38
|
-
#1.
|
42
|
+
==== #1.1. Class method:
|
39
43
|
|
40
44
|
class Someone
|
41
|
-
def self.
|
42
|
-
'
|
45
|
+
def self.say
|
46
|
+
'hello'
|
43
47
|
end
|
44
48
|
end
|
45
49
|
|
46
|
-
Someone.xstub(:
|
47
|
-
Someone.
|
50
|
+
Someone.xstub(:say)
|
51
|
+
Someone.say # yields: nil
|
52
|
+
|
53
|
+
Someone.xstub(:say => 'HELLO')
|
54
|
+
Someone.say # yields: 'HELLO'
|
55
|
+
|
56
|
+
==== #1.2. Instance method:
|
57
|
+
|
58
|
+
Someone.xstub(:say, :instance => true)
|
59
|
+
Someone.new.say # yields: nil
|
60
|
+
|
61
|
+
Someone.xstub({:say => 'HELLO'}, :instance => true)
|
62
|
+
Someone.new.say # yields: 'HELLO'
|
48
63
|
|
49
|
-
|
50
|
-
Someone.laugh # yields: 'HoHo'
|
64
|
+
=== #2. If a stubbed method requires argument, pass xstub a proc:
|
51
65
|
|
52
|
-
#2.
|
66
|
+
==== #2.1. Class method:
|
53
67
|
|
54
68
|
Someone.xstub do
|
55
|
-
def
|
56
|
-
"
|
69
|
+
def say(something)
|
70
|
+
'saying "%s"' % something
|
57
71
|
end
|
58
72
|
end
|
59
73
|
|
60
|
-
Someone.
|
74
|
+
Someone.say('HELLO') # yields: 'saying "HELLO"'
|
75
|
+
|
76
|
+
==== #2.2. Instance method:
|
77
|
+
|
78
|
+
Someone.xstub(:instance => true) do
|
79
|
+
def say(something)
|
80
|
+
'saying "%s"' % something
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
Someone.new.say('HELLO') # yields: 'saying "HELLO"'
|
85
|
+
|
86
|
+
IMPORTANT: Since switching from ParseTree to RubyParser, dynamic code analysis is no longer possible. When defining methods in the proc, fanciful coding is strongly discouraged, pls follow the conservative style suggested above.
|
61
87
|
|
62
|
-
#3. Something more complicated:
|
88
|
+
=== #3. Something more complicated:
|
63
89
|
|
64
90
|
something = 'hello'
|
65
91
|
Someone.xstub do
|
66
|
-
def
|
67
|
-
|
92
|
+
def say
|
93
|
+
'saying "%s"' % something
|
68
94
|
end
|
69
95
|
end
|
70
96
|
|
71
|
-
Someone.
|
97
|
+
Someone.say # failure !!
|
72
98
|
|
73
99
|
The above fails as a result of undefined variable/method 'something', to workaround we can have:
|
74
100
|
|
75
|
-
Someone.xstub(:something => '
|
76
|
-
def
|
77
|
-
|
101
|
+
Someone.xstub(:something => 'HELLO') do
|
102
|
+
def say
|
103
|
+
'saying "%s"' % something
|
78
104
|
end
|
79
105
|
end
|
80
106
|
|
81
|
-
Someone.
|
107
|
+
Someone.say # yields: 'saying "HELLO"'
|
108
|
+
|
109
|
+
== Cache Stores
|
110
|
+
|
111
|
+
Cache stores are needed to allow stubs to be made available for different processes. The following describes all cache stores available:
|
112
|
+
|
113
|
+
=== #1. File
|
114
|
+
|
115
|
+
# Setting up (current process)
|
116
|
+
CrossStub.setup :file => '<CACHE_FILE>'
|
117
|
+
|
118
|
+
# Refreshing (other process)
|
119
|
+
CrossStub.refresh :file => '<CACHE_FILE>'
|
120
|
+
|
121
|
+
=== #2. Memcache (requires memcache-client gem)
|
122
|
+
|
123
|
+
# Setting up (current process)
|
124
|
+
CrossStub.setup :memcache => 'localhost:11211/<CACHE_ID>'
|
125
|
+
|
126
|
+
# Refreshing (other process)
|
127
|
+
CrossStub.refresh :memcache => 'localhost:11211/<CACHE_ID>'
|
128
|
+
|
129
|
+
=== #3. Redis (requires redis gem)
|
130
|
+
|
131
|
+
# Setting up (current process)
|
132
|
+
CrossStub.setup :redis => 'localhost:6379/<CACHE_ID>'
|
133
|
+
|
134
|
+
# Refreshing (other process)
|
135
|
+
CrossStub.refresh :redis => 'localhost:6379/<CACHE_ID>'
|
136
|
+
|
137
|
+
Adding new store is super easy (w.r.t testing & actual implementation), let me know if u need more :]
|
82
138
|
|
83
139
|
== Caveats
|
84
140
|
|
85
141
|
#1. Cross-stub uses ruby's Marshal class to dump & load the stubs, thus it has the same limitations as Marshal
|
86
142
|
|
87
|
-
#2.
|
143
|
+
#2. Having switched from ParseTree to RubyParser, dynamic code analysis is no longer available, the proc taken by xstub no longer supports fanciful coding, pls follow the conservative style suggested in this README.
|
88
144
|
|
89
145
|
== TODO(s)
|
90
146
|
|
@@ -92,10 +148,10 @@ The above fails as a result of undefined variable/method 'something', to workaro
|
|
92
148
|
|
93
149
|
== Contacts
|
94
150
|
|
95
|
-
Written 2009 by:
|
151
|
+
Written since 2009 by:
|
96
152
|
|
97
|
-
#1. NgTzeYang, contact ngty77[at]gmail
|
153
|
+
#1. NgTzeYang, contact ngty77[at]gmail[dot]com or http://github.com/ngty
|
98
154
|
|
99
|
-
#2. WongLiangZan, contact liangzan[at]gmail
|
155
|
+
#2. WongLiangZan, contact liangzan[at]gmail[dot]com or http://github.com/liangzan
|
100
156
|
|
101
157
|
Released under the MIT license
|
data/Rakefile
CHANGED
@@ -12,9 +12,16 @@ begin
|
|
12
12
|
gem.authors = ["NgTzeYang"]
|
13
13
|
gem.add_development_dependency "bacon", ">= 0.0.0"
|
14
14
|
gem.add_development_dependency "eventmachine", ">= 0.0.0"
|
15
|
-
gem.add_dependency "ParseTree", "= 3.0.4"
|
16
15
|
gem.add_dependency "ruby2ruby", "= 1.2.4"
|
16
|
+
gem.add_dependency "ruby_parser", "= 2.0.4"
|
17
|
+
gem.required_ruby_version = '>= 1.8.7'
|
18
|
+
# ##
|
19
|
+
# TODO: How do we declare the following optional dependencies ??
|
20
|
+
# 1. gem.add_dependency "memcache-client", "= 1.8.5"
|
21
|
+
# 2. gem.add_dependency "redis", "= 2.0.3"
|
22
|
+
# ##
|
17
23
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
24
|
+
#
|
18
25
|
end
|
19
26
|
rescue LoadError
|
20
27
|
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/cross-stub.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{cross-stub}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["NgTzeYang"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-07-16}
|
13
13
|
s.description = %q{}
|
14
14
|
s.email = %q{ngty77@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -26,31 +26,49 @@ Gem::Specification.new do |s|
|
|
26
26
|
"VERSION",
|
27
27
|
"cross-stub.gemspec",
|
28
28
|
"lib/cross-stub.rb",
|
29
|
-
"lib/cross-stub/
|
30
|
-
"lib/cross-stub/
|
31
|
-
"lib/cross-stub/
|
32
|
-
"lib/cross-stub/
|
29
|
+
"lib/cross-stub/arguments.rb",
|
30
|
+
"lib/cross-stub/arguments/array.rb",
|
31
|
+
"lib/cross-stub/arguments/hash.rb",
|
32
|
+
"lib/cross-stub/arguments/proc.rb",
|
33
|
+
"lib/cross-stub/cache.rb",
|
34
|
+
"lib/cross-stub/stores.rb",
|
35
|
+
"lib/cross-stub/stores/base.rb",
|
36
|
+
"lib/cross-stub/stores/file.rb",
|
37
|
+
"lib/cross-stub/stores/memcache.rb",
|
38
|
+
"lib/cross-stub/stores/redis.rb",
|
39
|
+
"lib/cross-stub/stubber.rb",
|
33
40
|
"rails_generators/cross_stub/cross_stub_generator.rb",
|
34
41
|
"rails_generators/cross_stub/templates/config/initializers/cross-stub.rb",
|
35
42
|
"rails_generators/cross_stub/templates/features/support/cross-stub.rb",
|
36
43
|
"spec/.bacon",
|
37
|
-
"spec/
|
38
|
-
"spec/
|
39
|
-
"spec/
|
40
|
-
"spec/
|
44
|
+
"spec/arguments/proc_spec.rb",
|
45
|
+
"spec/includes.rb",
|
46
|
+
"spec/integration/clearing_instance_stubs_spec.rb",
|
47
|
+
"spec/integration/clearing_stubs_spec.rb",
|
48
|
+
"spec/integration/creating_instance_stubs_spec.rb",
|
49
|
+
"spec/integration/creating_stubs_spec.rb",
|
50
|
+
"spec/integration/shared_spec.rb",
|
51
|
+
"spec/integration/stubbing_error_spec.rb",
|
52
|
+
"spec/service.rb",
|
41
53
|
"spec/spec_helper.rb",
|
42
54
|
"tmp/.dummy"
|
43
55
|
]
|
44
56
|
s.homepage = %q{http://github.com/ngty/cross-stub}
|
45
57
|
s.rdoc_options = ["--charset=UTF-8"]
|
46
58
|
s.require_paths = ["lib"]
|
47
|
-
s.
|
59
|
+
s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
|
60
|
+
s.rubygems_version = %q{1.3.7}
|
48
61
|
s.summary = %q{Simple cross process stubbing}
|
49
62
|
s.test_files = [
|
50
|
-
"spec/
|
51
|
-
"spec/
|
52
|
-
"spec/
|
53
|
-
"spec/
|
63
|
+
"spec/includes.rb",
|
64
|
+
"spec/arguments/proc_spec.rb",
|
65
|
+
"spec/service.rb",
|
66
|
+
"spec/integration/stubbing_error_spec.rb",
|
67
|
+
"spec/integration/creating_instance_stubs_spec.rb",
|
68
|
+
"spec/integration/shared_spec.rb",
|
69
|
+
"spec/integration/clearing_stubs_spec.rb",
|
70
|
+
"spec/integration/creating_stubs_spec.rb",
|
71
|
+
"spec/integration/clearing_instance_stubs_spec.rb",
|
54
72
|
"spec/spec_helper.rb"
|
55
73
|
]
|
56
74
|
|
@@ -58,22 +76,22 @@ Gem::Specification.new do |s|
|
|
58
76
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
59
77
|
s.specification_version = 3
|
60
78
|
|
61
|
-
if Gem::Version.new(Gem::
|
79
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
62
80
|
s.add_development_dependency(%q<bacon>, [">= 0.0.0"])
|
63
81
|
s.add_development_dependency(%q<eventmachine>, [">= 0.0.0"])
|
64
|
-
s.add_runtime_dependency(%q<ParseTree>, ["= 3.0.4"])
|
65
82
|
s.add_runtime_dependency(%q<ruby2ruby>, ["= 1.2.4"])
|
83
|
+
s.add_runtime_dependency(%q<ruby_parser>, ["= 2.0.4"])
|
66
84
|
else
|
67
85
|
s.add_dependency(%q<bacon>, [">= 0.0.0"])
|
68
86
|
s.add_dependency(%q<eventmachine>, [">= 0.0.0"])
|
69
|
-
s.add_dependency(%q<ParseTree>, ["= 3.0.4"])
|
70
87
|
s.add_dependency(%q<ruby2ruby>, ["= 1.2.4"])
|
88
|
+
s.add_dependency(%q<ruby_parser>, ["= 2.0.4"])
|
71
89
|
end
|
72
90
|
else
|
73
91
|
s.add_dependency(%q<bacon>, [">= 0.0.0"])
|
74
92
|
s.add_dependency(%q<eventmachine>, [">= 0.0.0"])
|
75
|
-
s.add_dependency(%q<ParseTree>, ["= 3.0.4"])
|
76
93
|
s.add_dependency(%q<ruby2ruby>, ["= 1.2.4"])
|
94
|
+
s.add_dependency(%q<ruby_parser>, ["= 2.0.4"])
|
77
95
|
end
|
78
96
|
end
|
79
97
|
|
data/lib/cross-stub.rb
CHANGED
@@ -1,47 +1,81 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require '
|
2
|
+
require 'base64'
|
3
3
|
require 'ruby2ruby'
|
4
|
-
require '
|
5
|
-
require '
|
6
|
-
require 'cross-stub/
|
7
|
-
require 'cross-stub/
|
4
|
+
require 'ruby_parser'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'cross-stub/cache'
|
7
|
+
require 'cross-stub/stubber'
|
8
|
+
require 'cross-stub/arguments'
|
9
|
+
require 'cross-stub/stores'
|
8
10
|
|
9
11
|
module CrossStub
|
10
12
|
|
11
13
|
class Error < Exception ; end
|
12
14
|
class CannotStubInstanceError < Error ; end
|
15
|
+
class ModuleCannotBeInstantiatedError < Error ; end
|
13
16
|
|
14
17
|
class << self
|
15
18
|
|
16
|
-
|
17
|
-
|
18
|
-
include StubHelpers
|
19
|
+
extend Forwardable
|
20
|
+
def_delegator :'CrossStub::Cache', :setup
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
def refresh(opts)
|
23
|
+
Cache.refresh(opts)
|
24
|
+
[[:previous, :unapply], [:current, :apply]].each do |(mode, method)|
|
25
|
+
Cache.get(mode).map do |cache_key, stubs|
|
26
|
+
type, thing = stubbable(cache_key)
|
27
|
+
Stubber.send(method, type, thing, stubs)
|
28
|
+
end
|
29
|
+
end
|
25
30
|
end
|
26
31
|
|
27
32
|
def clear
|
28
|
-
|
33
|
+
Cache.get.map do |cache_key, stubs|
|
34
|
+
type, thing = stubbable(cache_key)
|
35
|
+
Stubber.unapply(type, thing, stubs)
|
36
|
+
end
|
37
|
+
Cache.clear
|
29
38
|
end
|
30
39
|
|
31
|
-
def apply(
|
32
|
-
|
40
|
+
def apply(type, thing, cache_key, args, &block)
|
41
|
+
Cache.set(Cache.get.merge(
|
42
|
+
cache_key => Stubber.apply(type, thing, Arguments.parse(args, &block))
|
43
|
+
))
|
33
44
|
end
|
34
45
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
46
|
+
def stubbable(str)
|
47
|
+
[
|
48
|
+
str.end_with?(suffix = '#instance') ? :instance : :class,
|
49
|
+
klassify(str.sub(suffix,''))
|
50
|
+
]
|
51
|
+
end
|
52
|
+
|
53
|
+
def klassify(str)
|
54
|
+
str.split('::').inject(Object){|klass, const| klass.const_get(const) }
|
38
55
|
end
|
39
56
|
|
40
57
|
end
|
41
58
|
|
42
59
|
module ClassMethods
|
43
|
-
def xstub(*args, &
|
44
|
-
|
60
|
+
def xstub(*args, &block)
|
61
|
+
if args[-1].is_a?(::Hash) && args[-1][:instance]
|
62
|
+
raise ModuleCannotBeInstantiatedError if self.class == Module
|
63
|
+
CrossStub.apply(
|
64
|
+
:instance, # stubbing for instance
|
65
|
+
self, # the class to action on
|
66
|
+
'%s#instance' % self, # cache key (storing of stubbing info for other process)
|
67
|
+
args.size>1 ? args[0..-2] : [], # stubbing arguments
|
68
|
+
&block # any other more complex stubbing arguments
|
69
|
+
)
|
70
|
+
else
|
71
|
+
CrossStub.apply(
|
72
|
+
:class, # stubbing for class/module
|
73
|
+
self, # the class to action on
|
74
|
+
"#{self}", # cache key (storing of stubbing info for other process)
|
75
|
+
args, # stubbing arguments
|
76
|
+
&block # any other more complex stubbing arguments
|
77
|
+
)
|
78
|
+
end
|
45
79
|
end
|
46
80
|
end
|
47
81
|
|
@@ -53,6 +87,11 @@ module CrossStub
|
|
53
87
|
|
54
88
|
end
|
55
89
|
|
56
|
-
Object.
|
57
|
-
|
58
|
-
|
90
|
+
Object.class_eval do
|
91
|
+
extend CrossStub::ClassMethods
|
92
|
+
include CrossStub::InstanceMethods
|
93
|
+
end
|
94
|
+
|
95
|
+
Module.class_eval do
|
96
|
+
include CrossStub::ClassMethods
|
97
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'cross-stub/arguments/hash'
|
2
|
+
require 'cross-stub/arguments/array'
|
3
|
+
require 'cross-stub/arguments/proc'
|
4
|
+
|
5
|
+
module CrossStub
|
6
|
+
module Arguments #:nodoc:
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def parse(args, &block)
|
10
|
+
(
|
11
|
+
case args[0]
|
12
|
+
when ::Hash then Hash.parse(args[0])
|
13
|
+
when ::Symbol then Array.parse(args)
|
14
|
+
else {}
|
15
|
+
end
|
16
|
+
).merge(block_given? ? Proc.parse(&block) : {})
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|