Class: Object

Inherits:
BasicObject
Defined in:
lib/motion-spec/mock/method_bind_unbind.rb,
lib/motion-spec/mock/mock.rb,
lib/motion-spec/mock/stub.rb,
lib/motion-spec/mock/proxy.rb,
lib/motion-spec/extensions/object.rb,
lib/motion-spec/extensions/boolean.rb

Overview

The meta-programming that allows us to pop methods on and off for mocking

Instance Method Summary collapse

Instance Method Details

#class_def(name, &block) ⇒ Object

Defines an instance method within a class



28
29
30
# File 'lib/motion-spec/mock/method_bind_unbind.rb', line 28

def class_def(name, &block)
  class_eval { define_method name, &block }
end

#false?Boolean

Returns:

  • (Boolean)


7
8
9
# File 'lib/motion-spec/extensions/boolean.rb', line 7

def false?
  false
end

#meta_def(name, &method_body) ⇒ Object

Adds methods to a metaclass



16
17
18
19
20
# File 'lib/motion-spec/mock/method_bind_unbind.rb', line 16

def meta_def(name, &method_body)
  meta_eval do
    define_method(name) { |*args, &block| method_body.call(*args, &block) }
  end
end

#meta_eval(&block) ⇒ Object



11
12
13
# File 'lib/motion-spec/mock/method_bind_unbind.rb', line 11

def meta_eval(&block)
  metaclass.instance_eval(&block)
end

#metaclassObject

The hidden singleton lurks behind everyone



5
6
7
8
9
# File 'lib/motion-spec/mock/method_bind_unbind.rb', line 5

def metaclass
  class << self
    self
  end
end

#mock!(method, options = {}, &block) ⇒ Object

Create a mock method on an object. A mock object will place an expectation on behavior and cause a test failure if it’s not fulfilled.

Examples

my_string = "a wooden rabbit"
my_string.mock!(:retreat!, :return => "run away!  run away!")
my_string.mock!(:question, :return => "what is the airspeed velocity of an unladen sparrow?")

# test/your_test.rb
my_string.retreat!    # => "run away!  run away!"
# If we let the test case end at this point, it fails with:
# Unmet expectation: #<Sparrow:1ee7> expected question


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
# File 'lib/motion-spec/mock/mock.rb', line 17

def mock!(method, options = {}, &block)
  MotionSpec::Mocks.add([self, method])

  behavior =
    if block_given?
      lambda do |*args|
        fail ArgumentError if block.arity >= 0 && args.length != block.arity

        MotionSpec::Mocks.verify([self, method])
        block.call(*args)
      end
    elsif !options[:yield].nil?
      lambda do |*_args|
        MotionSpec::Mocks.verify([self, method])
        yield(options[:yield])
      end
    else
      lambda do |*_args|
        MotionSpec::Mocks.verify([self, method])
        return options[:return]
      end
    end

  safe_meta_def method, &behavior
end

#proxy!(method, options = {}, &block) ⇒ Object

Creates a proxy method on an object. In this setup, it places an expectation on an object (like a mock) but still calls the original method. So if you want to make sure the method is called and still return its value, or simply want to invoke the side effects of a method and return a stubbed value, then you can do that.

Examples

class Parrot
  def speak!
    puts @words
  end

  def say_this(words)
    @words = words
    "I shall say #{words}!"
  end
end

# => test/your_test.rb
sqawky = Parrot.new
sqawky.proxy!(:say_this)
# Proxy method still calls original method...
sqawky.say_this("hey")   # => "I shall say hey!"
sqawky.speak!            # => "hey"

sqawky.proxy!(:say_this, "herro!")
# Even though we return a stubbed value...
sqawky.say_this("these words")   # => "herro!"
# ...the side effects are still there.
sqawky.speak!                    # => "these words"

TODO: This implementation is still very rough. Needs refactoring and refining. Won’t work on ActiveRecord attributes, for example.



37
38
39
40
41
42
43
44
45
# File 'lib/motion-spec/mock/proxy.rb', line 37

def proxy!(method, options = {}, &block)
  MotionSpec::Mocks.add([self, method])

  if respond_to?(method)
    proxy_existing_method(method, options, &block)
  else
    proxy_missing_method(method, options, &block)
  end
end

#reset(method_name) ⇒ Object



32
33
34
# File 'lib/motion-spec/mock/method_bind_unbind.rb', line 32

def reset(method_name)
  metaclass.restore_original_method(method_name)
end

#safe_meta_def(name, &method_body) ⇒ Object



22
23
24
25
# File 'lib/motion-spec/mock/method_bind_unbind.rb', line 22

def safe_meta_def(name, &method_body)
  metaclass.remember_original_method(name)
  meta_def(name, &method_body)
end

#should(*args, &block) ⇒ Object



3
4
5
# File 'lib/motion-spec/extensions/object.rb', line 3

def should(*args, &block)
  MotionSpec::Should.new(self).be(*args, &block)
end

#should_not_call(method) ⇒ Object



43
44
45
46
47
# File 'lib/motion-spec/mock/mock.rb', line 43

def should_not_call(method)
  safe_meta_def(method) do
    should.flunk "Umet expectations: #{method} expected to not be called"
  end
end

#stub!(method_name, options = {}, &stubbed) ⇒ Object

Create a stub method on an object. Simply returns a value for a method call on an object.

Examples

my_string = "a wooden rabbit"
my_string.stub!(:retreat!, :return => "run away!  run away!")

# test/your_test.rb
my_string.retreat!    # => "run away!  run away!"


14
15
16
17
18
19
20
# File 'lib/motion-spec/mock/stub.rb', line 14

def stub!(method_name, options = {}, &stubbed)
  MotionSpec::Stubs.add(self, method_name)

  behavior = (block_given? ? stubbed : -> { return options[:return] })

  safe_meta_def method_name, &behavior
end

#true?Boolean

Returns:

  • (Boolean)


3
4
5
# File 'lib/motion-spec/extensions/boolean.rb', line 3

def true?
  false
end