handoff 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -3,8 +3,9 @@ stdlib module +Forwardable+ makes implementing delegation simple and readable.
3
3
  Handoff is intended to make the code to test it just as simple and
4
4
  readable.
5
5
 
6
- Handoff depends on Mocha and Stubba (http://mocha.rubyforge.org/).
6
+ Handoff depends on Mocha and Stubba (http://mocha.rubyforge.org).
7
7
 
8
- Here's example_test.rb to give you an idea of what your tests will look like:
8
+ Here's example_test.rb to give you an idea of what you can do:
9
9
 
10
10
  :include: test/example_test.rb
11
+
@@ -1,18 +1,15 @@
1
1
  module Handoff
2
- =begin rdoc
3
- Module mixed into a *args array to munge it into a hash keying delegating
4
- method names to target method names.
5
- =end
2
+ # Handoff internal.
6
3
  module ArgumentNormalizer
7
4
 
8
5
  def to_method_map
9
- return self.first if self.size == 1 && self.first.is_a?(Hash)
6
+ return first if size == 1 && first.respond_to?(:each_pair)
10
7
  to_self_referencing_hash
11
8
  end
12
9
 
13
10
  def to_self_referencing_hash
14
11
  hash = Hash.new
15
- self.each {|sym| hash[sym]=sym}
12
+ each {|method_symbol| hash[method_symbol] = method_symbol}
16
13
  hash
17
14
  end
18
15
 
@@ -1,22 +1,28 @@
1
1
  require 'handoff/argument_normalizer'
2
+ require 'handoff/usage_error'
2
3
 
3
4
  module Handoff
4
5
  # This is where the fluent stuff is.
5
6
  # Get an assertion by calling Handoff#assert_handoff. Specify the delegating
6
- # behavior with from or from_any, to, and for_method or for_methods.
7
+ # behavior with
8
+ # * +from+ or +from_any+,
9
+ # * +to+,
10
+ # * +for+, +for_method+ or +for_methods+, and
11
+ # * (optionally) +with+ or +with_arguments+.
7
12
  class Assertion
8
13
 
14
+ # :stopdoc:
15
+
9
16
  attr_reader :method_map
10
- attr_reader :object_under_test
11
- private :method_map, :object_under_test
17
+ attr_reader :delegator
18
+ private :method_map, :delegator
12
19
 
13
- # Creates an expectation on the mock delegate that it will be touched later
14
- # to give a failure if the user forgets to specify everything required.
15
20
  def initialize mock_delegate
16
21
  @mock_delegate = mock_delegate
17
- @mock_delegate.expects(:called!)
18
22
  end
19
23
 
24
+ # :startdoc:
25
+
20
26
  # Creates an instance of +class_under_test+ with no arguments and uses it
21
27
  # as the object under test.
22
28
  def from_any class_under_test
@@ -24,20 +30,20 @@ module Handoff
24
30
  end
25
31
 
26
32
  # Specifies the object being tested, returning self.
27
- def from object_under_test
28
- @object_under_test = object_under_test
33
+ def from delegator
34
+ @delegator = delegator
29
35
  self
30
36
  end
31
37
 
32
38
  # Specifies the accessor the object under test will use to acquire its delegate,
33
39
  # returning self.
34
40
  def to(delegate_accessor)
35
- @object_under_test.expects(delegate_accessor).at_least_once.returns(@mock_delegate)
41
+ @delegate_accessor = delegate_accessor
36
42
  self
37
43
  end
38
44
 
39
45
  # Tells the assertion what delegating methods to test and what methods in the delegate
40
- # they should delegate to. Then actually does the testing.
46
+ # they should delegate to.
41
47
  #
42
48
  # :call-seq: for_methods(:delegating_method1, :delegating_method2, ...)
43
49
  # for_methods({:delegating_method1 => :target_method1, :delegating_method2 => :target_method2})
@@ -46,25 +52,49 @@ module Handoff
46
52
  # a Hash mapping methods in the delegating class to methods in the delegate.
47
53
  def for_methods(*args)
48
54
  @method_map = args.extend(ArgumentNormalizer).to_method_map
49
- conduct
55
+ self
50
56
  end
51
- alias for_method for_methods
57
+ alias for_method for_methods
58
+ alias for for_methods
59
+
60
+ def with_arguments *args
61
+ @args = args
62
+ end
63
+ alias with with_arguments
64
+
65
+ # Specifies that the assertion should pass no arguments to the delegating method and
66
+ # require that no arguments are sent to the target method.
67
+ # Equivalent to calling +with_arguments+ with no arguments.
68
+ def with_no_arguments
69
+ with_arguments
70
+ end
71
+
72
+ # :stopdoc:
52
73
 
53
- private
54
74
  def conduct
55
- @mock_delegate.called!
75
+ validate_sufficiently_specified
76
+ @delegator.expects(@delegate_accessor).at_least_once.returns(@mock_delegate)
56
77
  expected_returns = {}
57
78
  method_map.each_pair do |delegating_method, target|
58
79
  expected_returns[delegating_method] = "#{delegating_method.to_s}_return"
59
- @mock_delegate.expects(target).returns(expected_returns[delegating_method])
80
+ expectation = @mock_delegate.expects(target).returns(expected_returns[delegating_method])
81
+ expectation.with(*@args) unless @args.nil?
60
82
  end
61
83
  method_map.each_pair do |delegating_method, target|
62
- actual = object_under_test.send(delegating_method)
84
+ send_args = [delegating_method, @args].flatten
85
+ actual = delegator.send(*send_args)
63
86
  unless actual.equal? expected_returns[delegating_method]
64
- raise "expected #{delegating_method.to_s} to return #{expected_returns[method]}, but was #{actual}"
87
+ raise "expected #{delegating_method.to_s} to return #{expected_returns[delegating_method]}, but was #{actual}"
65
88
  end
66
89
  end
67
90
  end
68
91
 
92
+ private
93
+ def validate_sufficiently_specified
94
+ raise UsageError, "Object under test must be specified with 'from' or 'from_any'" unless @delegator
95
+ raise UsageError, "Delegate accessor must be specified with 'to'" unless @delegate_accessor
96
+ raise UsageError, "Delegating methods must be specified with 'for_method' or 'for_methods'" unless @method_map
97
+ end
98
+ # :startdoc:
69
99
  end
70
100
  end
@@ -0,0 +1,4 @@
1
+ module Handoff
2
+ class UsageError < RuntimeError
3
+ end
4
+ end
data/lib/handoff.rb CHANGED
@@ -1,18 +1,40 @@
1
1
  require 'handoff/assertion'
2
- require 'test/unit'
3
2
 
4
3
  # +require+ (or +require_gem+) 'handoff' and #assert_handoff can
5
- # be called from any +Test::Unit::TestCase+ test method to create
4
+ # be called from any Test::Unit::TestCase test method to create
6
5
  # a new Handoff::Assertion.
7
6
  #
8
- # After defining Handoff, This file includes it in Test::Unit::TestCase.
7
+ # After defining Handoff, this file includes it in Test::Unit::TestCase.
9
8
  module Handoff
10
9
 
11
10
  # Creates an Assertion, handing a it a mock named 'handoff delegate' on which
12
11
  # expectations will be set.
13
12
  def assert_handoff
14
- Handoff::Assertion.new(self.mock('handoff delegate'))
13
+ a = Handoff::Assertion.new(self.mock('handoff delegate'))
14
+ handoff_assertions << a
15
+ a
15
16
  end
17
+
18
+ def handoff_assertions
19
+ @handoff_assertions ||= []
20
+ end
21
+
22
+ def conduct_and_clear_handoffs
23
+ begin
24
+ handoff_assertions.each {|a| a.conduct }
25
+ ensure
26
+ handoff_assertions.clear
27
+ end
28
+ end
29
+
30
+ def self.included(base)
31
+ base.class_eval do
32
+ add_teardown_method :conduct_and_clear_handoffs
33
+ end
34
+ end
35
+
16
36
  end
17
37
 
18
- Test::Unit::TestCase.send :include, Handoff
38
+ class Test::Unit::TestCase
39
+ include Handoff unless ancestors.include?(Handoff)
40
+ end
data/test/example_test.rb CHANGED
@@ -1,30 +1,32 @@
1
1
  # This example is only helpful if you're familiar with Forwardable from the
2
2
  # stdlib, which you should be if you're coding delegating behavior in Ruby.
3
- # Here comes an example of something you might have in your code base, but
4
- # haven't managed to test readably. You might have some code like this that's
5
- # tested in very long methods which either repeat but varying set-ups
6
- # Foo is a simple delegating class, which normally wouldn't live in
7
- # your test.
3
+ #
4
+ # Foo is a simple delegating class. We want to specify that delegation in
5
+ # its unit test.
8
6
  class Foo
9
7
  require 'forwardable'
10
8
  extend Forwardable
11
9
  def_delegators :bar, :baz, :bat
12
10
  def_delegator :bar, :baq, :bar_baq
13
11
  def_delegator :bar, :ban, :bar_ban
12
+
13
+ # Foo#bar gets the delegate. This is not what Handoff tests.
14
+ # Rather, Handoff will mock this method and ensure that the
15
+ # delegating methods defined above use it correctly.
14
16
  def bar
15
- @bar ||= BarFactory.create_for(Foo) # gets the delegate ... this is not what we're testing
17
+ @bar ||= BarFactory.create
16
18
  end
17
19
  end
18
20
 
19
21
  require 'test/unit'
20
22
  require 'rubygems'
21
23
 
22
- require File.dirname(__FILE__) + '/../lib/handoff' # I require it this way because I don't want the installed gem.
23
- # require 'handoff' # this is how you'd require handoff (or do require_gem w/ a version).
24
-
25
- require 'mocha' # you need to require mocha and stubba in your test
24
+ require 'mocha' # You need to require mocha and stubba in your test.
26
25
  require 'stubba' # ditto
27
26
 
27
+ require File.dirname(__FILE__) + '/../lib/handoff' # I require it this way because I don't want the installed gem.
28
+ # require 'handoff' # This is how you'd require handoff (or do require_gem w/ a version).
29
+
28
30
  class ExampleTest < Test::Unit::TestCase
29
31
 
30
32
  def test_using_from_and_single_method
@@ -36,6 +38,15 @@ class ExampleTest < Test::Unit::TestCase
36
38
  end
37
39
 
38
40
  def test_showing_delegating_methods_with_names_different_than_their_delegate_methods
39
- assert_handoff.from(Foo.new).to(:bar).for_methods(:bar_baq => :baq, :bar_ban => :ban)
41
+ assert_handoff.from(Foo.new).to(:bar).for(:bar_baq => :baq, :bar_ban => :ban)
40
42
  end
43
+
44
+ def test_specifying_args
45
+ assert_handoff.from(Foo.new).to(:bar).for(:baz).with('arg1', 2, :three)
46
+ end
47
+
48
+ def test_specifying_some_arg
49
+ assert_handoff.from(Foo.new).to(:bar).for(:baz).with_no_arguments
50
+ end
51
+
41
52
  end
@@ -9,32 +9,88 @@ module Handoff
9
9
  class AssertionTest < Test::Unit::TestCase
10
10
 
11
11
  test 'should construct instance to test and return self when from_any is called' do
12
- mock_class = mock(:new => nil)
13
- mock_delegate = mock
14
- assertion = Assertion.new(mock_delegate)
15
- mock_delegate.expectations.clear
16
- assert_same assertion, assertion.from_any(mock_class)
12
+ assertion = Assertion.new(nil)
13
+ assert_same assertion, assertion.from_any(mock('class under test', :new => nil))
17
14
  end
18
15
 
19
- test 'should create expectation on creation to guard against incomplete usage' do
20
- mock_delegate = mock
21
- Assertion.new(mock_delegate)
22
- assert_equal :called!, mock_delegate.expectations.first.method_name
23
- mock_delegate.expectations.clear
24
- end
25
-
26
- test 'should create expectations on mock_delegate and object under test once fully specified' do
27
- cut = define_class(Object, <<-EOS)
16
+ test 'should create expectations on mock_delegate and object under test upon conduct' do
17
+ cut = Class.new do
28
18
  extend Forwardable
29
19
  def_delegator :asdf, :meth
30
20
  attr_reader :asdf
31
- EOS
21
+ end
32
22
  mock_delegate = mock
33
23
  object_under_test = cut.new
34
- Assertion.new(mock_delegate).from(object_under_test).to(:asdf).for_method(:meth)
24
+ assertion = Assertion.new(mock_delegate).from(object_under_test).to(:asdf).for_method(:meth)
25
+ assertion.conduct
35
26
  assert_equal :asdf, object_under_test.mocha.expectations.first.method_name
36
27
  assert_equal :meth, mock_delegate.expectations.last.method_name
37
28
  end
38
29
 
30
+ test 'should create mock_delegate expectation with arguments when supplied' do
31
+ delegator = Class.new do
32
+ extend Forwardable
33
+ def_delegator :delegate, :meth
34
+ attr_reader :delegate
35
+ end.new
36
+ assertion = Assertion.new(mock_delegate = mock).from(delegator).to(:delegate).for(:meth)
37
+ assertion.with('arg1', :arg2, 3)
38
+ assertion.conduct
39
+ assert_true mock_delegate.expectations.first.match?(:meth, 'arg1', :arg2, 3)
40
+ assert_false mock_delegate.expectations.first.match?(:meth)
41
+ assert_false mock_delegate.expectations.first.match?(:meth, 'arg1')
42
+ end
43
+
44
+ {
45
+ :with => lambda {|assertion| assertion.with},
46
+ :with_no_arguments => lambda {|assertion| assertion.with_no_arguments}
47
+ }.each_pair do |method, proc|
48
+ test "should force no args when #{method} is used to specify none" do
49
+ delegator = Class.new do
50
+ extend Forwardable
51
+ def_delegator :delegate, :meth
52
+ attr_reader :delegate
53
+ end.new
54
+ assertion = Assertion.new(mock_delegate = mock).from(delegator).to(:delegate).for(:meth)
55
+ proc.call(assertion)
56
+ assertion.conduct
57
+ assert_true mock_delegate.expectations.first.match?(:meth)
58
+ assert_false mock_delegate.expectations.first.match?(:meth, 'arg1')
59
+ end
60
+ end
61
+
62
+ test 'should allow for args or no args if nothing specified' do
63
+ delegator = Class.new do
64
+ extend Forwardable
65
+ def_delegator :delegate, :meth
66
+ attr_reader :delegate
67
+ end.new
68
+ assertion = Assertion.new(mock_delegate = mock).from(delegator).to(:delegate).for(:meth)
69
+ assertion.conduct
70
+ assert_true mock_delegate.expectations.first.match?(:meth)
71
+ assert_true mock_delegate.expectations.first.match?(:meth, 'arg1')
72
+ end
73
+
74
+ test 'validates presence of delegator, delegate accessor, and delegating methods' do
75
+ assertion = Assertion.new(mock).from(nil).to(:asdf).for_method(:meth)
76
+ assert_raises(UsageError) { assertion.conduct }
77
+
78
+ assertion = Assertion.new(mock).from("whatever").to(nil).for_method(:meth)
79
+ assert_raises(UsageError) { assertion.conduct }
80
+
81
+ assertion = Assertion.new(mock).from("whatever").to(:asdf)
82
+ assert_raises(UsageError) { assertion.conduct }
83
+ end
84
+
85
+ test 'for, for_method, and for_methods are aliases' do
86
+ a = Assertion.new nil
87
+ assert_equal a.method(:for_methods), a.method(:for_method)
88
+ assert_equal a.method(:for_methods), a.method(:for)
89
+ end
90
+
91
+ test 'with_arguments and with are aliases' do
92
+ a = Assertion.new nil
93
+ assert_equal a.method(:with_arguments), a.method(:with)
94
+ end
39
95
  end
40
- end
96
+ end
data/test/handoff_test.rb CHANGED
@@ -13,5 +13,20 @@ class HandoffTest < Test::Unit::TestCase
13
13
  end
14
14
  Assertion.expects(:new).with(:mock_return).returns(:new_assertion)
15
15
  assert_equal :new_assertion, assert_handoff
16
+ assert_equal :new_assertion, handoff_assertions.delete(:new_assertion)
17
+ assert_true handoff_assertions.empty?
18
+ end
19
+
20
+ test 'should add a teardown method for conducting handoff assertions' do
21
+ assert_true self.class.teardown_methods.include?(:conduct_and_clear_handoffs)
22
+ end
23
+
24
+ test 'should conduct each assertion in conduct_and_clear_handoffs' do
25
+ handoff_assertions << assertion1 = mock(:conduct => nil)
26
+ handoff_assertions << assertion2 = mock(:conduct => nil)
27
+ conduct_and_clear_handoffs
28
+ assert_true handoff_assertions.empty?
29
+ assertion1.verify
30
+ assertion2.verify
16
31
  end
17
32
  end
@@ -0,0 +1,10 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class TestHelperTest < Test::Unit::TestCase
4
+
5
+ test 'assert_true passes only for true, not for non-nulls' do
6
+ assert_true true
7
+ assert_raises(Test::Unit::AssertionFailedError) { assert_true 'foo' }
8
+ end
9
+
10
+ end
metadata CHANGED
@@ -3,9 +3,9 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: handoff
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.1
7
- date: 2007-01-17 00:00:00 -05:00
8
- summary: Handoff is a fluent interface for making test assertions about simple delegation.
6
+ version: 0.1.0
7
+ date: 2007-02-17 00:00:00 -05:00
8
+ summary: Handoff is a fluent interface for making test assertions about delegation.
9
9
  require_paths:
10
10
  - lib
11
11
  email: ""
@@ -33,10 +33,12 @@ files:
33
33
  - lib/handoff.rb
34
34
  - lib/handoff/argument_normalizer.rb
35
35
  - lib/handoff/assertion.rb
36
+ - lib/handoff/usage_error.rb
36
37
  - README
37
38
  test_files:
38
39
  - test/example_test.rb
39
40
  - test/handoff_test.rb
41
+ - test/test_helper_test.rb
40
42
  - test/handoff/argument_normalizer_test.rb
41
43
  - test/handoff/assertion_test.rb
42
44
  rdoc_options: []