gimme 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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
@@ -0,0 +1,55 @@
|
|
1
|
+
|
2
|
+
# A simple class hierarchy declaring a few different types of methods on it that I can write cucumber features against something other than standard ruby classes/methods
|
3
|
+
|
4
|
+
module Eater
|
5
|
+
def eat(*foods)
|
6
|
+
#verify varargs
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Animal
|
11
|
+
end
|
12
|
+
|
13
|
+
class Dog < Animal
|
14
|
+
include Eater
|
15
|
+
|
16
|
+
# to exercise stub & verify of attributes: matching(regex)
|
17
|
+
attr_accessor :name
|
18
|
+
|
19
|
+
def walk_to(x,y)
|
20
|
+
'use me to exercise numeric matchers: numeric, less_than, greater_than, within_range'
|
21
|
+
end
|
22
|
+
|
23
|
+
def introduce_to(animal)
|
24
|
+
'use me to exercise identity matchers: anything, is_a(Animal), is_a(Cat), any(Animal), any(Dog)'
|
25
|
+
end
|
26
|
+
|
27
|
+
def holler_at(loudly)
|
28
|
+
'use me to exercise: boolean'
|
29
|
+
end
|
30
|
+
|
31
|
+
def use_toys(hash_of_toys_and_actions)
|
32
|
+
'use me to exercise: hash_including'
|
33
|
+
end
|
34
|
+
|
35
|
+
def clean_toys(array_of_toys)
|
36
|
+
'use me to exercise: including'
|
37
|
+
end
|
38
|
+
|
39
|
+
def purebred?
|
40
|
+
'stub me to return a boolean'
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
class Cat < Animal
|
46
|
+
end
|
47
|
+
|
48
|
+
class Turtle < Animal
|
49
|
+
def initialize(shell)
|
50
|
+
@shell = shell
|
51
|
+
end
|
52
|
+
|
53
|
+
def swim
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
Feature: Encountering Unknown Methods
|
2
|
+
|
3
|
+
As a test author
|
4
|
+
I want my test double to yell at me when I try invoking a method that instances of the class being doubled wouldn't respond to
|
5
|
+
so that I don't find myself with a green bar and a dependency that can't do what the test thinks it does.
|
6
|
+
|
7
|
+
However, I also want to be able to stub and verify methods that aren't apparently on the class
|
8
|
+
so that I can test behavior that I'm confident will be added to the test double's real counterpart dynamically at runtime
|
9
|
+
|
10
|
+
Scenario: on a classy double, stubbing an unknown method
|
11
|
+
Given a new Dog test double
|
12
|
+
When I stub meow to return "Woof"
|
13
|
+
Then a NoMethodError is raised
|
14
|
+
|
15
|
+
Scenario: on a classy double, verifying an unknown method
|
16
|
+
Given a new Dog test double
|
17
|
+
When I invoke gobbeldy_gook
|
18
|
+
Then verifying gobbeldy_gook raises a NoMethodError
|
19
|
+
|
20
|
+
Scenario: on a classy double, stubbing a method with "give!"
|
21
|
+
Given a new Dog test double
|
22
|
+
When I stub! meow to return :woof
|
23
|
+
Then invoking meow returns :woof
|
24
|
+
|
25
|
+
Scenario: on a classy double, verifying a method with "verify!"
|
26
|
+
Given a new Dog test double
|
27
|
+
When I invoke meow
|
28
|
+
Then I can verify! meow has been invoked 1 time
|
29
|
+
|
30
|
+
Scenario: on a generic double, stubbing an unknown method
|
31
|
+
Given a new test double
|
32
|
+
When I stub meow to return "Woof"
|
33
|
+
Then no error is raised
|
34
|
+
|
35
|
+
Scenario: on a generic double, verifying an unknown method
|
36
|
+
Given a new test double
|
37
|
+
When I invoke gobbeldy_gook
|
38
|
+
Then I can verify gobbeldy_gook has been invoked 1 time
|
39
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Feature: verify with an anything matcher
|
2
|
+
|
3
|
+
As a test author
|
4
|
+
I want to be able to verify an invocation replacing exact arguments with the anything matcher
|
5
|
+
so that I'm able to specify only the parameters that matter to me
|
6
|
+
|
7
|
+
Scenario: a single-argument
|
8
|
+
Given a new Dog test double
|
9
|
+
Then I can verify holler_at(anything) has been invoked 0 times
|
10
|
+
When I invoke holler_at(false)
|
11
|
+
Then I can verify holler_at(anything) has been invoked 1 time
|
12
|
+
When I invoke holler_at(true)
|
13
|
+
Then I can verify holler_at(anything) has been invoked 2 times
|
14
|
+
|
15
|
+
Scenario: two arguments
|
16
|
+
Given a new Dog test double
|
17
|
+
Then I can verify walk_to(anything,anything) has been invoked 0 times
|
18
|
+
When I invoke walk_to(12.34,943.1)
|
19
|
+
Then I can verify walk_to(anything,anything) has been invoked 1 time
|
20
|
+
And I can verify walk_to(anything,943.1) has been invoked 1 time
|
21
|
+
And I can verify walk_to(12.34,anything) has been invoked 1 time
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Feature: verification of no-arg methods
|
2
|
+
|
3
|
+
As a test author
|
4
|
+
I want to verify my test double's no-arg method was invoked
|
5
|
+
So that I can specify its behavior
|
6
|
+
|
7
|
+
Scenario:
|
8
|
+
Given a new test double
|
9
|
+
But I do not invoke to_s
|
10
|
+
Then verifying to_s raises a Gimme::Errors::VerificationFailedError
|
11
|
+
But I can verify to_s has been invoked 0 times
|
12
|
+
|
13
|
+
When I invoke to_s
|
14
|
+
Then I can verify to_s has been invoked
|
15
|
+
And I can verify to_s has been invoked 1 time
|
16
|
+
|
17
|
+
When I invoke to_s
|
18
|
+
Then I can verify to_s has been invoked 2 times
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Feature: verification of argumentative methods
|
2
|
+
|
3
|
+
As a test author
|
4
|
+
I want to verify my test double's argument-having method was invoked
|
5
|
+
So that I can specify the exact arguments
|
6
|
+
|
7
|
+
Scenario:
|
8
|
+
Given a new test double
|
9
|
+
But I do not invoke equal?(:pants)
|
10
|
+
Then verifying equal?(:pants) raises a Gimme::Errors::VerificationFailedError
|
11
|
+
But I can verify equal?(:pants) has been invoked 0 times
|
12
|
+
|
13
|
+
When I invoke equal?(:pants)
|
14
|
+
Then I can verify equal?(:pants) has been invoked
|
15
|
+
And I can verify equal?(:pants) has been invoked 1 time
|
16
|
+
And I can verify equal?(:kaka) has been invoked 0 times
|
17
|
+
|
18
|
+
When I invoke equal?(:pants)
|
19
|
+
And I invoke equal?(:kaka)
|
20
|
+
Then I can verify equal?(:kaka) has been invoked 1 time
|
21
|
+
And I can verify equal?(:pants) has been invoked 2 times
|
data/gimme.gemspec
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{gimme}
|
8
|
+
s.version = "0.1.6"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Justin Searls"]
|
12
|
+
s.date = %q{2011-01-18}
|
13
|
+
s.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). }
|
14
|
+
s.email = %q{searls@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.markdown",
|
18
|
+
"README.rdoc"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
".bundle/config",
|
22
|
+
".document",
|
23
|
+
"Gemfile",
|
24
|
+
"Gemfile.lock",
|
25
|
+
"LICENSE.txt",
|
26
|
+
"README.markdown",
|
27
|
+
"README.rdoc",
|
28
|
+
"Rakefile",
|
29
|
+
"VERSION",
|
30
|
+
"features/argument_captors.feature",
|
31
|
+
"features/gimme_next.feature",
|
32
|
+
"features/step_definitions/gimme_steps.rb",
|
33
|
+
"features/stub_basic.feature",
|
34
|
+
"features/stub_matchers.feature",
|
35
|
+
"features/stub_sensible_defaults.feature",
|
36
|
+
"features/support/animals.rb",
|
37
|
+
"features/support/env.rb",
|
38
|
+
"features/unknown_methods.feature",
|
39
|
+
"features/verify_matcher_anything.feature",
|
40
|
+
"features/verify_no_args.feature",
|
41
|
+
"features/verify_with_args.feature",
|
42
|
+
"gimme.gemspec",
|
43
|
+
"lib/gimme-double.rb",
|
44
|
+
"lib/gimme.rb",
|
45
|
+
"lib/gimme/captor.rb",
|
46
|
+
"lib/gimme/errors.rb",
|
47
|
+
"lib/gimme/gives.rb",
|
48
|
+
"lib/gimme/matchers.rb",
|
49
|
+
"lib/gimme/method_resolver.rb",
|
50
|
+
"lib/gimme/test_double.rb",
|
51
|
+
"lib/gimme/verifies.rb"
|
52
|
+
]
|
53
|
+
s.homepage = %q{http://github.com/searls/gimme}
|
54
|
+
s.licenses = ["MIT"]
|
55
|
+
s.require_paths = ["lib"]
|
56
|
+
s.rubygems_version = %q{1.4.2}
|
57
|
+
s.summary = %q{gimme — a low-specification test double library for Ruby}
|
58
|
+
|
59
|
+
if s.respond_to? :specification_version then
|
60
|
+
s.specification_version = 3
|
61
|
+
|
62
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
63
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
64
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
65
|
+
s.add_development_dependency(%q<rspec>, [">= 1.3.1"])
|
66
|
+
s.add_development_dependency(%q<cucumber>, [">= 0.10.0"])
|
67
|
+
s.add_development_dependency(%q<rspec>, [">= 1.3.1"])
|
68
|
+
s.add_development_dependency(%q<cucumber>, [">= 0.10.0"])
|
69
|
+
else
|
70
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
71
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
72
|
+
s.add_dependency(%q<rspec>, [">= 1.3.1"])
|
73
|
+
s.add_dependency(%q<cucumber>, [">= 0.10.0"])
|
74
|
+
s.add_dependency(%q<rspec>, [">= 1.3.1"])
|
75
|
+
s.add_dependency(%q<cucumber>, [">= 0.10.0"])
|
76
|
+
end
|
77
|
+
else
|
78
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
79
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
80
|
+
s.add_dependency(%q<rspec>, [">= 1.3.1"])
|
81
|
+
s.add_dependency(%q<cucumber>, [">= 0.10.0"])
|
82
|
+
s.add_dependency(%q<rspec>, [">= 1.3.1"])
|
83
|
+
s.add_dependency(%q<cucumber>, [">= 0.10.0"])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
data/lib/gimme/captor.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Gimme
|
2
|
+
|
3
|
+
class Captor
|
4
|
+
attr_accessor :value
|
5
|
+
end
|
6
|
+
|
7
|
+
module Matchers
|
8
|
+
class Capture < Matchers::Matcher
|
9
|
+
def initialize(captor)
|
10
|
+
@captor = captor
|
11
|
+
end
|
12
|
+
|
13
|
+
def matches?(arg)
|
14
|
+
@captor.value = arg
|
15
|
+
true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def capture(captor)
|
20
|
+
Capture.new(captor)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/gimme/errors.rb
ADDED
data/lib/gimme/gives.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Gimme
|
2
|
+
|
3
|
+
class Gives < BlankSlate
|
4
|
+
attr_accessor :raises_no_method_error
|
5
|
+
def initialize(double)
|
6
|
+
@double = double
|
7
|
+
@raises_no_method_error = true
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(sym, *args, &block)
|
11
|
+
sym = MethodResolver.resolve_sent_method(@double,sym,args,@raises_no_method_error)
|
12
|
+
|
13
|
+
@double.stubbings[sym] ||= {}
|
14
|
+
@double.stubbings[sym][args] = block if block
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def give(double)
|
19
|
+
Gimme::Gives.new(double)
|
20
|
+
end
|
21
|
+
|
22
|
+
def give!(double)
|
23
|
+
give = give(double)
|
24
|
+
give.raises_no_method_error = false
|
25
|
+
give
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Gimme
|
2
|
+
module Matchers
|
3
|
+
class Matcher
|
4
|
+
def matches?(arg)
|
5
|
+
false
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Anything < Matcher
|
10
|
+
def matches?(arg)
|
11
|
+
true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
def anything
|
15
|
+
Gimme::Matchers::Anything.new
|
16
|
+
end
|
17
|
+
|
18
|
+
class IsA < Matcher
|
19
|
+
def initialize(cls)
|
20
|
+
@cls = cls
|
21
|
+
end
|
22
|
+
|
23
|
+
def matches?(arg)
|
24
|
+
arg.kind_of?(@cls)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
def is_a(cls)
|
28
|
+
Gimme::Matchers::IsA.new(cls)
|
29
|
+
end
|
30
|
+
|
31
|
+
class Any < IsA
|
32
|
+
def matches?(arg)
|
33
|
+
arg == nil || arg.kind_of?(@cls)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
def any(cls)
|
37
|
+
Gimme::Matchers::Any.new(cls)
|
38
|
+
end
|
39
|
+
|
40
|
+
class Numeric < Matcher
|
41
|
+
def matches?(arg)
|
42
|
+
arg.kind_of?(Fixnum) || arg.kind_of?(Numeric) || arg.kind_of?(Float)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
def numeric
|
46
|
+
Gimme::Matchers::Numeric.new
|
47
|
+
end
|
48
|
+
|
49
|
+
class Boolean < Matcher
|
50
|
+
def matches?(arg)
|
51
|
+
arg.kind_of?(TrueClass) || arg.kind_of?(FalseClass)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
def boolean
|
55
|
+
Gimme::Matchers::Boolean.new
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Gimme
|
2
|
+
|
3
|
+
class MethodResolver
|
4
|
+
def self.resolve_sent_method(double,sym,args,raises_no_method_error=true)
|
5
|
+
cls = double.cls
|
6
|
+
sym = args.shift if sym == :send
|
7
|
+
if cls && raises_no_method_error
|
8
|
+
if cls.private_methods.include?(sym.to_s)
|
9
|
+
raise NoMethodError.new("#{sym} is a private method of your #{cls} test double, so stubbing/verifying it
|
10
|
+
might not be a great idea. If you want to try to stub or verify this method anyway, then you can
|
11
|
+
invoke give! or verify! to suppress this error.")
|
12
|
+
elsif !cls.instance_methods.include?(sym.to_s)
|
13
|
+
raise NoMethodError.new("Your test double of #{cls} may not know how to respond to the '#{sym}' method.
|
14
|
+
If you're confident that a real #{cls} will know how to respond to '#{sym}', then you can
|
15
|
+
invoke give! or verify! to suppress this error.")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
sym
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Gimme
|
2
|
+
class BlankSlate
|
3
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__/ }
|
4
|
+
end
|
5
|
+
|
6
|
+
class TestDouble < BlankSlate
|
7
|
+
attr_accessor :cls
|
8
|
+
attr_accessor :stubbings
|
9
|
+
attr_reader :invocations
|
10
|
+
|
11
|
+
def initialize(cls=nil)
|
12
|
+
@cls = cls
|
13
|
+
@stubbings = {}
|
14
|
+
@invocations = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(sym, *args, &block)
|
18
|
+
sym = MethodResolver.resolve_sent_method(self,sym,args,false)
|
19
|
+
|
20
|
+
@invocations[sym] ||= {}
|
21
|
+
@stubbings[sym] ||= {}
|
22
|
+
|
23
|
+
@invocations[sym][args] = 1 + (@invocations[sym][args]||0)
|
24
|
+
|
25
|
+
matching_stub_block = nil
|
26
|
+
@stubbings[sym].each do |stub_args,stub_block|
|
27
|
+
matching = args.size == stub_args.size
|
28
|
+
args.each_index do |i|
|
29
|
+
unless args[i] == stub_args[i] || (stub_args[i].respond_to?(:matches?) && stub_args[i].matches?(args[i]))
|
30
|
+
matching = false
|
31
|
+
break
|
32
|
+
end
|
33
|
+
end
|
34
|
+
matching_stub_block = stub_block if matching
|
35
|
+
end
|
36
|
+
|
37
|
+
if matching_stub_block
|
38
|
+
matching_stub_block.call
|
39
|
+
elsif sym.to_s[-1,1] == '?'
|
40
|
+
false
|
41
|
+
else
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def gimme(cls=nil)
|
48
|
+
Gimme::TestDouble.new(cls)
|
49
|
+
end
|
50
|
+
|
51
|
+
def gimme_next(cls)
|
52
|
+
double = Gimme::TestDouble.new(cls)
|
53
|
+
meta_class = class << cls; self; end
|
54
|
+
real_new = cls.method(:new)
|
55
|
+
meta_class.send(:define_method,:new) do |*args|
|
56
|
+
double.send(:initialize,*args)
|
57
|
+
meta_class.send(:define_method,:new,real_new) #restore :new on the class
|
58
|
+
double
|
59
|
+
end
|
60
|
+
double
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Gimme
|
2
|
+
|
3
|
+
class Verifies < BlankSlate
|
4
|
+
attr_accessor :raises_no_method_error
|
5
|
+
def initialize(double,times=1)
|
6
|
+
@double = double
|
7
|
+
@times = times
|
8
|
+
@raises_no_method_error = true
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_missing(sym, *args, &block)
|
12
|
+
sym = MethodResolver.resolve_sent_method(@double,sym,args,@raises_no_method_error)
|
13
|
+
|
14
|
+
#gosh, this loop sure looks familiar. just like another ugly loop I know. TODO.
|
15
|
+
invoked = 0
|
16
|
+
if @double.invocations[sym]
|
17
|
+
@double.invocations[sym].each do |invoke_args,count|
|
18
|
+
matching = args.size == invoke_args.size
|
19
|
+
invoke_args.each_index do |i|
|
20
|
+
unless invoke_args[i] == args[i] || (args[i].respond_to?(:matches?) && args[i].matches?(invoke_args[i]))
|
21
|
+
matching = false
|
22
|
+
break
|
23
|
+
end
|
24
|
+
end
|
25
|
+
invoked += count if matching
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
if invoked != @times
|
30
|
+
raise Errors::VerificationFailedError.new("expected #{sym} to have been called with #{args}")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def verify(double,times=1)
|
36
|
+
Gimme::Verifies.new(double,times)
|
37
|
+
end
|
38
|
+
|
39
|
+
def verify!(double,times=1)
|
40
|
+
verify = verify(double,times)
|
41
|
+
verify.raises_no_method_error = false
|
42
|
+
verify
|
43
|
+
end
|
44
|
+
end
|
data/lib/gimme-double.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'gimme'
|