rr 0.1.0
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/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
|