gimme 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.bundle/config +2 -0
- data/.document +5 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +38 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +219 -0
- data/README.rdoc +22 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/features/argument_captors.feature +12 -0
- data/features/gimme_next.feature +13 -0
- data/features/step_definitions/gimme_steps.rb +118 -0
- data/features/stub_basic.feature +34 -0
- data/features/stub_matchers.feature +60 -0
- data/features/stub_sensible_defaults.feature +10 -0
- data/features/support/animals.rb +55 -0
- data/features/support/env.rb +4 -0
- data/features/unknown_methods.feature +39 -0
- data/features/verify_matcher_anything.feature +21 -0
- data/features/verify_no_args.feature +18 -0
- data/features/verify_with_args.feature +21 -0
- data/gimme.gemspec +86 -0
- data/lib/gimme/captor.rb +24 -0
- data/lib/gimme/errors.rb +6 -0
- data/lib/gimme/gives.rb +27 -0
- data/lib/gimme/matchers.rb +58 -0
- data/lib/gimme/method_resolver.rb +23 -0
- data/lib/gimme/test_double.rb +63 -0
- data/lib/gimme/verifies.rb +44 -0
- data/lib/gimme-double.rb +1 -0
- data/lib/gimme.rb +10 -0
- metadata +194 -0
data/.bundle/config
ADDED
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
|
4
|
+
# Add dependencies to develop your gem here.
|
5
|
+
# Include everything needed to run rake, tests, features, etc.
|
6
|
+
group :development do
|
7
|
+
gem "bundler", "~> 1.0.0"
|
8
|
+
gem "jeweler", "~> 1.5.2"
|
9
|
+
gem "rspec", ">= 1.3.1"
|
10
|
+
gem "cucumber", ">= 0.10.0"
|
11
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
builder (3.0.0)
|
5
|
+
cucumber (0.10.0)
|
6
|
+
builder (>= 2.1.2)
|
7
|
+
diff-lcs (~> 1.1.2)
|
8
|
+
gherkin (~> 2.3.2)
|
9
|
+
json (~> 1.4.6)
|
10
|
+
term-ansicolor (~> 1.0.5)
|
11
|
+
diff-lcs (1.1.2)
|
12
|
+
gherkin (2.3.3)
|
13
|
+
json (~> 1.4.6)
|
14
|
+
git (1.2.5)
|
15
|
+
jeweler (1.5.2)
|
16
|
+
bundler (~> 1.0.0)
|
17
|
+
git (>= 1.2.5)
|
18
|
+
rake
|
19
|
+
json (1.4.6)
|
20
|
+
rake (0.8.7)
|
21
|
+
rspec (2.4.0)
|
22
|
+
rspec-core (~> 2.4.0)
|
23
|
+
rspec-expectations (~> 2.4.0)
|
24
|
+
rspec-mocks (~> 2.4.0)
|
25
|
+
rspec-core (2.4.0)
|
26
|
+
rspec-expectations (2.4.0)
|
27
|
+
diff-lcs (~> 1.1.2)
|
28
|
+
rspec-mocks (2.4.0)
|
29
|
+
term-ansicolor (1.0.5)
|
30
|
+
|
31
|
+
PLATFORMS
|
32
|
+
ruby
|
33
|
+
|
34
|
+
DEPENDENCIES
|
35
|
+
bundler (~> 1.0.0)
|
36
|
+
cucumber (>= 0.10.0)
|
37
|
+
jeweler (~> 1.5.2)
|
38
|
+
rspec (>= 1.3.1)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Justin Searls
|
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.
|
data/README.markdown
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
# Gimme
|
2
|
+
|
3
|
+
Gimme is a very lightweight test double library for ruby. Written to be an opinionated (but not noisy) means to facilitate test-driving by enabling the author to specifying only what matters. But if could only feed Google one thing at this point, it would be: "[Mockito](http://mockito.org/) for Ruby"
|
4
|
+
|
5
|
+
You can read the (possibly stale) documentation below or the (fresh) [gimme Cucumber features on Relish](http://relishapp.com/searls/gimme)
|
6
|
+
|
7
|
+
## Basics (or "What does it Gimme?" ... har.)
|
8
|
+
|
9
|
+
Gimme was originally named (well, for the first five hours of its life) "[Tabula Rasa](http://en.wikipedia.org/wiki/Tabula_rasa)," to very clearly indicate that it generates blank slate test doubles that lack any initial coupling with the concepts associated with specific [test double](http://xunitpatterns.com/Test%20Double.html) subtypes like mocks/stubs/fakes/spies/proxies. But in the end, "gimme" was easier to type than "tabula rasa", and I generally wanted to avoid test pattern lingo from leaking into the context and language of everybody's tests (hence no method named "stub").
|
10
|
+
|
11
|
+
Gimme doubles are most accurately identified as [test spies](http://xunitpatterns.com/Test%20Spy.html) in [this table discriminating the types](http://xunitpatterns.com/Mocks,%20Fakes,%20Stubs%20and%20Dummies.html) over at Gerard Meszaros' helpful xUnit patterns repository.
|
12
|
+
|
13
|
+
Gimme aims to enable you to write low-friction, low-specification tests that feel a little more like [Mockito](http://mockito.org/) than existing ruby test double libraries. Gimme should do whatever it can to help you isolate your SUT from its dependencies and then get out of your way. And if gimme can provide some fast-feedback about potential problems, it should try to do that too.
|
14
|
+
|
15
|
+
The few things it gives you:
|
16
|
+
|
17
|
+
* Many typical test double library features, like: stubbing & verifying methods, argument matchers for determining what gets stubbed and what to verify, and argument captors for inspecting stuff your [SUT](http://xunitpatterns.com/SUT.html) passes its dependencies
|
18
|
+
* Natural arrange-act-assert flow — meaning that you can call `verify` after you've interacted with your system under test.
|
19
|
+
* No stringy/symbolic representations of methods — similar to [rr](https://github.com/btakita/rr), gimme uses the blank slate pattern and `method_missing` to allow for minimally terse stubs and verifications
|
20
|
+
* Sometimes you know the class of a dependency of your SUT; when you do, gimme can try to help out by raising a NoMethodError when you attempt to stub or verify a method that the class doesn't respond to.
|
21
|
+
* Gimme won't punish you for not setting up an expectation for every interaction your SUT has with your test double, leaving you to verify exactly what matters to you in the context of what you're building; sometimes specifying the behavior of your SUT on a collaborator is significant, and sometimes it isn't.
|
22
|
+
|
23
|
+
|
24
|
+
## The Disclaimer
|
25
|
+
|
26
|
+
Gimme is still in early development and a little light on features / hardening. While gimme should be enough to get started writing tests/specs, you'll likely run into edge cases that haven't been handled yet. If you're willing to try out gimme on your next toy project and either submit issues or pull requests when you run into issues, hopefully we can work together to make gimme a first-class test double framework in the Ruby community.
|
27
|
+
|
28
|
+
## Getting started
|
29
|
+
|
30
|
+
### Setting up
|
31
|
+
First, install the gem:
|
32
|
+
|
33
|
+
gem install gimme
|
34
|
+
|
35
|
+
Next, wherever you set up your test environment, require gimme:
|
36
|
+
|
37
|
+
require 'gimme'
|
38
|
+
|
39
|
+
### Creating a double
|
40
|
+
Once you're in your test or spec, to create a test double.
|
41
|
+
|
42
|
+
If you know what what class your SUT will be depending on, you can specify it:
|
43
|
+
|
44
|
+
double = gimme(Object)
|
45
|
+
|
46
|
+
Or you could just create a generic double can stub/verify any method you need:
|
47
|
+
|
48
|
+
double = gimme()
|
49
|
+
|
50
|
+
### Stubbing
|
51
|
+
|
52
|
+
Once you have your double, you can stub methods:
|
53
|
+
|
54
|
+
give(double).to_s { 'Pants' }
|
55
|
+
double.to_s #=> 'Pants'
|
56
|
+
|
57
|
+
give(double).equal?(:ninja) { true }
|
58
|
+
give(double).equal?(:fruit) { false }
|
59
|
+
double.equal?(:ninja) #=> true
|
60
|
+
|
61
|
+
You can also stub your double to raise an exception (or really, do anything in the passed block):
|
62
|
+
|
63
|
+
dog = gimme(Dog)
|
64
|
+
give(dog).holler_at(:mail_man) { raise LeashLawError }
|
65
|
+
|
66
|
+
dog.holler_at(:mail_man) # raises LeashLawError
|
67
|
+
|
68
|
+
### Verifying
|
69
|
+
|
70
|
+
You can also verify interactions with your double
|
71
|
+
|
72
|
+
double.equal?(:fruit)
|
73
|
+
|
74
|
+
verify(double).equal?(:fruit) # passes verification (read: does nothing)
|
75
|
+
verify(double).equal?(:what_the) # fails verification (raises a Gimme::VerifyFailedError)
|
76
|
+
|
77
|
+
You can also specify how many times a specific invocation should have occurred (defaults to 1):
|
78
|
+
|
79
|
+
double.equal?(:fruit)
|
80
|
+
double.equal?(:fruit)
|
81
|
+
|
82
|
+
verify(double,2).equal?(:fruit)
|
83
|
+
|
84
|
+
### Using Argument Matchers
|
85
|
+
|
86
|
+
Gimme includes several argument matchers which can be used to control which invocations will satisfy a particular stubbing or verification.
|
87
|
+
|
88
|
+
**anything**
|
89
|
+
|
90
|
+
Replacing an argument with `anything` will instantiate a `Gimme::Matchers::Anything` matcher, which always returns true, regardless of what gets passed in.
|
91
|
+
|
92
|
+
give(dog).walk_to(anything,5) { 'Park' }
|
93
|
+
|
94
|
+
walk_to(3,5) #=> 'Park'
|
95
|
+
walk_to('pants',5) #=> 'Park'
|
96
|
+
walk_to(nil,5) #=> 'Park'
|
97
|
+
walk_to(3,5.1) #=> nil
|
98
|
+
|
99
|
+
Matchers can be used when both stubbing and verifying a method. To verify on anything, you could:
|
100
|
+
|
101
|
+
dog.holler_at(true)
|
102
|
+
|
103
|
+
verify(dog).holler_at(anything) #=> passes verification
|
104
|
+
|
105
|
+
Other matchers:
|
106
|
+
|
107
|
+
**is_a(class)** — matches any arguments that are `kind_of?` the provided class
|
108
|
+
**any(class)** — same as `is_a`, but also matches nil
|
109
|
+
**boolean** — matches true or false arguments
|
110
|
+
**numeric** — matches numeric arguments
|
111
|
+
|
112
|
+
See the [cucumber feature for examples using these matchers](http://relishapp.com/searls/gimme/stubbing-with-matchers)
|
113
|
+
|
114
|
+
#### Writing Custom Argument Matchers
|
115
|
+
|
116
|
+
It's pretty easy to roll your own argument matchers as well. All you really need to do is pass as an argument to a method stubbed by `give` or verified by `verify` an object
|
117
|
+
that can respond to `matches?(arg)`. Maybe something like this would work (even though it'd be of questionable utility):
|
118
|
+
|
119
|
+
class Nothing
|
120
|
+
def matches?(arg)
|
121
|
+
false
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
give(dog).introduce_to(Nothing.new) { :meow } #b/c Nothing.matches? always returns false, :meow will never returned by the double.
|
126
|
+
|
127
|
+
|
128
|
+
### Using Argument Captors
|
129
|
+
|
130
|
+
An instance of an argument `Captor`, when paired with the `capture` matcher, is a valuable way for your test to get at the values that your SUT passes to its collaborators. Often, classes are responsible for building objects to be ingested by their collaborators but for which normal state verification would either be difficult or nonsensical. Argument captors should only be necessary sparingly for most types of applications, but they're a handy tool to have in the toolbox.
|
131
|
+
|
132
|
+
In cases like these, a captor can be used to "capture" the real argument value that the system under test passes its collaborator. This pseudocode provides an example:
|
133
|
+
|
134
|
+
#arrange
|
135
|
+
searches_system = gimme(SearchesSystem)
|
136
|
+
sut = QueryExecutor.new(searches_sytem)
|
137
|
+
query_captor = Captor.new
|
138
|
+
|
139
|
+
#act
|
140
|
+
sut.submit_query_for_string("find dogs")
|
141
|
+
|
142
|
+
#assert
|
143
|
+
verify(searches_system).execute(capture(query_captor))
|
144
|
+
query_captor.value.table_name.should == "Dogs"
|
145
|
+
|
146
|
+
|
147
|
+
### Suppressing NoMethodError
|
148
|
+
|
149
|
+
You may be reading this section because you got this message:
|
150
|
+
|
151
|
+
The Test Double of <Class Name> may not know how to respond to the '<Method Name>' method.
|
152
|
+
If you're confident that a real Kernel will know how to respond to '<Method Name>', then you can
|
153
|
+
invoke give! or verify! to suppress this error.
|
154
|
+
|
155
|
+
Whenever you stub or verify a method against a test double on a class, gimme will first verify that the method can be found on the class being
|
156
|
+
doubled. Since the vast majority of methods can be verified in this way, this default behavior is designed to provide fast failure.
|
157
|
+
This can be really handy, whether the cause is as simple as a transcription error of a method name from irb or as convoluted as an incorrect version of a dependency that lacks the method you expected.
|
158
|
+
|
159
|
+
However, because classes can be reopened and edited at runtime, often you'll outsmart gimme by knowing that a particular
|
160
|
+
method *will* be available on the class being doubled, even though it isn't *right now*.
|
161
|
+
|
162
|
+
For these situations, you could either (1) declare your double without a class argument (e.g. `gimme()` instead of `gimme(Dog)`), or (2) use `give!` and `verify!` to suppress the check that triggers the NoMethodError from being raised.
|
163
|
+
|
164
|
+
Here's an example where our Dog is again being doubled to facilitate some test, and even though the Dog class lacks a public `meow()` method, we happen to know that at runtime, the newest version of the `bananimals` gem will reopen Dog and add `meow()` to it.
|
165
|
+
|
166
|
+
dog = gimme(Dog)
|
167
|
+
give!(dog).meow { :purr }
|
168
|
+
|
169
|
+
dog.meow #=> :purr
|
170
|
+
|
171
|
+
We cam accomplish the same thing using `verify!`:
|
172
|
+
|
173
|
+
dog = gimme(Dog)
|
174
|
+
|
175
|
+
dog.meow
|
176
|
+
|
177
|
+
verify!(dog).meow #=> verification passes, even though gimme can't see the meow method.
|
178
|
+
|
179
|
+
### gimme_next
|
180
|
+
|
181
|
+
To my knowledge, there isn't an established pattern or name for this next feature. Sometimes you may want your SUT to instantiate its own dependency. However, if you also want to achieve isolation from the implementation details of that dependency, you can use `gimme_next`.
|
182
|
+
|
183
|
+
Take this example method from the RSpec book:
|
184
|
+
|
185
|
+
def guess(guess)
|
186
|
+
marker = Marker.new(@secret,guess)
|
187
|
+
@output.puts '+'*marker.exact_match_count + '-'*marker.number_match_count
|
188
|
+
end
|
189
|
+
|
190
|
+
This can be tested with gimme in isolation (meaning that a real Marker object is never instantiated or invoked) like so:
|
191
|
+
|
192
|
+
describe '#guess' do
|
193
|
+
let(:marker) { gimme_next(Marker) }
|
194
|
+
before do
|
195
|
+
give(marker).exact_match_count { 4 }
|
196
|
+
give(marker).number_match_count { 0 }
|
197
|
+
|
198
|
+
game.guess('1234')
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'instantiates a marker with the secret and guess' do
|
202
|
+
verify!(marker).initialize('1234','1234')
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'outputs the exact matches followed by the number matches' do
|
206
|
+
verify(output).puts('++++')
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
As you can see above, `gimme_next(Marker)` will create a double just like `gimme()` would have, but it will also temporarily redefine the passed class's `new` method such that the next instantiation of that class (presumably by the SUT) will return the same double.*
|
211
|
+
|
212
|
+
This way we can clearly specify the SUT's interaction with the Marker class while maintaining its isolation.
|
213
|
+
|
214
|
+
*Subsequent instantiations of the passed class will continue to return normal instances.
|
215
|
+
|
216
|
+
## About
|
217
|
+
|
218
|
+
### Maintainers
|
219
|
+
* [Justin Searls](http://about.emw/searls), [Pillar Technology](http://pillartechnology.com)
|
data/README.rdoc
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
= gimme
|
2
|
+
|
3
|
+
gimme is a lightweight test double library for ruby
|
4
|
+
|
5
|
+
Project site: http://github.com/searls/gimme
|
6
|
+
Gimme features: http://relishapp.com/searls/gimme
|
7
|
+
|
8
|
+
== Contributing to gimme
|
9
|
+
|
10
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
11
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
12
|
+
* Fork the project
|
13
|
+
* Start a feature/bugfix branch
|
14
|
+
* Commit and push until you are happy with your contribution
|
15
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
16
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
17
|
+
|
18
|
+
== Copyright
|
19
|
+
|
20
|
+
Copyright (c) 2010 Justin Searls. See LICENSE.txt for
|
21
|
+
further details.
|
22
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "gimme"
|
16
|
+
gem.homepage = "http://github.com/searls/gimme"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{gimme — a low-specification test double library for Ruby}
|
19
|
+
gem.description = %Q{gimme attempts to bring to Ruby a test double workflow akin to Mockito in Java. Major distinctions include preserving arrange-act-assert in tests, fast feedback for methods the double's real counterpart may not know how to respond to, no string/symbolic representations of methods, argument captors, and strong opinions (weakly held). }
|
20
|
+
gem.email = "searls@gmail.com"
|
21
|
+
gem.authors = ["Justin Searls"]
|
22
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
23
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
24
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
25
|
+
gem.add_development_dependency "rspec", ">= 1.3.1"
|
26
|
+
gem.add_development_dependency "cucumber", ">= 0.10.0"
|
27
|
+
end
|
28
|
+
Jeweler::RubygemsDotOrgTasks.new
|
29
|
+
|
30
|
+
require 'cucumber/rake/task'
|
31
|
+
Cucumber::Rake::Task.new do |t|
|
32
|
+
t.cucumber_opts = %w{--format pretty}
|
33
|
+
end
|
34
|
+
|
35
|
+
task :default => :cucumber
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "gimme #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.6
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Feature: Capturing Arguments
|
2
|
+
|
3
|
+
As a test author
|
4
|
+
I want to capture the value of an argument passed to my test double
|
5
|
+
So that I can assert things about arguments my system under test passes to its collaborators
|
6
|
+
|
7
|
+
Scenario: capturing an argument
|
8
|
+
Given a new Dog test double
|
9
|
+
And a new argument captor
|
10
|
+
When I invoke holler_at(:panda)
|
11
|
+
Then I can verify holler_at(capture(@captor)) has been invoked 1 time
|
12
|
+
And the captor's value is :panda
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Feature: Gimme Next
|
2
|
+
|
3
|
+
As a test author
|
4
|
+
I want a test double to stand-in for the next new'ing of a given class
|
5
|
+
so that I can isolate my SUT and stub & verify behavior of collaborators instantiated by the SUT
|
6
|
+
|
7
|
+
Scenario:
|
8
|
+
Given I create a double via gimme_next(Turtle)
|
9
|
+
When my SUT tries creating a real Turtle.new(:shell)
|
10
|
+
And I invoke swim
|
11
|
+
Then both the double and real object reference the same object
|
12
|
+
And I can verify! initialize(:shell) has been invoked 1 time
|
13
|
+
And I can verify swim has been invoked 1 time
|
@@ -0,0 +1,118 @@
|
|
1
|
+
include Gimme
|
2
|
+
METHOD_PATTERN = /([^\(]*)(\(.*\))?/
|
3
|
+
|
4
|
+
# Creating
|
5
|
+
|
6
|
+
Given /^a new (.*)\s?test double$/ do | type |
|
7
|
+
@double = type.empty? ? gimme : gimme(eval(type))
|
8
|
+
end
|
9
|
+
|
10
|
+
Given /^I create a double via gimme_next\((.*)\)$/ do |klass|
|
11
|
+
@double = gimme_next(eval(klass))
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
# Stubbing
|
16
|
+
|
17
|
+
When /^I stub #{METHOD_PATTERN} to return (.*)$/ do |method,args,result|
|
18
|
+
send_and_trap_error(NoMethodError,give(@double),method,args,result)
|
19
|
+
end
|
20
|
+
|
21
|
+
When /^I stub! #{METHOD_PATTERN} to return (.*)$/ do |method,args,result|
|
22
|
+
send_and_trap_error(NoMethodError,give!(@double),method,args,result)
|
23
|
+
end
|
24
|
+
|
25
|
+
When /^I stub #{METHOD_PATTERN} to raise (.*)$/ do |method,args,error_type|
|
26
|
+
sendish(give(@double),method,args,"raise #{error_type}")
|
27
|
+
end
|
28
|
+
|
29
|
+
# Invoking
|
30
|
+
|
31
|
+
Then /^invoking #{METHOD_PATTERN} returns (.*)$/ do |method,args,result|
|
32
|
+
sendish(@double,method,args).should == eval(result)
|
33
|
+
end
|
34
|
+
|
35
|
+
When /^I invoke #{METHOD_PATTERN}$/ do |method,args|
|
36
|
+
sendish(@double,method,args)
|
37
|
+
end
|
38
|
+
|
39
|
+
Given /^I do not invoke #{METHOD_PATTERN}$/ do |method,args|
|
40
|
+
end
|
41
|
+
|
42
|
+
Then /^invoking (.*) raises a (.*)$/ do |method,error_type|
|
43
|
+
expect_error(eval(error_type)) { sendish(@double,method) }
|
44
|
+
end
|
45
|
+
|
46
|
+
# Verifying
|
47
|
+
|
48
|
+
Then /^verifying #{METHOD_PATTERN} raises a (.*)$/ do |method,args,error_type|
|
49
|
+
expect_error(eval(error_type)) { verify(@double).send(method.to_sym) }
|
50
|
+
end
|
51
|
+
|
52
|
+
Then /^I can verify #{METHOD_PATTERN} has been invoked$/ do |method,args|
|
53
|
+
sendish(verify(@double),method,args)
|
54
|
+
end
|
55
|
+
|
56
|
+
Then /^I can verify #{METHOD_PATTERN} has been invoked (\d+) times?$/ do |method,args,times|
|
57
|
+
sendish(verify(@double,times.to_i),method,args)
|
58
|
+
end
|
59
|
+
|
60
|
+
Then /^I can verify! #{METHOD_PATTERN} has been invoked (\d+) times?$/ do |method,args,times|
|
61
|
+
sendish(verify!(@double,times.to_i),method,args)
|
62
|
+
end
|
63
|
+
|
64
|
+
#Captors
|
65
|
+
|
66
|
+
Given /^a new argument captor$/ do
|
67
|
+
@captor = Captor.new
|
68
|
+
end
|
69
|
+
|
70
|
+
Then /^the captor's value is (.*)$/ do |value|
|
71
|
+
@captor.value.should == eval(value)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Exceptions
|
75
|
+
Then /^a (.*) is raised$/ do |error_type|
|
76
|
+
@error.should be_a_kind_of eval(error_type)
|
77
|
+
@error = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
Then /^no error is raised$/ do
|
81
|
+
@error.should be nil
|
82
|
+
end
|
83
|
+
|
84
|
+
# Gimme Next
|
85
|
+
|
86
|
+
When /^my SUT tries creating a real (.*)$/ do |instantiation|
|
87
|
+
@real = eval(instantiation)
|
88
|
+
end
|
89
|
+
|
90
|
+
Then /^both the double and real object reference the same object$/ do
|
91
|
+
@real.__id__ == @double.__id__
|
92
|
+
end
|
93
|
+
|
94
|
+
# private
|
95
|
+
|
96
|
+
def send_and_trap_error(error_type,target,method,args=nil,result=nil)
|
97
|
+
begin
|
98
|
+
sendish(target,method,args,result)
|
99
|
+
rescue error_type => e
|
100
|
+
@error = e
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def sendish(target,method,args=nil,result=nil)
|
105
|
+
s = "target.#{method}#{args}"
|
106
|
+
s += "{ #{result} }" if result
|
107
|
+
eval(s)
|
108
|
+
end
|
109
|
+
|
110
|
+
def expect_error(type,&block)
|
111
|
+
rescued = false
|
112
|
+
begin
|
113
|
+
yield
|
114
|
+
rescue type
|
115
|
+
rescued = true
|
116
|
+
end
|
117
|
+
rescued.should be true
|
118
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
Feature: basic stubbing
|
2
|
+
|
3
|
+
As a test author
|
4
|
+
I want to create a test double
|
5
|
+
so that I can stub method returns
|
6
|
+
|
7
|
+
Scenario Outline: stubbing
|
8
|
+
Given a new Dog test double
|
9
|
+
When I stub <method> to return <gives>
|
10
|
+
Then invoking <invocation> returns <returns>
|
11
|
+
|
12
|
+
Scenarios: no-arg methods
|
13
|
+
| method | gives | invocation | returns |
|
14
|
+
| to_s | 'something' | to_s | 'something' |
|
15
|
+
| purebred? | true | purebred? | true |
|
16
|
+
|
17
|
+
Scenarios: one-arg methods
|
18
|
+
| method | gives | invocation | returns |
|
19
|
+
| holler_at(true) | :ruff | holler_at(true) | :ruff |
|
20
|
+
| holler_at(true) | :ruff | holler_at(false) | nil |
|
21
|
+
| holler_at(true) | :ruff | holler_at(:panda) | nil |
|
22
|
+
| holler_at(true) | :ruff | holler_at(nil) | nil |
|
23
|
+
|
24
|
+
Scenarios: two-arg methods
|
25
|
+
| method | gives | invocation | returns |
|
26
|
+
| walk_to(1,2) | :park | walk_to(1,2) | :park |
|
27
|
+
| walk_to(1,2) | :park | walk_to(0.9,2) | nil |
|
28
|
+
| walk_to(1,2) | :park | walk_to(1,2.1) | nil |
|
29
|
+
| walk_to([1,5],[2,7]) | :park | walk_to([1],[5,2,7]) | nil |
|
30
|
+
|
31
|
+
Scenario:
|
32
|
+
Given a new Dog test double
|
33
|
+
When I stub purebred? to raise StandardError
|
34
|
+
Then invoking purebred? raises a StandardError
|
@@ -0,0 +1,60 @@
|
|
1
|
+
Feature: stubbing with matchers
|
2
|
+
|
3
|
+
As a test author
|
4
|
+
I want to be able to stub an invocation based on matchers' evaluations of the arguments
|
5
|
+
so that I don't need to redundantly stub things I don't care about
|
6
|
+
|
7
|
+
Scenario Outline: stubbing
|
8
|
+
Given a new Dog test double
|
9
|
+
When I stub <method> to return <gives>
|
10
|
+
Then invoking <invocation> returns <returns>
|
11
|
+
|
12
|
+
Scenarios: the anything matcher with a one-argument method
|
13
|
+
| method | gives | invocation | returns |
|
14
|
+
| introduce_to(anything) | 'Why Hello!' | introduce_to(Cat.new) | 'Why Hello!' |
|
15
|
+
| introduce_to(anything) | 'Why Hello!' | introduce_to(Dog.new) | 'Why Hello!' |
|
16
|
+
| introduce_to(anything) | 'Why Hello!' | introduce_to(nil) | 'Why Hello!' |
|
17
|
+
|
18
|
+
Scenarios: the anything matcher with a two-argument method
|
19
|
+
| method | gives | invocation | returns |
|
20
|
+
| walk_to(anything,5) | 'Park' | walk_to(5,5) | 'Park' |
|
21
|
+
| walk_to(anything,5) | 'Park' | walk_to('pants',5) | 'Park' |
|
22
|
+
| walk_to(anything,5) | 'Park' | walk_to(nil,5) | 'Park' |
|
23
|
+
| walk_to(anything,5) | 'Park' | walk_to(3,5.1) | nil |
|
24
|
+
| walk_to(3.123,anything) | 'Park' | walk_to(3.123,nil) | 'Park' |
|
25
|
+
| walk_to(anything,anything) | 'Park' | walk_to(3,5.1) | 'Park' |
|
26
|
+
|
27
|
+
Scenarios: the anything matcher with a variable-argument method (argument size must match; but I don't know if I like some of these…)
|
28
|
+
| method | gives | invocation | returns |
|
29
|
+
| eat(anything,:fish,anything) | :yum | eat(:cat,:fish,:mouse) | :yum |
|
30
|
+
| eat(anything,:fish,anything) | :yum | eat(:cat,:pants,:mouse) | nil |
|
31
|
+
| eat(anything) | :yum | eat(:cat,:pants) | nil |
|
32
|
+
| eat(:cat,anything) | :yum | eat(:cat) | nil |
|
33
|
+
| eat(:cat,anything) | :yum | eat(:cat,nil) | :yum |
|
34
|
+
|
35
|
+
Scenarios: the is_a matcher
|
36
|
+
| method | gives | invocation | returns |
|
37
|
+
| introduce_to(is_a(Animal)) | :howdy | introduce_to(Animal.new) | :howdy |
|
38
|
+
| introduce_to(is_a(Animal)) | :howdy | introduce_to(Cat.new) | :howdy |
|
39
|
+
| introduce_to(is_a(Animal)) | :howdy | introduce_to(Object.new) | nil |
|
40
|
+
| introduce_to(is_a(Animal)) | :howdy | introduce_to(nil) | nil |
|
41
|
+
|
42
|
+
Scenarios: the any matcher (like is_a but also matches nil)
|
43
|
+
| method | gives | invocation | returns |
|
44
|
+
| introduce_to(any(Animal)) | :howdy | introduce_to(Animal.new) | :howdy |
|
45
|
+
| introduce_to(any(Animal)) | :howdy | introduce_to(Cat.new) | :howdy |
|
46
|
+
| introduce_to(any(Animal)) | :howdy | introduce_to(Object.new) | nil |
|
47
|
+
| introduce_to(any(Animal)) | :howdy | introduce_to(nil) | :howdy |
|
48
|
+
|
49
|
+
Scenarios: the numeric matcher
|
50
|
+
| method | gives | invocation | returns |
|
51
|
+
| walk_to(numeric,numeric) | :hydrant | walk_to(1.498,8) | :hydrant |
|
52
|
+
| walk_to(numeric,numeric) | :hydrant | walk_to(1.498,'string') | nil |
|
53
|
+
| walk_to(numeric,numeric) | :hydrant | walk_to(1.498,nil) | nil |
|
54
|
+
|
55
|
+
Scenarios: the boolean matcher
|
56
|
+
| method | gives | invocation | returns |
|
57
|
+
| holler_at(boolean) | :ruff | holler_at(true) | :ruff |
|
58
|
+
| holler_at(boolean) | :ruff | holler_at(false) | :ruff |
|
59
|
+
| holler_at(boolean) | :ruff | holler_at('woof') | nil |
|
60
|
+
| holler_at(boolean) | :ruff | holler_at(nil) | nil |
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Feature: Default returns
|
2
|
+
|
3
|
+
As a test author
|
4
|
+
I want my test double to have some sensible defaults
|
5
|
+
so that I do not find myself writing redundant/obvious stub code
|
6
|
+
|
7
|
+
Scenario: query? methods' default stubbing is false
|
8
|
+
Given a new Dog test double
|
9
|
+
Then invoking purebred? returns false
|
10
|
+
And invoking walk_to(1,1) returns nil
|