Module: Mocoso

Defined in:
lib/mocoso.rb

Overview

Yet Another Simple Stub & Mock library, that also:

- Always restore stubbed methods to their original implementations.
- Doesn't allow to stub or mock undefined methods.
- Doesn't monkey-patch any class or object.
- Test-framework agnostic (Doesn't need integration code).

Setup

Execute:

gem install mocoso

Usage

Quick start:

require "cutest"
require "mocoso"

include Mocoso

test "mocking a class method" do
  user = User.new

  expect(User, :find, with: [1], return: user) do
    assert_equal user, User.find(1)
  end

  assert_equal nil, User.find(1)
end

test "stubbing an instance method" do
  user = User.new

  stub(user, :valid?, true) do
    assert user.valid?
  end

  assert !user.valid?
end

Note: this example uses the test framework Cutest:

Mocoso is inspired by (stolen from) Override, Minitest::Mock and Mocha.

Constant Summary collapse

ExpectationError =

Raised by #expect when a expectation is not fulfilled.

Mocoso.expect(object, :method, with: "argument", return: nil) do
  object.method("unexpected argument")
  # => Mocoso::ExpectationError: Expected ["argument"], got ["unexpected argument"]
end
Class.new(StandardError)

Instance Method Summary collapse

Instance Method Details

#expect(object, method, options) ⇒ Object

Expects that method method is called with the arguments specified in the :with option (defaults to [] if it's not given) and return the value specified in the :return option. If expectations are not met, it raises Mocoso::ExpectationError error. It uses #stub internally, so it will restore the methods to their original implementation after the block is executed.

class User < Model
end

user = User[1]

Mocoso.expect(user, :update, with: [{ name: "new name" }], return: true) do
  subject.update(unexpected: nil)
  # => Mocoso::ExpectationError: Expected [{:name=>"new name"}], got [{:unexpected=>nil}]

  user.update(name: "new name")
  # => true
end

109
110
111
112
113
114
115
116
117
# File 'lib/mocoso.rb', line 109

def expect(object, method, options)
  expectation = ->(*params) {
    with = options.fetch(:with, [])
    raise ExpectationError, "Expected #{with}, got #{params}" if params != with
    options.fetch(:return)
  }

  stub(object, method, expectation, &proc)
end

#stub(object, method, result) ⇒ Object

Rewrites each method from methods and defined in object. methods is a Hash that represents stubbed method name symbols as keys and corresponding return values as values. The methods are restored after the given block is executed.

 = SignupForm.new params[:user]

.valid? # => false

Mocoso.stub(, :valid?, true) do
  .valid? # => true
end

You can pass a callable object (responds to call) as a value:

Mocoso.stub(subject, :foo, -> { "foo" }) do
  subject.foo # => "foo"
end

Mocoso.stub(subject, :bar, ->(value) { value }) do
  subject.bar("foo") # => "foo"
end

76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/mocoso.rb', line 76

def stub(object, method, result)
  metaclass = object.singleton_class
  original = object.method(method)

  metaclass.send(:define_method, method) do |*args|
    result.respond_to?(:call) ? result.(*args) : result
  end

  yield
ensure
  metaclass.send(:undef_method, method)
  metaclass.send(:define_method, method, original)
end