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
@@ -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'
|