svenfuchs-stubby 0.0.1

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.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Sven Fuchs
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,242 @@
1
+ ## Stubby - lightweight and fast stubbing
2
+
3
+ Stubby is a lightweight and fast stubbing framework that was
4
+
5
+ * designed to help with the repetitive work of setting up stub scenarios for specifying/testing and
6
+ * optimized for speed and ease of use.
7
+
8
+ Stubby does not track references to stubbed methods on real objects (like
9
+ RSpec's `stub!` method does it which is used by `stub_model` & co).
10
+
11
+ Instead it dynamically creates a Ruby class for every stub object once. These
12
+ classes will then be instantiated for every specification (or test). This
13
+ circumvents the overhead of adding, tracking and removing lots of methods
14
+ and can - depending on your stubs and specs - result in significant speed
15
+ improvements.
16
+
17
+ ## Stubs setup
18
+
19
+ Stubby comes with a slick DSL to make it easy to describe stub setups. Basically
20
+ there are three main statements:
21
+
22
+ * `define Model &block` - defines a stub base class that camouflages as a model class
23
+ * `instance :key`, methods - defines a concrete stub that inherits from a stub base class and will be instantiated on request
24
+ * `scenario :key &block` - defines a code block that can be executed from your spec (e.g. in before :each blocks)
25
+
26
+ Things are pretty much readable and self-explaining:
27
+
28
+ define Site do
29
+ methods :save => true, :destroy => true, :active? => true
30
+ instance :homepage, :id => 1
31
+ instance :customer, :id => 2, :active? => false
32
+ end
33
+
34
+ This creates a stub base class that camouflages as your `Site` model (i.e. it
35
+ behaves as this model when methods like `class`, `is_a?`, `===` etc. are sent to it).
36
+
37
+ For the Site stub base class there are a couple of methods defined (stubbed)
38
+ that you can call in your specs and that will return the given values (like
39
+ `save` will return `true`).
40
+
41
+ It also defines two stub instances for this class that can be accessed through
42
+ the keys `:homepage` and `:customer`. These both respond to the `id` method. The
43
+ `:customer` instance additionally overwrites the `active?` value that's present
44
+ in the base class.
45
+
46
+ ## Access stubs
47
+
48
+ You can access these stubs from
49
+
50
+ * the stub base class definition block (like the one above)
51
+ * scenario blocks (see below)
52
+ * your specs
53
+
54
+ To access them you can use the following method apis. With the Site stub base
55
+ class above being defined:
56
+
57
+ stub_site # returns the first defined stub instance (i.e. :homepage)
58
+ stub_site(:homepage) # returns the :homepage stub instance
59
+ stub_sites # returns an array with all defined stub instances
60
+ stub_sites(:homepage) # returns an array containing the :homepage stub instance
61
+
62
+ You can also use the `lookup(:site, :homepage)` method in the same way in case you
63
+ need it.
64
+
65
+ These accessors can be used from within the stub base class definition like
66
+ this:
67
+
68
+ define Site do
69
+ instance :homepage, :next => stub_site(:customer)
70
+ instance :customer, :next => stub_site(:homepage)
71
+ end
72
+
73
+ This creates a method `next` on both instances which return the respective
74
+ stub instances.
75
+
76
+ For convenience you can pass arrays of method names as keys of method
77
+ hashs:
78
+
79
+ define Site do
80
+ methods [:save, :destroy, :active?] => true # same result as above
81
+ end
82
+
83
+ ## Stubbing ActiveRecord associations
84
+
85
+ To make stubbing of more complex ActiveRecord models easier there are also the
86
+ following helpers:
87
+
88
+ define Site do
89
+ has_many :sections, [:find, :build] => stub_section,
90
+ [:paginate] => stub_sections
91
+ end
92
+
93
+ This has the expected effects. With this definition in place the following
94
+ lines will now all return the same section stub object:
95
+
96
+ stub_site.sections.first
97
+ stub_site.sections.find
98
+ stub_site.sections.build
99
+ stub_site.sections.paginate.first
100
+
101
+ Watch the first of these lines! As you can see the sections `has_many_proxy`
102
+ will automatically be populated with the array stub_sections if you don't
103
+ specify anything explicitely. I.e. that's the same as:
104
+
105
+ define Site do
106
+ has_many :sections, stub_sections,
107
+ [:find, :build] => stub_section,
108
+ [:paginate] => stub_sections
109
+ end
110
+
111
+
112
+ If you want the sections array to return something else, though you can specify
113
+ it:
114
+
115
+ define Site do
116
+ has_many :sections, stub_sections(:root),
117
+ [:find, :build] => stub_section,
118
+ [:paginate] => stub_sections
119
+ end
120
+
121
+ There are also the `has_one` and `belongs_to` statements which also behave in
122
+ the expected way:
123
+
124
+ define Site do
125
+ has_one :api_key
126
+ belongs_to :user
127
+ end
128
+
129
+ The `belongs_to` statement implicitely defines the `user_id` method so that it
130
+ returns the id defined for the `stub_user` stub.
131
+
132
+ ## Scenarios
133
+
134
+ A scenario is simply a code block that can be accessed and executed from your
135
+ specs. You can define a scenario like this:
136
+
137
+ scenario :default do
138
+ @site = stub_site
139
+ Site.stub!(:find).and_return @site
140
+ end
141
+
142
+ You can then invoke this scenario from your Spec like so:
143
+
144
+ before :each do
145
+ scenario :default # pass as many scenario names as you like
146
+ end
147
+
148
+ Because the scenario code block is evaluated in the scope of the before :each
149
+ block the @site instance variable is then accessible from within your specs.
150
+
151
+ ## Installation and Setup
152
+
153
+ Install Stubby as a usual Rails plugin and add the following line somewhere
154
+ in your specs where it gets executed. The best place for this is probably
155
+ your `spec/spec_helper.rb` file:
156
+
157
+ Stubby::Loader.load
158
+
159
+ This requires the stubby code and loads the stub definition files once.
160
+
161
+ ## Stub definition files
162
+
163
+ Stubby assumes that you have on directory where you store your stub definition
164
+ files (and nothing else). A stub definition file is just a ruby file that contains
165
+ all or some of your stub base class and scenario definitions like described above.
166
+ You can structure your files as you like.
167
+
168
+ By default Stubby assumes that you
169
+
170
+ * use a directory named `stubs/`
171
+ * that is located beneath the directory of your `spec_helper.rb` file
172
+ * that starts requires the stubby code
173
+
174
+ Stubby then tries to guess the correct directory location. If it gets things
175
+ wrong or if you want to use a different setup you can specify the directory
176
+ where it looks for your stub definitions files like this:
177
+
178
+ Stubby.directory = 'path/to/stubs'
179
+
180
+ ## Limitations
181
+
182
+ Be aware that Stubby is a framework for plain stubs. That means that it doesn't
183
+ make a single attemp of looking at your model classes and trying to mimick
184
+ their behaviour. This is different from what other solutions do (which might
185
+ instantiate full functional model objects, like fixtures, or at least mimick
186
+ the model's attributes or similar).
187
+
188
+ Thus you need to define every single method that your specs call. Personally
189
+ I feel that this is an advantage because it makes visible *what* portions
190
+ of the code my specs actually run (and what they shouldn't). But your mileage
191
+ may vary.
192
+
193
+ Also, with Stubby it is currently not possible:
194
+
195
+ * define class methods in stub base classes. You still need to stub class methods manually. The scenario block is a good place to stub common methods though.
196
+ * stub methods in a way so that they return different results depending on a certain parameter passed. You can still use RSpec `stub!(:method).with(:param).and_return(result)` for that though.
197
+
198
+ If you want to help me with these, please let me know :)
199
+
200
+ Also, this code is brand new. Consider this an alpha release. I have written
201
+ this library to clean-up and speed-up the 1000+ specs of a project that I'm
202
+ currently working on. For me Stubby works quite well in this project's specs
203
+ and it runs these specs in less than 30 seconds (which is less then 50% of
204
+ what a solution with `mock_model` needed).
205
+
206
+ That doesn't mean that there aren't any major bugs or problems though. Please
207
+ send me your pull requests, patches or just bug requests if so.
208
+
209
+ ## Similar solutions
210
+
211
+ There are a couple of similar solutions that aim at the same problem but all
212
+ go down different routes. I highly recommend to have a look at:
213
+
214
+ * [Fixture Scenarios](http://code.google.com/p/fixture-scenarios/) by Tom Preston-Werner, extends Rails' native fixtures
215
+ * [Model Stubbing](http://ar-code.svn.engineyard.com/plugins/model_stubbing/README) by Rick Olson, creates in-memory versions of your models
216
+ * [Exemplar](http://www.bofh.org.uk/articles/2007/08/05/doing-the-fixture-thing) by Piers Cawley
217
+
218
+ ## The name
219
+
220
+ I was told that in Canada a "stubby" is a certain kind of beer bottle. The
221
+ [Wikipedia page](http://en.wikipedia.org/wiki/Beer_bottle) lists a couple of
222
+ advantages that perfectly match the reasons why I've written this library in
223
+ the first place:
224
+
225
+ * easier to handle
226
+ * chills faster
227
+ * less breakage
228
+ * lighter in weight
229
+ * less storage space
230
+ * lower center of gravity
231
+
232
+ Now, given that I've thought of this name before I knew this page I think
233
+ that's pretty funny and a perfect name for the library.
234
+
235
+ Also, I'm not a native English speaker but I've been informed on #rubyonrails
236
+ that "Stubby" might also have some sexual connotations in some parts of the
237
+ English speaking world.
238
+
239
+ ## Etc
240
+
241
+ Authors: [Sven Fuchs](http://www.artweb-design.de) <svenfuchs at artweb-design dot de>
242
+ License: MIT
@@ -0,0 +1,54 @@
1
+ require 'stubby/handle'
2
+ require 'stubby/instances'
3
+ require 'stubby/loader'
4
+
5
+ require 'stubby/base'
6
+ require 'stubby/definition'
7
+ require 'stubby/class_factory'
8
+ require 'stubby/has_many_proxy'
9
+
10
+ module Stubby
11
+ module Classes; end
12
+
13
+ mattr_accessor :directory
14
+ mattr_accessor :scenarios, :base_definitions, :instance_definitions
15
+ @@scenarios = {}
16
+ @@base_definitions = {}
17
+ @@instance_definitions = {}
18
+
19
+ class << self
20
+ def included(base)
21
+ base.after :each do Stubby::Instances.clear! end
22
+ end
23
+
24
+ def base_definition(name)
25
+ @@base_definitions[name]
26
+ end
27
+
28
+ def instance_definitions(name)
29
+ @@instance_definitions[name] ||= {}
30
+ end
31
+ end
32
+
33
+ def scenario(*names)
34
+ names.each do |name|
35
+ raise "scenario :#{name} is not defined" unless scenarios[name]
36
+ instance_eval &scenarios[name]
37
+ end
38
+ end
39
+
40
+ def lookup(key, *args)
41
+ Stubby::Instances.lookup(key.to_s, *args)
42
+ end
43
+
44
+ def method_missing(name, *args)
45
+ return lookup($1, *args) if name.to_s =~ /^stub_(.*)/
46
+ super
47
+ end
48
+ end
49
+
50
+ # trying to guess the stub definitions directory, overwrite this setting
51
+ # as needed in your spec_helper.rb
52
+ if filename = caller.detect{|line| line !~ /rubygems|dependencies/ }
53
+ Stubby.directory = File.expand_path File.dirname(filename) + "/stubs"
54
+ end
@@ -0,0 +1,38 @@
1
+ module Stubby
2
+ class Base
3
+ class << self
4
+ attr_accessor :original_class
5
+
6
+ def new
7
+ returning super do |object|
8
+ object.instance_variable_set :@original_class, original_class
9
+ end
10
+ end
11
+
12
+ def base_class
13
+ original_class.base_class
14
+ end
15
+ end
16
+
17
+ def to_param
18
+ id.to_s
19
+ end
20
+
21
+ def class
22
+ @original_class
23
+ end
24
+
25
+ def new_record?
26
+ id.nil?
27
+ end
28
+
29
+ def is_a?(klass)
30
+ @original_class.ancestors.include? klass
31
+ end
32
+ alias :kind_of? :is_a?
33
+
34
+ def instance_of?(klass)
35
+ @original_class == klass
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,76 @@
1
+ module Stubby
2
+ class ClassFactory # too bad, we can't extend ::Class
3
+ class << self
4
+ def create(base_class, *args, &block)
5
+ klass = new(base_class, *args, &block).klass
6
+ end
7
+ end
8
+
9
+ attr_reader :klass
10
+
11
+ def initialize(base_class, name, original_class, methods = {}, &block)
12
+ @klass = ::Class.new(base_class)
13
+ target = base_class == Stubby::Base ? Stubby::Classes : base_class
14
+ target.const_set name.sub('::', ''), @klass
15
+
16
+ @klass.original_class = original_class
17
+ define_methods methods
18
+ instance_eval &block if block
19
+ end
20
+
21
+ def has_many(names, *args)
22
+ methods = args.extract_options!
23
+ Array(names).each do |name|
24
+ values = args.last || lookup(name, :all)
25
+ as = methods.delete(:_as) || name
26
+ proxy = Handle.new{ HasManyProxy.new name, values, methods }
27
+ define_method (as || name), proxy
28
+ end
29
+ end
30
+
31
+ def has_one(names, object = nil)
32
+ Array(names).each do |name|
33
+ object ||= lookup(name, :first)
34
+ define_method name, object
35
+ end
36
+ end
37
+
38
+ def belongs_to(names, object = nil)
39
+ Array(names).each do |name|
40
+ object ||= lookup(name, :first)
41
+ define_method name, object
42
+ define_method :"#{name}_id", Handle.new{ object.resolve.id }
43
+ end
44
+ end
45
+
46
+ def instance(*args)
47
+ definition = Definition.new :base_class => @klass,
48
+ :methods => args.extract_options!,
49
+ :name => args.shift.to_s.camelize
50
+ definition.create!
51
+ end
52
+
53
+ def lookup(key, *args)
54
+ Handle.new{ Stubby::Instances.lookup(key.to_s, *args) }
55
+ end
56
+
57
+ def method_missing(name, *args)
58
+ return lookup($1, *args) if name.to_s =~ /^stub_(.*)/
59
+ super
60
+ end
61
+
62
+ def define_methods(methods)
63
+ methods.each do |names, value|
64
+ Array(names).each{|name| define_method name, value}
65
+ end
66
+ end
67
+ alias :methods :define_methods
68
+
69
+ def define_method(name, value)
70
+ @klass.send :define_method, name do |*args|
71
+ @__values ||= {}
72
+ @__values[name] ||= value.respond_to?(:resolve) ? value.resolve : value
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,78 @@
1
+ module Stubby
2
+ class Definition
3
+ attr_reader :name, :class, :original_class, :base_class, :methods
4
+ attr_accessor :default_instance_key
5
+
6
+ def initialize(attributes = {})
7
+ attributes.each{|name, value| instance_variable_set :"@#{name}", value }
8
+ register
9
+ end
10
+
11
+ def register
12
+ if base_class == Base
13
+ Stubby.base_definitions[key] = self
14
+ else
15
+ base_definition.default_instance_key ||= instance_key
16
+ Stubby.instance_definitions(base_key)[instance_key] = self
17
+ end
18
+ end
19
+
20
+ def name
21
+ @name ||= original_class.name if original_class
22
+ @name
23
+ end
24
+
25
+ def base_class
26
+ @base_class ||= Base
27
+ end
28
+
29
+ def base_key
30
+ @base_key ||= base_class.name.demodulize
31
+ end
32
+
33
+ def base_definition
34
+ @base_definition ||= Stubby.base_definitions[base_key]
35
+ end
36
+
37
+ def key
38
+ @key ||= name.to_s.classify.sub('::', '')
39
+ end
40
+
41
+ def instance_key
42
+ @instance_key ||= name.demodulize.underscore.to_sym
43
+ end
44
+
45
+ def original_class
46
+ @original_class ||= base_class.original_class if base_class
47
+ @original_class
48
+ end
49
+
50
+ def methods
51
+ @methods ||= {}
52
+ end
53
+
54
+ def create!(&block)
55
+ @class = ClassFactory.create(base_class, name, original_class, methods, &block)
56
+ end
57
+
58
+ def instantiate(key = nil)
59
+ if key == :all
60
+ instance_definitions.collect{|key, definition| definition.instantiate }
61
+ elsif key
62
+ key = default_instance_key if key == :first
63
+ instance_definition(key).instantiate
64
+ else
65
+ Instances.by_key(base_class)[instance_key] or
66
+ Instances.store(base_class, self.class.new, instance_key)
67
+ end
68
+ end
69
+
70
+ def instance_definitions
71
+ Stubby.instance_definitions(key)
72
+ end
73
+
74
+ def instance_definition(instance_key)
75
+ Stubby.instance_definitions(key)[instance_key]
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,17 @@
1
+ module Stubby
2
+ class Handle < Proc
3
+ def initialize(&block)
4
+ super &block
5
+ end
6
+
7
+ def resolve
8
+ result = call
9
+ result = result.resolve if result.respond_to? :resolve
10
+ result
11
+ end
12
+
13
+ def inspect
14
+ "<Stubby::Handle:#{__id__}>"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,37 @@
1
+ module Stubby
2
+ class HasManyProxy < Array
3
+ attr_accessor :name
4
+
5
+ def initialize(name, values, methods)
6
+ @name = name
7
+ @values = values
8
+ define_methods methods
9
+ end
10
+
11
+ def resolve
12
+ if @values.respond_to?(:resolve)
13
+ replace @values.resolve
14
+ else
15
+ replace @values.collect{|object| object.resolve if object.respond_to?(:resolve) }
16
+ end
17
+ end
18
+
19
+ def define_methods(methods)
20
+ methods.each do |names, value|
21
+ Array(names).each{|name| define_method name, value}
22
+ end
23
+ end
24
+
25
+ def define_method(name, value)
26
+ (class << self; self; end).send :define_method, name do |*args|
27
+ @__values ||= {}
28
+ @__values[name] ||= value.respond_to?(:resolve) ? value.resolve : value
29
+ end
30
+ end
31
+
32
+ def inspect
33
+ "<HasManyProxy:#{name.to_s.camelize}:#{object_id} #{super}>"
34
+ end
35
+ alias :to_s :inspect
36
+ end
37
+ end
@@ -0,0 +1,62 @@
1
+ module Stubby
2
+ module Instances
3
+ class InstanceNotFound < RuntimeError; end
4
+
5
+ class << self
6
+ def lookup(klass, key = nil)
7
+ if (singular = klass.singularize) != klass
8
+ klass = singular
9
+ method = :find_all
10
+ key ||= :all
11
+ else
12
+ method = :find_one
13
+ key ||= :first
14
+ end
15
+ method = :find_all if key == :all
16
+
17
+ definition = Stubby.base_definition(klass.classify) or raise "could not find base_definition for #{klass.classify}"
18
+ send(method, definition, key)
19
+ end
20
+
21
+ def find_all(definition, key)
22
+ key = nil if key == :all
23
+ if key
24
+ [find_one(definition, key)].compact
25
+ else
26
+ definition.instantiate(:all) unless complete[definition.class]
27
+ complete[definition.class] = true
28
+ by_class(definition.class)
29
+ end
30
+ end
31
+
32
+ def find_one(definition, key)
33
+ key = definition.default_instance_key if key == :first
34
+ by_key(definition.class)[key] || definition.instantiate(key)
35
+ end
36
+
37
+ def by_class(klass)
38
+ @by_class ||= {}
39
+ @by_class[klass] ||= []
40
+ end
41
+
42
+ def by_key(klass)
43
+ @by_key ||= {}
44
+ @by_key[klass] ||= {}
45
+ end
46
+
47
+ def complete
48
+ @complete ||= {}
49
+ end
50
+
51
+ def store(klass, object, key)
52
+ by_class(klass) << object
53
+ by_key(klass)[key] = object
54
+ end
55
+
56
+ def clear!
57
+ @by_class = {}
58
+ @by_key = {}
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,23 @@
1
+ module Stubby
2
+ module Loader
3
+ class << self
4
+ def scenario(name, &block)
5
+ Stubby.scenarios[name] = block
6
+ end
7
+
8
+ def define(original_class, &block)
9
+ definition = Definition.new :original_class => original_class
10
+ definition.create! &block
11
+ end
12
+
13
+ def load
14
+ unless @loaded
15
+ Dir["#{Stubby.directory}/*"].each do |filename|
16
+ instance_eval IO.read(filename), filename
17
+ end
18
+ end
19
+ @loaded = true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,102 @@
1
+ class Object
2
+ def returning(value)
3
+ yield(value)
4
+ value
5
+ end
6
+ end
7
+
8
+ class String
9
+ def singularize
10
+ sub /s$/, ''
11
+ end
12
+
13
+ def underscore
14
+ gsub(/::/, '/').
15
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
16
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
17
+ tr("-", "_").
18
+ downcase
19
+ end
20
+
21
+ def camelize
22
+ to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
23
+ end
24
+ alias :classify :camelize
25
+
26
+ def constantize
27
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
28
+ raise NameError, "#{inspect} is not a valid constant name!"
29
+ end
30
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
31
+ end
32
+
33
+ def demodulize
34
+ gsub(/^.*::/, '')
35
+ end
36
+ end
37
+
38
+ class Array
39
+ def extract_options!
40
+ last.is_a?(::Hash) ? pop : {}
41
+ end
42
+ end
43
+
44
+ class Hash
45
+ # Returns a new hash with only the given keys.
46
+ def slice(*keys)
47
+ allowed = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
48
+ reject { |key,| !allowed.include?(key) }
49
+ end
50
+
51
+ # Replaces the hash with only the given keys.
52
+ def slice!(*keys)
53
+ replace(slice(*keys))
54
+ end
55
+ end
56
+
57
+ class Module
58
+ def mattr_reader(*syms)
59
+ syms.each do |sym|
60
+ next if sym.is_a?(Hash)
61
+ class_eval(<<-EOS, __FILE__, __LINE__)
62
+ unless defined? @@#{sym}
63
+ @@#{sym} = nil
64
+ end
65
+
66
+ def self.#{sym}
67
+ @@#{sym}
68
+ end
69
+
70
+ def #{sym}
71
+ @@#{sym}
72
+ end
73
+ EOS
74
+ end
75
+ end
76
+
77
+ def mattr_writer(*syms)
78
+ options = syms.extract_options!
79
+ syms.each do |sym|
80
+ class_eval(<<-EOS, __FILE__, __LINE__)
81
+ unless defined? @@#{sym}
82
+ @@#{sym} = nil
83
+ end
84
+
85
+ def self.#{sym}=(obj)
86
+ @@#{sym} = obj
87
+ end
88
+
89
+ #{"
90
+ def #{sym}=(obj)
91
+ @@#{sym} = obj
92
+ end
93
+ " unless options[:instance_writer] == false }
94
+ EOS
95
+ end
96
+ end
97
+
98
+ def mattr_accessor(*syms)
99
+ mattr_reader(*syms)
100
+ mattr_writer(*syms)
101
+ end
102
+ end
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format progress
3
+ --loadby mtime
4
+ --reverse
@@ -0,0 +1,35 @@
1
+ require 'active_support'
2
+ # require File.dirname(__FILE__) + "/no_rails"
3
+ # require 'spec'
4
+
5
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__) + "/../lib")
6
+
7
+ require "stubby"
8
+
9
+ Stubby.directory = File.dirname(__FILE__) + "/stubs"
10
+
11
+ Spec::Runner.configure do |config|
12
+ # == Fixtures
13
+ #
14
+ # You can declare fixtures for each example_group like this:
15
+ # describe "...." do
16
+ # fixtures :table_a, :table_b
17
+ #
18
+ # Alternatively, if you prefer to declare them only once, you can
19
+ # do so right here. Just uncomment the next line and replace the fixture
20
+ # names with your fixtures.
21
+ #
22
+ # config.global_fixtures = :table_a, :table_b
23
+ #
24
+ # If you declare global fixtures, be aware that they will be declared
25
+ # for all of your examples, even those that don't use them.
26
+ #
27
+ # == Mock Framework
28
+ #
29
+ # RSpec uses it's own mocking framework by default. If you prefer to
30
+ # use mocha, flexmock or RR, uncomment the appropriate line:
31
+ #
32
+ # config.mock_with :mocha
33
+ # config.mock_with :flexmock
34
+ # config.mock_with :rr
35
+ end
@@ -0,0 +1,205 @@
1
+ require File.dirname(__FILE__) + "/spec_helper"
2
+
3
+ module ActiveRecord
4
+ class Base
5
+ attr_accessor :id
6
+ def has_attribute?(name)
7
+ false
8
+ end
9
+ def inspect
10
+ "<#{self.class.name}:#{object_id}>"
11
+ end
12
+ end
13
+ end
14
+
15
+ class Site < ActiveRecord::Base; end
16
+ class Section < ActiveRecord::Base; end
17
+ class User < ActiveRecord::Base; end
18
+ class ApiKey < ActiveRecord::Base; end
19
+
20
+ Stubby::Loader.load
21
+
22
+ describe "Stubby" do
23
+ include Stubby
24
+
25
+ before :each do
26
+ scenario :site
27
+ end
28
+
29
+ describe "base class creation" do
30
+ it "creates a class Stubby::Classes::Site" do
31
+ lambda{ Stubby::Classes::Site }.should_not raise_error
32
+ end
33
+
34
+ it "creates a class Stubby::Classes::Section" do
35
+ lambda{ Stubby::Classes::Section }.should_not raise_error
36
+ end
37
+
38
+ describe "an instance of the site base class" do
39
+ it "responds to :save" do
40
+ Stubby::Classes::Site.new.should respond_to(:save)
41
+ end
42
+
43
+ it "responds to :destroy" do
44
+ Stubby::Classes::Site.new.should respond_to(:destroy)
45
+ end
46
+
47
+ it "responds to :active?" do
48
+ Stubby::Classes::Site.new.should respond_to(:active?)
49
+ end
50
+
51
+ it "responds to :sections" do
52
+ Stubby::Classes::Site.new.should respond_to(:sections)
53
+ end
54
+
55
+ it "responds to :user" do
56
+ Stubby::Classes::Site.new.should respond_to(:user)
57
+ end
58
+
59
+ it "responds to :api_key" do
60
+ Stubby::Classes::Site.new.should respond_to(:api_key)
61
+ end
62
+ end
63
+ end
64
+
65
+ describe "instance class creation" do
66
+ it "creates a class Stubby::Classes::Site::Site" do
67
+ lambda{ Stubby::Classes::Site::Site }.should_not raise_error
68
+ end
69
+
70
+ it "creates a class Stubby::Classes::Section::Root" do
71
+ lambda{ Stubby::Classes::Section::Root }.should_not raise_error
72
+ end
73
+
74
+ describe "an instance of the site instance class" do
75
+ it "responds to :save (as inherited from its base class)" do
76
+ Stubby::Classes::Site::Site.new.should respond_to(:save)
77
+ end
78
+
79
+ it "responds to :name" do
80
+ Stubby::Classes::Site::Site.new.should respond_to(:name)
81
+ end
82
+ end
83
+ end
84
+
85
+ describe "stub lookup" do
86
+ describe "with a singular lookup method" do
87
+ it "returns the first defined stub if no key is given" do
88
+ stub_site.name.should == 'site'
89
+ end
90
+
91
+ it "returns the stub referenced by a given key" do
92
+ stub_site(:another).name.should == 'another'
93
+ end
94
+
95
+ it "returns a collection of all stubs when :all is given as a key" do
96
+ stub_site(:all).should == [stub_site, stub_site(:another)]
97
+ end
98
+ end
99
+
100
+ describe "with a plural lookup method" do
101
+ it "returns an array containing all stubs when no key is given" do
102
+ sites = stub_sites
103
+ sites.size.should == 2
104
+ sites.should == [stub_site, stub_site(:another)]
105
+ end
106
+
107
+ it "returns an array containing all stubs when :all is given as a key" do
108
+ sites = stub_sites(:all)
109
+ sites.size.should == 2
110
+ sites.should == [stub_site, stub_site(:another)]
111
+ end
112
+
113
+ it "returns an array containing the referenced stub when a key is given" do
114
+ sites = stub_sites(:another)
115
+ sites.size.should == 1
116
+ sites.should == [stub_site(:another)]
117
+ end
118
+
119
+ it "returns an array containing all stubs even when a single stub has been looked up before" do
120
+ stub_site
121
+ sites = stub_sites(:all)
122
+ sites.size.should == 2
123
+ sites.should == [stub_site, stub_site(:another)]
124
+ end
125
+ end
126
+
127
+ end
128
+
129
+ describe "a has_many_proxy" do
130
+ it "responds to :find" do
131
+ @site.sections.respond_to?(:find).should be_true
132
+ end
133
+
134
+ it "its find method returns an array of stub instance masquerading as a Site instance" do
135
+ @site.sections.find.should == stub_section
136
+ end
137
+
138
+ it "its target is an array of stub instances masquerading as a Site instance" do
139
+ @site.sections.should == stub_sections
140
+ end
141
+
142
+ it "works with rspec mock expectations" do
143
+ @site.sections.should_receive(:foo)
144
+ @site.sections.foo
145
+ end
146
+ end
147
+
148
+ describe "a method on a stub" do
149
+ before :each do
150
+ @another = stub_site(:another)
151
+ end
152
+
153
+ it "returns the same stub during one spec" do
154
+ @site.next.object_id.should == @another.object_id
155
+ @site.next.object_id.should == @another.object_id
156
+ end
157
+
158
+ previous_object_id = nil
159
+ 1.upto(2) do # what's a less brittle way to spec something like this?
160
+ it "returns a new stub for each spec" do
161
+ previous_object_id.should_not == @site.next.object_id
162
+ previous_object_id = @site.next.object_id
163
+ end
164
+ end
165
+ end
166
+
167
+ describe "a method on a HasManyProxy" do
168
+ before :each do
169
+ @section = stub_section
170
+ end
171
+
172
+ it "returns the same stub during one spec" do
173
+ @site.sections.find.object_id.should == @section.object_id
174
+ @site.sections.find.object_id.should == @section.object_id
175
+ end
176
+
177
+ previous_object_id = nil
178
+ 1.upto(2) do # what's a less brittle way to spec something like this?
179
+ it "returns a new stub for each spec" do
180
+ previous_object_id.should_not == @site.sections.find.object_id
181
+ previous_object_id = @site.sections.find.object_id
182
+ end
183
+ end
184
+ end
185
+
186
+ describe "a HasManyProxy's content" do
187
+ before :each do
188
+ @section = stub_section
189
+ end
190
+
191
+ it "returns the same stub during one spec" do
192
+ @site.sections.first.object_id.should == @section.object_id
193
+ @site.sections.first.object_id.should == @section.object_id
194
+ end
195
+
196
+ previous_object_id = nil
197
+ 1.upto(2) do # what's a less brittle way to spec something like this?
198
+ it "returns a new stub for each spec" do
199
+ previous_object_id.should_not == @site.sections.first.object_id
200
+ previous_object_id = @site.sections.first.object_id
201
+ end
202
+ end
203
+ end
204
+
205
+ end
@@ -0,0 +1,34 @@
1
+ define Section do
2
+ instance :root
3
+ end
4
+
5
+ define User do
6
+ instance :admin
7
+ end
8
+
9
+ define ApiKey do
10
+ instance :default
11
+ end
12
+
13
+ define Site do
14
+ has_many :sections, [:find, :build, :root] => stub_section,
15
+ [:paginate] => stub_sections
16
+ belongs_to :user
17
+ has_one :api_key
18
+
19
+ methods [:save, :destroy] => true,
20
+ :next => stub_site(:another),
21
+ :active? => true
22
+
23
+ instance :site,
24
+ :id => 1,
25
+ :name => 'site'
26
+
27
+ instance :another,
28
+ :id => 2,
29
+ :name => 'another'
30
+ end
31
+
32
+ scenario :site do
33
+ @site = stub_site(:site)
34
+ end
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "stubby"
3
+ s.version = "0.0.1"
4
+ s.date = "2008-05-29"
5
+ s.summary = "Lightweight and fast stubbing framework"
6
+ s.email = "svenfuchs@artweb-design.de"
7
+ s.homepage = "http://github.com/svenfuchs/stubby"
8
+ s.description = "Stubby is a lightweight and fast stubbing framework that was designed to help with the repetitive work of setting up stub scenarios for specifying/testing and optimized for speed and ease of use."
9
+ s.has_rdoc = false
10
+ s.authors = ["Sven Fuchs"]
11
+ s.files = ["lib/stubby/base.rb", "lib/stubby/class_factory.rb", "lib/stubby/definition.rb", "lib/stubby/handle.rb", "lib/stubby/has_many_proxy.rb", "lib/stubby/instances.rb", "lib/stubby/loader.rb", "lib/stubby.rb", "MIT-LICENSE", "README.markdown", "spec/no_rails.rb", "spec/spec.opts", "spec/spec_helper.rb", "spec/stubby_spec.rb", "spec/stubs", "spec/stubs/site.rb", "stubby.gemspec"]
12
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: svenfuchs-stubby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Sven Fuchs
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-05-29 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Stubby is a lightweight and fast stubbing framework that was designed to help with the repetitive work of setting up stub scenarios for specifying/testing and optimized for speed and ease of use.
17
+ email: svenfuchs@artweb-design.de
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/stubby/base.rb
26
+ - lib/stubby/class_factory.rb
27
+ - lib/stubby/definition.rb
28
+ - lib/stubby/handle.rb
29
+ - lib/stubby/has_many_proxy.rb
30
+ - lib/stubby/instances.rb
31
+ - lib/stubby/loader.rb
32
+ - lib/stubby.rb
33
+ - MIT-LICENSE
34
+ - README.markdown
35
+ - spec/no_rails.rb
36
+ - spec/spec.opts
37
+ - spec/spec_helper.rb
38
+ - spec/stubby_spec.rb
39
+ - spec/stubs
40
+ - spec/stubs/site.rb
41
+ - stubby.gemspec
42
+ has_rdoc: false
43
+ homepage: http://github.com/svenfuchs/stubby
44
+ post_install_message:
45
+ rdoc_options: []
46
+
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.0.1
65
+ signing_key:
66
+ specification_version: 2
67
+ summary: Lightweight and fast stubbing framework
68
+ test_files: []
69
+