File: expectation_builder.rb

package info (click to toggle)
ruby-flexmock 3.0.1-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 836 kB
  • sloc: ruby: 7,572; makefile: 6
file content (139 lines) | stat: -rw-r--r-- 4,581 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/env ruby

#---
# Copyright 2003-2013 by Jim Weirich (jim.weirich@gmail.com).
# All rights reserved.

# Permission is granted for use, copying, modification, distribution,
# and distribution of modified versions of this work as long as the
# above copyright notice is included.
#+++

require 'flexmock/composite_expectation'

class FlexMock

  class ExpectationBuilder
    # :call-seq:
    #   parse_should_args(args) { |symbol| ... }
    #
    # This method provides common handling for the various should_receive
    # argument lists. It sorts out the differences between symbols, arrays and
    # hashes, and identifies the method names specified by each.  As each
    # method name is identified, create a mock expectation for it using the
    # supplied block.
    def parse_should_args(mock, args, kw, &block)  # :nodoc:
      result = CompositeExpectation.new
      args.each do |arg|
        case arg
        when Hash
          arg.each do |k, v|
            exp = create_expectation(mock, k, &block).and_return(v)
            result.add(exp)
          end
        when Symbol, String
          result.add(create_expectation(mock, arg, &block))
        end
      end

      kw.each do |k, v|
        exp = create_expectation(mock, k, &block).and_return(v)
        result.add(exp)
      end

      result
    end

    # Create an expectation for the name on this mock. For simple
    # mocks, this is done by calling the provided block parameter and
    # letting the calling site handle the creation of the expectation
    # (which differs between full mocks and partial mocks).
    #
    # If the name_chain contains demeter mocking chains, then the
    # process is more complex. A series of mocks are created, each
    # component of the chain returning the next mock until the
    # expectation for the last component is returned.
    def create_expectation(mock, name_chain, &block)
      names = name_chain.to_s.split('.').map { |n| n.to_sym }
      check_method_names(names)
      if names.size == 1
        block.call(names.first)
      elsif names.size > 1
        create_demeter_chain(mock, names)
      else
        fail ArgumentError, "Empty list of names"
      end
    end

    # Build the chain of mocks for demeter style mocking.
    #
    # This method builds a chain of mocks to support demeter style
    # mocking.  Given a mock chain of "first.second.third.last", we
    # must build a chain of mock methods that return the next mock in
    # the chain.  The expectation for the last method of the chain is
    # returned as the result of the method.
    #
    # Things to consider:
    #
    # * The expectations for all methods but the last in the chain
    #   will be setup to expect no parameters and to return the next
    #   mock in the chain.
    #
    # * It could very well be the case that several demeter chains
    #   will be defined on a single mock object, and those chains
    #   could share some of the same methods (e.g. "mock.one.two.read"
    #   and "mock.one.two.write" both share the methods "one" and
    #   "two"). It is important that the shared methods return the
    #   same mocks in both chains.
    #
    def create_demeter_chain(mock, names)
      container = mock.flexmock_container
      last_method = names.pop
      names.each do |name|
        exp = mock.flexmock_find_expectation(name)
        if exp
          next_mock = exp._return_value([], {}, nil)
          check_proper_mock(next_mock, name)
        else
          next_mock = container.flexmock("demeter_#{name}")
          mock.should_receive(name).and_return(next_mock)
        end
        mock = next_mock
      end
      mock.should_receive(last_method)
    end

    # Check that the given mock is a real FlexMock mock.
    def check_proper_mock(mock, method_name)
      unless mock.respond_to?(:should_receive)
        fail FlexMock::UsageError,
          "Conflicting mock declaration for '#{method_name}' in demeter style mock"
      end
    end

    METHOD_NAME_ALTS = [
      '[A-Za-z_][A-Za-z0-9_]*[=!?]?',
      '\[\]=?',
      '\*\\*',
      '<<',
      '>>',
      '<=>',
      '[<>=!]=',
      '[=!]~',
      '===',
      '[-+]@',
      '[-+\*\/%&^|<>~`!]'
    ].join("|")
    METHOD_NAME_RE = /^(#{METHOD_NAME_ALTS})$/

    # Check that all the names in the list are valid method names.
    def check_method_names(names)
      names.each do |name|
        fail FlexMock::UsageError, "Ill-formed method name '#{name}'" if
          name.to_s !~ METHOD_NAME_RE
      end
    end
  end

  EXP_BUILDER = ExpectationBuilder.new
end