rr 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/CHANGES +1 -0
  2. data/README +45 -0
  3. data/Rakefile +60 -0
  4. data/examples/environment_fixture_setup.rb +7 -0
  5. data/examples/example_helper.rb +8 -0
  6. data/examples/example_suite.rb +45 -0
  7. data/examples/high_level_example.rb +132 -0
  8. data/examples/rr/do_not_allow_creator_example.rb +90 -0
  9. data/examples/rr/double_bind_example.rb +32 -0
  10. data/examples/rr/double_dispatching_example.rb +167 -0
  11. data/examples/rr/double_example.rb +67 -0
  12. data/examples/rr/double_register_scenario_example.rb +25 -0
  13. data/examples/rr/double_reset_example.rb +60 -0
  14. data/examples/rr/double_verify_example.rb +24 -0
  15. data/examples/rr/expectations/any_argument_expectation_example.rb +43 -0
  16. data/examples/rr/expectations/anything_argument_equality_expectation_example.rb +39 -0
  17. data/examples/rr/expectations/argument_equality_expectation_example.rb +53 -0
  18. data/examples/rr/expectations/boolean_argument_equality_expectation_example.rb +49 -0
  19. data/examples/rr/expectations/duck_type_argument_equality_expectation_example.rb +68 -0
  20. data/examples/rr/expectations/is_a_argument_equality_expectation_example.rb +51 -0
  21. data/examples/rr/expectations/numeric_argument_equality_expectation_example.rb +47 -0
  22. data/examples/rr/expectations/range_argument_equality_expectation_example.rb +60 -0
  23. data/examples/rr/expectations/regexp_argument_equality_expectation_example.rb +68 -0
  24. data/examples/rr/expectations/times_called_expectation_example.rb +176 -0
  25. data/examples/rr/extensions/double_methods_example.rb +130 -0
  26. data/examples/rr/mock_creator_example.rb +65 -0
  27. data/examples/rr/probe_creator_example.rb +83 -0
  28. data/examples/rr/rspec/rspec_adapter_example.rb +64 -0
  29. data/examples/rr/rspec/rspec_usage_example.rb +38 -0
  30. data/examples/rr/scenario_example.rb +399 -0
  31. data/examples/rr/space_create_example.rb +185 -0
  32. data/examples/rr/space_example.rb +30 -0
  33. data/examples/rr/space_helper.rb +7 -0
  34. data/examples/rr/space_register_example.rb +33 -0
  35. data/examples/rr/space_reset_example.rb +73 -0
  36. data/examples/rr/space_verify_example.rb +146 -0
  37. data/examples/rr/stub_creator_example.rb +74 -0
  38. data/examples/rr/test_unit/test_helper.rb +9 -0
  39. data/examples/rr/test_unit/test_unit_integration_test.rb +39 -0
  40. data/examples/rspec_example_suite.rb +25 -0
  41. data/examples/test_unit_example_suite.rb +21 -0
  42. data/lib/rr.rb +27 -0
  43. data/lib/rr/adapters/rspec.rb +19 -0
  44. data/lib/rr/adapters/test_unit.rb +27 -0
  45. data/lib/rr/do_not_allow_creator.rb +39 -0
  46. data/lib/rr/double.rb +91 -0
  47. data/lib/rr/expectations/any_argument_expectation.rb +17 -0
  48. data/lib/rr/expectations/argument_equality_expectation.rb +35 -0
  49. data/lib/rr/expectations/times_called_expectation.rb +39 -0
  50. data/lib/rr/expectations/wildcard_matchers/anything.rb +15 -0
  51. data/lib/rr/expectations/wildcard_matchers/boolean.rb +20 -0
  52. data/lib/rr/expectations/wildcard_matchers/duck_type.rb +26 -0
  53. data/lib/rr/expectations/wildcard_matchers/is_a.rb +22 -0
  54. data/lib/rr/expectations/wildcard_matchers/numeric.rb +11 -0
  55. data/lib/rr/expectations/wildcard_matchers/range.rb +7 -0
  56. data/lib/rr/expectations/wildcard_matchers/regexp.rb +7 -0
  57. data/lib/rr/extensions/double_methods.rb +81 -0
  58. data/lib/rr/mock_creator.rb +32 -0
  59. data/lib/rr/probe_creator.rb +31 -0
  60. data/lib/rr/scenario.rb +184 -0
  61. data/lib/rr/scenario_not_found_error.rb +4 -0
  62. data/lib/rr/scenario_order_error.rb +4 -0
  63. data/lib/rr/space.rb +111 -0
  64. data/lib/rr/stub_creator.rb +36 -0
  65. 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,9 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../../environment_fixture_setup"
3
+ require "rr/adapters/test_unit"
4
+
5
+ require "test/unit"
6
+
7
+ class Test::Unit::TestCase
8
+ include RR::Adapters::TestUnit
9
+ 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
@@ -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
@@ -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,17 @@
1
+ module RR
2
+ module Expectations
3
+ class AnyArgumentExpectation
4
+ def exact_match?(*arguments)
5
+ false
6
+ end
7
+
8
+ def wildcard_match?(*arguments)
9
+ true
10
+ end
11
+
12
+ def ==(other)
13
+ self.class == other.class
14
+ end
15
+ end
16
+ end
17
+ 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