rr 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +1 -0
- data/README +45 -0
- data/Rakefile +60 -0
- data/examples/environment_fixture_setup.rb +7 -0
- data/examples/example_helper.rb +8 -0
- data/examples/example_suite.rb +45 -0
- data/examples/high_level_example.rb +132 -0
- data/examples/rr/do_not_allow_creator_example.rb +90 -0
- data/examples/rr/double_bind_example.rb +32 -0
- data/examples/rr/double_dispatching_example.rb +167 -0
- data/examples/rr/double_example.rb +67 -0
- data/examples/rr/double_register_scenario_example.rb +25 -0
- data/examples/rr/double_reset_example.rb +60 -0
- data/examples/rr/double_verify_example.rb +24 -0
- data/examples/rr/expectations/any_argument_expectation_example.rb +43 -0
- data/examples/rr/expectations/anything_argument_equality_expectation_example.rb +39 -0
- data/examples/rr/expectations/argument_equality_expectation_example.rb +53 -0
- data/examples/rr/expectations/boolean_argument_equality_expectation_example.rb +49 -0
- data/examples/rr/expectations/duck_type_argument_equality_expectation_example.rb +68 -0
- data/examples/rr/expectations/is_a_argument_equality_expectation_example.rb +51 -0
- data/examples/rr/expectations/numeric_argument_equality_expectation_example.rb +47 -0
- data/examples/rr/expectations/range_argument_equality_expectation_example.rb +60 -0
- data/examples/rr/expectations/regexp_argument_equality_expectation_example.rb +68 -0
- data/examples/rr/expectations/times_called_expectation_example.rb +176 -0
- data/examples/rr/extensions/double_methods_example.rb +130 -0
- data/examples/rr/mock_creator_example.rb +65 -0
- data/examples/rr/probe_creator_example.rb +83 -0
- data/examples/rr/rspec/rspec_adapter_example.rb +64 -0
- data/examples/rr/rspec/rspec_usage_example.rb +38 -0
- data/examples/rr/scenario_example.rb +399 -0
- data/examples/rr/space_create_example.rb +185 -0
- data/examples/rr/space_example.rb +30 -0
- data/examples/rr/space_helper.rb +7 -0
- data/examples/rr/space_register_example.rb +33 -0
- data/examples/rr/space_reset_example.rb +73 -0
- data/examples/rr/space_verify_example.rb +146 -0
- data/examples/rr/stub_creator_example.rb +74 -0
- data/examples/rr/test_unit/test_helper.rb +9 -0
- data/examples/rr/test_unit/test_unit_integration_test.rb +39 -0
- data/examples/rspec_example_suite.rb +25 -0
- data/examples/test_unit_example_suite.rb +21 -0
- data/lib/rr.rb +27 -0
- data/lib/rr/adapters/rspec.rb +19 -0
- data/lib/rr/adapters/test_unit.rb +27 -0
- data/lib/rr/do_not_allow_creator.rb +39 -0
- data/lib/rr/double.rb +91 -0
- data/lib/rr/expectations/any_argument_expectation.rb +17 -0
- data/lib/rr/expectations/argument_equality_expectation.rb +35 -0
- data/lib/rr/expectations/times_called_expectation.rb +39 -0
- data/lib/rr/expectations/wildcard_matchers/anything.rb +15 -0
- data/lib/rr/expectations/wildcard_matchers/boolean.rb +20 -0
- data/lib/rr/expectations/wildcard_matchers/duck_type.rb +26 -0
- data/lib/rr/expectations/wildcard_matchers/is_a.rb +22 -0
- data/lib/rr/expectations/wildcard_matchers/numeric.rb +11 -0
- data/lib/rr/expectations/wildcard_matchers/range.rb +7 -0
- data/lib/rr/expectations/wildcard_matchers/regexp.rb +7 -0
- data/lib/rr/extensions/double_methods.rb +81 -0
- data/lib/rr/mock_creator.rb +32 -0
- data/lib/rr/probe_creator.rb +31 -0
- data/lib/rr/scenario.rb +184 -0
- data/lib/rr/scenario_not_found_error.rb +4 -0
- data/lib/rr/scenario_order_error.rb +4 -0
- data/lib/rr/space.rb +111 -0
- data/lib/rr/stub_creator.rb +36 -0
- metadata +113 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
dir = File.dirname(__FILE__)
|
2
|
+
require "#{dir}/../example_helper"
|
3
|
+
|
4
|
+
module RR
|
5
|
+
describe StubCreator, :shared => true do
|
6
|
+
before(:each) do
|
7
|
+
@space = Space.new
|
8
|
+
@subject = Object.new
|
9
|
+
end
|
10
|
+
|
11
|
+
it "initializes creator with passed in object" do
|
12
|
+
class << @creator
|
13
|
+
attr_reader :subject
|
14
|
+
end
|
15
|
+
@creator.subject.should === @subject
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe StubCreator, ".new without block" do
|
20
|
+
it_should_behave_like "RR::StubCreator"
|
21
|
+
|
22
|
+
before do
|
23
|
+
@creator = StubCreator.new(@space, @subject)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe StubCreator, ".new with block" do
|
28
|
+
it_should_behave_like "RR::StubCreator"
|
29
|
+
|
30
|
+
before do
|
31
|
+
@creator = StubCreator.new(@space, @subject) do |c|
|
32
|
+
c.foobar(1, 2) {:one_two}
|
33
|
+
c.foobar(1) {:one}
|
34
|
+
c.foobar.with_any_args {:default}
|
35
|
+
c.baz() {:baz_result}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "creates doubles" do
|
40
|
+
@subject.foobar(1, 2).should == :one_two
|
41
|
+
@subject.foobar(1, 2).should == :one_two
|
42
|
+
@subject.foobar(1).should == :one
|
43
|
+
@subject.foobar(1).should == :one
|
44
|
+
@subject.foobar(:something).should == :default
|
45
|
+
@subject.foobar(:something).should == :default
|
46
|
+
@subject.baz.should == :baz_result
|
47
|
+
@subject.baz.should == :baz_result
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe StubCreator, "#method_missing" do
|
52
|
+
it_should_behave_like "RR::StubCreator"
|
53
|
+
|
54
|
+
before do
|
55
|
+
@subject = Object.new
|
56
|
+
@creator = StubCreator.new(@space, @subject)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "stubs the subject without any args" do
|
60
|
+
@creator.foobar {:baz}
|
61
|
+
@subject.foobar.should == :baz
|
62
|
+
end
|
63
|
+
|
64
|
+
it "stubs the subject mapping passed in args with the output" do
|
65
|
+
@creator.foobar(1, 2) {:one_two}
|
66
|
+
@creator.foobar(1) {:one}
|
67
|
+
@creator.foobar() {:nothing}
|
68
|
+
@subject.foobar.should == :nothing
|
69
|
+
@subject.foobar(1).should == :one
|
70
|
+
@subject.foobar(1, 2).should == :one_two
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
dir = File.dirname(__FILE__)
|
2
|
+
require "#{dir}/test_helper"
|
3
|
+
|
4
|
+
class TestUnitIntegrationTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
super
|
7
|
+
@subject = Object.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_using_a_mock
|
15
|
+
mock(@subject).foobar(1, 2) {:baz}
|
16
|
+
assert_equal :baz, @subject.foobar(1, 2)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_using_a_stub
|
20
|
+
stub(@subject).foobar {:baz}
|
21
|
+
assert_equal :baz, @subject.foobar("any", "thing")
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_using_a_probe
|
25
|
+
def @subject.foobar
|
26
|
+
:baz
|
27
|
+
end
|
28
|
+
|
29
|
+
probe(@subject).foobar
|
30
|
+
assert_equal :baz, @subject.foobar
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_times_called_verification
|
34
|
+
mock(@subject).foobar(1, 2) {:baz}
|
35
|
+
assert_raise RR::Expectations::TimesCalledExpectationError do
|
36
|
+
teardown
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "spec"
|
3
|
+
|
4
|
+
class RspecExampleSuite
|
5
|
+
def run
|
6
|
+
options = ::Spec::Runner::OptionParser.new.parse(ARGV.dup, STDERR, STDOUT, false)
|
7
|
+
$behaviour_runner = options.create_behaviour_runner
|
8
|
+
|
9
|
+
require_specs
|
10
|
+
|
11
|
+
puts "Running Rspec Example Suite"
|
12
|
+
$behaviour_runner.run(ARGV, false)
|
13
|
+
end
|
14
|
+
|
15
|
+
def require_specs
|
16
|
+
dir = File.dirname(__FILE__)
|
17
|
+
Dir["#{dir}/rr/rspec/**/*_example.rb"].each do |file|
|
18
|
+
require file
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if $0 == __FILE__
|
24
|
+
RspecExampleSuite.new.run
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "spec"
|
3
|
+
|
4
|
+
class TestUnitTestSuite
|
5
|
+
def run
|
6
|
+
require_tests
|
7
|
+
|
8
|
+
puts "Running Test::Unit Test Suite"
|
9
|
+
end
|
10
|
+
|
11
|
+
def require_tests
|
12
|
+
dir = File.dirname(__FILE__)
|
13
|
+
Dir["#{dir}/rr/test_unit/**/*_test.rb"].each do |file|
|
14
|
+
require file
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
if $0 == __FILE__
|
20
|
+
TestUnitTestSuite.new.run
|
21
|
+
end
|
data/lib/rr.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
dir = File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require "rr/double"
|
4
|
+
|
5
|
+
require "rr/mock_creator"
|
6
|
+
require "rr/stub_creator"
|
7
|
+
require "rr/probe_creator"
|
8
|
+
require "rr/do_not_allow_creator"
|
9
|
+
|
10
|
+
require "rr/scenario"
|
11
|
+
require "rr/space"
|
12
|
+
|
13
|
+
require "rr/scenario_not_found_error"
|
14
|
+
require "rr/scenario_order_error"
|
15
|
+
|
16
|
+
require "rr/expectations/argument_equality_expectation"
|
17
|
+
require "rr/expectations/any_argument_expectation"
|
18
|
+
require "rr/expectations/times_called_expectation"
|
19
|
+
require "rr/expectations/wildcard_matchers/anything"
|
20
|
+
require "rr/expectations/wildcard_matchers/is_a"
|
21
|
+
require "rr/expectations/wildcard_matchers/numeric"
|
22
|
+
require "rr/expectations/wildcard_matchers/boolean"
|
23
|
+
require "rr/expectations/wildcard_matchers/duck_type"
|
24
|
+
require "rr/expectations/wildcard_matchers/regexp"
|
25
|
+
require "rr/expectations/wildcard_matchers/range"
|
26
|
+
|
27
|
+
require "rr/extensions/double_methods"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "spec/mocks"
|
2
|
+
require "rr"
|
3
|
+
|
4
|
+
module RR
|
5
|
+
module Adapters
|
6
|
+
module Rspec
|
7
|
+
include RR::Extensions::DoubleMethods
|
8
|
+
def setup_mocks_for_rspec
|
9
|
+
RR::Space.instance.reset_doubles
|
10
|
+
end
|
11
|
+
def verify_mocks_for_rspec
|
12
|
+
RR::Space.instance.verify_doubles
|
13
|
+
end
|
14
|
+
def teardown_mocks_for_rspec
|
15
|
+
RR::Space.instance.reset_doubles
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "spec/mocks"
|
2
|
+
require "rr"
|
3
|
+
|
4
|
+
module RR
|
5
|
+
module Adapters
|
6
|
+
module TestUnit
|
7
|
+
include RR::Extensions::DoubleMethods
|
8
|
+
def self.included(mod)
|
9
|
+
mod.class_eval do
|
10
|
+
alias_method :setup_without_rr, :setup
|
11
|
+
def setup_with_rr
|
12
|
+
setup_without_rr
|
13
|
+
RR::Space.instance.reset_doubles
|
14
|
+
end
|
15
|
+
alias_method :setup, :setup_with_rr
|
16
|
+
|
17
|
+
alias_method :teardown_without_rr, :teardown
|
18
|
+
def teardown_with_rr
|
19
|
+
RR::Space.instance.verify_doubles
|
20
|
+
teardown_without_rr
|
21
|
+
end
|
22
|
+
alias_method :teardown, :teardown_with_rr
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module RR
|
2
|
+
# RR::DoNotAllowCreator uses RR::DoNotAllowCreator#method_missing to create
|
3
|
+
# a Scenario that expects never to be called.
|
4
|
+
#
|
5
|
+
# The following example mocks method_name with arg1 and arg2
|
6
|
+
# returning return_value.
|
7
|
+
#
|
8
|
+
# do_not_allow(subject).method_name(arg1, arg2) { return_value }
|
9
|
+
#
|
10
|
+
# The DoNotAllowCreator also supports a block sytnax.
|
11
|
+
#
|
12
|
+
# do_not_allow(subject) do |m|
|
13
|
+
# m.method1 # Do not allow method1 with any arguments
|
14
|
+
# m.method2(arg1, arg2) # Do not allow method2 with arguments arg1 and arg2
|
15
|
+
# m.method3.with_no_args # Do not allow method3 with no arguments
|
16
|
+
# end
|
17
|
+
class DoNotAllowCreator
|
18
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__/ }
|
19
|
+
|
20
|
+
def initialize(space, subject)
|
21
|
+
@space = space
|
22
|
+
@subject = subject
|
23
|
+
yield(self) if block_given?
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
def method_missing(method_name, *args, &returns)
|
28
|
+
double = @space.create_double(@subject, method_name)
|
29
|
+
scenario = @space.create_scenario(double)
|
30
|
+
if args.empty?
|
31
|
+
scenario.with_any_args
|
32
|
+
else
|
33
|
+
scenario.with(*args)
|
34
|
+
end
|
35
|
+
scenario.never.returns(&returns)
|
36
|
+
scenario
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/rr/double.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
module RR
|
2
|
+
# RR::Double is the binding of an object and a method.
|
3
|
+
# A double has 0 to many Scenario objects. Each Scenario
|
4
|
+
# has Argument Expectations and Times called Expectations.
|
5
|
+
class Double
|
6
|
+
MethodArguments = Struct.new(:arguments, :block)
|
7
|
+
attr_reader :space, :object, :method_name, :original_method, :scenarios
|
8
|
+
|
9
|
+
def initialize(space, object, method_name)
|
10
|
+
@space = space
|
11
|
+
@object = object
|
12
|
+
@method_name = method_name.to_sym
|
13
|
+
@original_method = object.method(method_name) if @object.methods.include?(method_name.to_s)
|
14
|
+
@scenarios = []
|
15
|
+
end
|
16
|
+
|
17
|
+
# RR::Double#register_scenario adds the passed in Scenario
|
18
|
+
# into this Double's list of Scenario objects.
|
19
|
+
def register_scenario(scenario)
|
20
|
+
@scenarios << scenario
|
21
|
+
end
|
22
|
+
|
23
|
+
# RR::Double#bind injects a method that acts as a dispatcher
|
24
|
+
# that dispatches to the matching Scenario when the method
|
25
|
+
# is called.
|
26
|
+
def bind
|
27
|
+
define_implementation_placeholder
|
28
|
+
returns_method = <<-METHOD
|
29
|
+
def #{@method_name}(*args, &block)
|
30
|
+
arguments = MethodArguments.new(args, block)
|
31
|
+
#{placeholder_name}(arguments)
|
32
|
+
end
|
33
|
+
METHOD
|
34
|
+
meta.class_eval(returns_method, __FILE__, __LINE__ - 5)
|
35
|
+
end
|
36
|
+
|
37
|
+
# RR::Double#verify verifies each Scenario
|
38
|
+
# TimesCalledExpectation are met.
|
39
|
+
def verify
|
40
|
+
@scenarios.each do |scenario|
|
41
|
+
scenario.verify
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# RR::Double#reset removes the injected dispatcher method.
|
46
|
+
# It binds the original method implementation on the object
|
47
|
+
# if one exists.
|
48
|
+
def reset
|
49
|
+
meta.send(:remove_method, placeholder_name)
|
50
|
+
if @original_method
|
51
|
+
meta.send(:define_method, @method_name, &@original_method)
|
52
|
+
else
|
53
|
+
meta.send(:remove_method, @method_name)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
def define_implementation_placeholder
|
59
|
+
me = self
|
60
|
+
meta.send(:define_method, placeholder_name) do |arguments|
|
61
|
+
me.send(:call_method, arguments.arguments, arguments.block)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def call_method(args, block)
|
66
|
+
matching_scenarios = []
|
67
|
+
@scenarios.each do |scenario|
|
68
|
+
if scenario.exact_match?(*args)
|
69
|
+
matching_scenarios << scenario
|
70
|
+
return scenario.call(*args, &block) unless scenario.times_called_verified?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
@scenarios.each do |scenario|
|
74
|
+
if scenario.wildcard_match?(*args)
|
75
|
+
matching_scenarios << scenario
|
76
|
+
return scenario.call(*args, &block) unless scenario.times_called_verified?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
matching_scenarios.first.call(*args) unless matching_scenarios.empty?
|
80
|
+
raise ScenarioNotFoundError, "No scenario for arguments #{args.inspect}"
|
81
|
+
end
|
82
|
+
|
83
|
+
def placeholder_name
|
84
|
+
"__rr__#{@method_name}__rr__"
|
85
|
+
end
|
86
|
+
|
87
|
+
def meta
|
88
|
+
(class << @object; self; end)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module RR
|
2
|
+
module Expectations
|
3
|
+
class ArgumentEqualityExpectationError < RuntimeError
|
4
|
+
end
|
5
|
+
|
6
|
+
class ArgumentEqualityExpectation
|
7
|
+
attr_reader :expected_arguments
|
8
|
+
|
9
|
+
def initialize(*expected_arguments)
|
10
|
+
@expected_arguments = expected_arguments
|
11
|
+
end
|
12
|
+
|
13
|
+
def exact_match?(*arguments)
|
14
|
+
@expected_arguments == arguments
|
15
|
+
end
|
16
|
+
|
17
|
+
def wildcard_match?(*arguments)
|
18
|
+
return false unless arguments.length == @expected_arguments.length
|
19
|
+
arguments.each_with_index do |arg, index|
|
20
|
+
expected_argument = @expected_arguments[index]
|
21
|
+
if expected_argument.respond_to?(:wildcard_match?)
|
22
|
+
return false unless expected_argument.wildcard_match?(arg)
|
23
|
+
else
|
24
|
+
return false unless expected_argument == arg
|
25
|
+
end
|
26
|
+
end
|
27
|
+
return true
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(other)
|
31
|
+
@expected_arguments == other.expected_arguments
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module RR
|
2
|
+
module Expectations
|
3
|
+
class TimesCalledExpectationError < RuntimeError
|
4
|
+
end
|
5
|
+
|
6
|
+
class TimesCalledExpectation
|
7
|
+
attr_reader :times, :times_called
|
8
|
+
|
9
|
+
def initialize(times=nil, &time_condition_block)
|
10
|
+
raise ArgumentError, "Cannot pass in both an argument and a block" if times && time_condition_block
|
11
|
+
@times = times || time_condition_block
|
12
|
+
@times_called = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def verify_input
|
16
|
+
@times_called += 1
|
17
|
+
verify_input_error if @times.is_a?(Integer) && @times_called > @times
|
18
|
+
verify_input_error if @times.is_a?(Range) && @times_called > @times.end
|
19
|
+
return
|
20
|
+
end
|
21
|
+
|
22
|
+
def verify
|
23
|
+
return true if @times.is_a?(Integer) && @times == @times_called
|
24
|
+
return true if @times.is_a?(Proc) && @times.call(@times_called)
|
25
|
+
return true if @times.is_a?(Range) && @times.include?(@times_called)
|
26
|
+
return false
|
27
|
+
end
|
28
|
+
|
29
|
+
def verify!
|
30
|
+
raise TimesCalledExpectationError unless verify
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
def verify_input_error
|
35
|
+
raise TimesCalledExpectationError, "Called #{@times_called.inspect} times. Expected #{@times.inspect}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|