├── .gemtest ├── docs ├── CNAME ├── css │ └── common.css ├── frames.html ├── file.COPYING.html ├── file_list.html └── top-level-namespace.html ├── .github └── FUNDING.yml ├── lib ├── mocha.rb └── mocha │ ├── version.rb │ ├── is_a.rb │ ├── ruby_version.rb │ ├── macos_version.rb │ ├── logger.rb │ ├── minitest.rb │ ├── test_unit.rb │ ├── debug.rb │ ├── raised_exception.rb │ ├── not_initialized_error.rb │ ├── stubbing_error.rb │ ├── thrown_object.rb │ ├── thrower.rb │ ├── single_return_value.rb │ ├── integration │ ├── assertion_counter.rb │ ├── minitest │ │ ├── exception_translation.rb │ │ └── adapter.rb │ ├── monkey_patcher.rb │ ├── minitest.rb │ ├── test_unit.rb │ └── test_unit │ │ └── adapter.rb │ ├── change_state_side_effect.rb │ ├── error_with_filtered_backtrace.rb │ ├── in_state_ordering_constraint.rb │ ├── instance_method.rb │ ├── expectation_error.rb │ ├── any_instance_method.rb │ ├── argument_iterator.rb │ ├── method_matcher.rb │ ├── backtrace_filter.rb │ ├── exception_raiser.rb │ ├── yield_parameters.rb │ ├── deprecation.rb │ ├── detection │ ├── minitest.rb │ └── test_unit.rb │ ├── block_matcher.rb │ ├── return_values.rb │ ├── parameter_matchers │ ├── instance_methods.rb │ ├── anything.rb │ ├── any_parameters.rb │ ├── equals.rb │ ├── is_a.rb │ ├── not.rb │ ├── instance_of.rb │ ├── all_of.rb │ ├── kind_of.rb │ ├── has_key.rb │ ├── regexp_matches.rb │ ├── has_value.rb │ ├── any_of.rb │ ├── yaml_equivalent.rb │ ├── has_keys.rb │ ├── equivalent_uri.rb │ ├── has_entries.rb │ ├── optionally.rb │ ├── positional_or_keyword_hash.rb │ └── base.rb │ ├── names.rb │ ├── receivers.rb │ ├── central.rb │ ├── parameters_matcher.rb │ ├── sequence.rb │ ├── parameter_matchers.rb │ ├── expectation_error_factory.rb │ ├── expectation_list.rb │ ├── inspect.rb │ ├── invocation.rb │ ├── class_methods.rb │ ├── hooks.rb │ └── stubbed_method.rb ├── .gitignore ├── gemfiles ├── Gemfile.minitest.latest └── Gemfile.test-unit.latest ├── test ├── simple_counter.rb ├── hashlike.rb ├── assertions.rb ├── integration │ ├── minitest_test.rb │ └── test_unit_test.rb ├── unit │ ├── string_inspect_test.rb │ ├── parameter_matchers │ │ ├── stub_matcher.rb │ │ ├── anything_test.rb │ │ ├── equals_test.rb │ │ ├── is_a_test.rb │ │ ├── kind_of_test.rb │ │ ├── instance_of_test.rb │ │ ├── not_test.rb │ │ ├── yaml_equivalent_test.rb │ │ ├── all_of_test.rb │ │ ├── any_of_test.rb │ │ ├── regexp_matches_test.rb │ │ ├── equivalent_uri_test.rb │ │ ├── responds_with_test.rb │ │ ├── has_key_test.rb │ │ ├── has_value_test.rb │ │ └── has_keys_test.rb │ ├── single_return_value_test.rb │ ├── module_methods_test.rb │ ├── array_inspect_test.rb │ ├── date_time_inspect_test.rb │ ├── thrower_test.rb │ ├── change_state_side_effect_test.rb │ ├── hash_inspect_test.rb │ ├── hooks_test.rb │ ├── method_matcher_test.rb │ ├── backtrace_filter_test.rb │ ├── in_state_ordering_constraint_test.rb │ ├── object_inspect_test.rb │ ├── exception_raiser_test.rb │ ├── object_methods_test.rb │ ├── class_methods_test.rb │ ├── configuration_test.rb │ └── receivers_test.rb ├── deprecation_disabler.rb ├── acceptance │ ├── bug_21563_test.rb │ ├── unexpected_invocation_test.rb │ ├── bug_21465_test.rb │ ├── issue_457_test.rb │ ├── array_flatten_test.rb │ ├── bug_18914_test.rb │ ├── issue_524_test.rb │ ├── stubbing_same_class_method_on_parent_and_child_classes_test.rb │ ├── acceptance_test_helper.rb │ ├── stub_method_defined_on_module_and_aliased_test.rb │ ├── throw_test.rb │ ├── raise_exception_test.rb │ ├── partial_mocks_test.rb │ ├── issue_272_test.rb │ ├── stub_test.rb │ ├── exception_rescue_test.rb │ ├── stub_everything_test.rb │ ├── return_value_test.rb │ ├── issue_70_test.rb │ ├── issue_65_test.rb │ ├── stubbing_method_accepting_block_parameter_test.rb │ ├── expectations_on_multiple_methods_test.rb │ ├── multiple_yielding_test.rb │ ├── states_test.rb │ ├── stub_instance_method_defined_on_active_record_association_proxy_test.rb │ ├── stubba_test_result_test.rb │ ├── stub_any_instance_method_defined_on_superclass_test.rb │ ├── stubbing_nil_test.rb │ ├── prepend_test.rb │ ├── stub_class_method_defined_on_active_record_association_proxy_test.rb │ ├── optional_parameters_test.rb │ ├── yielding_test.rb │ ├── stub_class_method_defined_on_module_test.rb │ ├── stubbing_method_unnecessarily_test.rb │ ├── stub_instance_method_defined_on_singleton_class_test.rb │ ├── mocked_methods_dispatch_test.rb │ ├── stub_instance_method_defined_on_class_and_aliased_test.rb │ ├── stub_instance_method_defined_on_class_test.rb │ ├── stub_instance_method_defined_on_superclass_test.rb │ ├── stubbing_error_backtrace_test.rb │ ├── mocha_test_result_test.rb │ └── failure_messages_test.rb ├── test_unit_result.rb ├── method_definer.rb ├── execution_point.rb ├── minitest_result.rb ├── test_helper.rb └── test_runner.rb ├── COPYING.md ├── CONTRIBUTING.md ├── .yardopts ├── Gemfile ├── .rubocop_todo.yml ├── mocha.gemspec ├── MIT-LICENSE.md └── .rubocop.yml /.gemtest: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | mocha.jamesmead.org 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: floehopper 2 | -------------------------------------------------------------------------------- /lib/mocha.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/version' 2 | -------------------------------------------------------------------------------- /docs/css/common.css: -------------------------------------------------------------------------------- 1 | /* Override this file with custom rules */ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | TODO 2 | Gemfile*.lock 3 | pkg 4 | tmp 5 | .yardoc 6 | -------------------------------------------------------------------------------- /lib/mocha/version.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | VERSION = '2.4.5'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /lib/mocha/is_a.rb: -------------------------------------------------------------------------------- 1 | class Object 2 | # :stopdoc: 3 | 4 | alias_method :__is_a__, :is_a? 5 | 6 | # :startdoc: 7 | end 8 | -------------------------------------------------------------------------------- /lib/mocha/ruby_version.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | RUBY_V27_PLUS = Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.7') 3 | end 4 | -------------------------------------------------------------------------------- /gemfiles/Gemfile.minitest.latest: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec :path=>"../" 4 | 5 | group :development do 6 | gem "rake" 7 | gem "minitest" 8 | end 9 | -------------------------------------------------------------------------------- /gemfiles/Gemfile.test-unit.latest: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec :path=>"../" 4 | 5 | group :development do 6 | gem "rake" 7 | gem "test-unit" 8 | end 9 | -------------------------------------------------------------------------------- /lib/mocha/macos_version.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | MACOS = /darwin/.match(RUBY_PLATFORM) 3 | MACOS_VERSION = MACOS && /darwin(\d+)/.match(RUBY_PLATFORM)[1].to_i 4 | MACOS_MOJAVE_VERSION = 18 5 | end 6 | -------------------------------------------------------------------------------- /test/simple_counter.rb: -------------------------------------------------------------------------------- 1 | class SimpleCounter 2 | attr_reader :count 3 | 4 | def initialize 5 | @count = 0 6 | end 7 | 8 | def increment 9 | @count += 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/mocha/logger.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | class Logger 3 | def initialize(io) 4 | @io = io 5 | end 6 | 7 | def warn(message) 8 | @io.puts "WARNING: #{message}" 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/hashlike.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | 3 | class Hashlike 4 | extend Forwardable 5 | 6 | def_delegators :@hash, :keys, :[] 7 | 8 | def initialize(hash = {}) 9 | @hash = hash 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/mocha/minitest.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/ruby_version' 2 | require 'mocha/integration/minitest' 3 | 4 | unless Mocha::Integration::Minitest.activate 5 | raise "Minitest must be loaded *before* `require 'mocha/minitest'`." 6 | end 7 | -------------------------------------------------------------------------------- /COPYING.md: -------------------------------------------------------------------------------- 1 | Copyright James Mead 2006 2 | 3 | You may use, copy and redistribute this library under the same terms as [Ruby itself](https://www.ruby-lang.org/en/about/license.txt) or under the [MIT license](https://mit-license.org/). 4 | -------------------------------------------------------------------------------- /lib/mocha/test_unit.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/ruby_version' 2 | require 'mocha/integration/test_unit' 3 | 4 | unless Mocha::Integration::TestUnit.activate 5 | raise "Test::Unit must be loaded *before* `require 'mocha/test_unit'`." 6 | end 7 | -------------------------------------------------------------------------------- /test/assertions.rb: -------------------------------------------------------------------------------- 1 | module Assertions 2 | def assert_method_visibility(object, method_name, visibility) 3 | assert object.send("#{visibility}_methods").include?(method_name), "#{method_name} is not #{visibility}" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/mocha/debug.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | module Debug 3 | OPTIONS = (ENV['MOCHA_OPTIONS'] || '').split(',').freeze 4 | 5 | def self.puts(message) 6 | warn(message) if OPTIONS.include?('debug') 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/integration/minitest_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'mocha/minitest' 4 | require 'integration/shared_tests' 5 | 6 | class MinitestTest < Mocha::TestCase 7 | include SharedTests 8 | end 9 | -------------------------------------------------------------------------------- /test/integration/test_unit_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'mocha/test_unit' 4 | require 'integration/shared_tests' 5 | 6 | class TestUnitTest < Mocha::TestCase 7 | include SharedTests 8 | end 9 | -------------------------------------------------------------------------------- /lib/mocha/raised_exception.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | class RaisedException 3 | def initialize(exception) 4 | @exception = exception 5 | end 6 | 7 | def mocha_inspect 8 | "raised #{@exception}" 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/mocha/not_initialized_error.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/error_with_filtered_backtrace' 2 | 3 | module Mocha 4 | # Exception raised when Mocha has not been initialized, e.g. outside the 5 | # context of a test. 6 | class NotInitializedError < ErrorWithFilteredBacktrace; end 7 | end 8 | -------------------------------------------------------------------------------- /lib/mocha/stubbing_error.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/error_with_filtered_backtrace' 2 | 3 | module Mocha 4 | # Exception raised when stubbing a particular method is not allowed. 5 | # 6 | # @see Configuration.prevent 7 | class StubbingError < ErrorWithFilteredBacktrace; end 8 | end 9 | -------------------------------------------------------------------------------- /lib/mocha/thrown_object.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | class ThrownObject 3 | def initialize(tag, value = nil) 4 | @tag = tag 5 | @value = value 6 | end 7 | 8 | def mocha_inspect 9 | "threw (#{@tag.mocha_inspect}, #{@value.mocha_inspect})" 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/mocha/thrower.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | class Thrower 3 | def initialize(tag, object = nil) 4 | @tag = tag 5 | @object = object 6 | end 7 | 8 | def evaluate(invocation) 9 | invocation.threw(@tag, @object) 10 | throw @tag, @object 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/mocha/single_return_value.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/is_a' 2 | 3 | module Mocha 4 | class SingleReturnValue 5 | def initialize(value) 6 | @value = value 7 | end 8 | 9 | def evaluate(invocation) 10 | invocation.returned(@value) 11 | @value 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/mocha/integration/assertion_counter.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | module Integration 3 | class AssertionCounter 4 | def initialize(test_case) 5 | @test_case = test_case 6 | end 7 | 8 | def increment 9 | @test_case.assert(true) 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/mocha/change_state_side_effect.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | class ChangeStateSideEffect 3 | def initialize(state) 4 | @state = state 5 | end 6 | 7 | def perform 8 | @state.activate 9 | end 10 | 11 | def mocha_inspect 12 | "then #{@state.mocha_inspect}" 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/unit/string_inspect_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require 'mocha/inspect' 3 | 4 | class StringInspectTest < Mocha::TestCase 5 | def test_should_use_default_inspect_method 6 | string = 'my_string' 7 | assert_equal %("my_string"), string.mocha_inspect 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | * Pull requests are welcomed. 2 | * Fork the repository. 3 | * Make your changes in a branch. 4 | * Add/modify/remove tests as appropriate. 5 | * Open a pull request based on a branch on your fork. 6 | * Wait for your pull request build to pass on [Circle CI](https://app.circleci.com/pipelines/github/freerange/mocha). 7 | * Pull requests with failing tests will not be accepted. 8 | -------------------------------------------------------------------------------- /lib/mocha/error_with_filtered_backtrace.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/backtrace_filter' 2 | 3 | module Mocha 4 | # @private 5 | class ErrorWithFilteredBacktrace < StandardError 6 | # @private 7 | def initialize(message = nil, backtrace = []) 8 | super(message) 9 | filter = BacktraceFilter.new 10 | set_backtrace(filter.filtered(backtrace)) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/mocha/in_state_ordering_constraint.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | class InStateOrderingConstraint 3 | def initialize(state_predicate) 4 | @state_predicate = state_predicate 5 | end 6 | 7 | def allows_invocation_now? 8 | @state_predicate.active? 9 | end 10 | 11 | def mocha_inspect 12 | "when #{@state_predicate.mocha_inspect}" 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/deprecation_disabler.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/deprecation' 2 | 3 | module DeprecationDisabler 4 | def disable_deprecations 5 | original_mode = Mocha::Deprecation.mode 6 | Mocha::Deprecation.mode = :disabled 7 | begin 8 | yield 9 | ensure 10 | Mocha::Deprecation.mode = original_mode 11 | end 12 | end 13 | 14 | module_function :disable_deprecations 15 | end 16 | -------------------------------------------------------------------------------- /lib/mocha/instance_method.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/stubbed_method' 2 | 3 | module Mocha 4 | class InstanceMethod < StubbedMethod 5 | private 6 | 7 | def mock_owner 8 | stubbee 9 | end 10 | 11 | def stubbee_method(method_name) 12 | stubbee._method(method_name) 13 | end 14 | 15 | def original_method_owner 16 | stubbee.singleton_class 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/mocha/expectation_error.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | # Default exception class raised when an unexpected invocation or an unsatisfied expectation occurs. 3 | # 4 | # Authors of test libraries may use +Mocha::ExpectationErrorFactory+ to have Mocha raise a different exception. 5 | # 6 | # @see Mocha::ExpectationErrorFactory 7 | class ExpectationError < Exception; end # rubocop:disable Lint/InheritException 8 | end 9 | -------------------------------------------------------------------------------- /lib/mocha/any_instance_method.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/stubbed_method' 2 | 3 | module Mocha 4 | class AnyInstanceMethod < StubbedMethod 5 | private 6 | 7 | def mock_owner 8 | stubbee.any_instance 9 | end 10 | 11 | def stubbee_method(method_name) 12 | stubbee.instance_method(method_name) 13 | end 14 | 15 | def original_method_owner 16 | stubbee 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/mocha/argument_iterator.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | class ArgumentIterator 3 | def initialize(argument) 4 | @argument = argument 5 | end 6 | 7 | def each 8 | if @argument.is_a?(Hash) 9 | @argument.each do |method_name, return_value| 10 | yield method_name, return_value 11 | end 12 | else 13 | yield @argument 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/mocha/method_matcher.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | class MethodMatcher 3 | attr_reader :expected_method_name 4 | 5 | def initialize(expected_method_name) 6 | @expected_method_name = expected_method_name 7 | end 8 | 9 | def match?(actual_method_name) 10 | @expected_method_name == actual_method_name.to_sym 11 | end 12 | 13 | def mocha_inspect 14 | @expected_method_name.to_s 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/mocha/backtrace_filter.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | class BacktraceFilter 3 | LIB_DIRECTORY = File.expand_path(File.join(File.dirname(__FILE__), '..')) + File::SEPARATOR 4 | 5 | def initialize(lib_directory = LIB_DIRECTORY) 6 | @lib_directory = lib_directory 7 | end 8 | 9 | def filtered(backtrace) 10 | backtrace.reject { |location| File.expand_path(location).start_with?(@lib_directory) } 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/mocha/exception_raiser.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | class ExceptionRaiser 3 | def initialize(exception, message) 4 | @exception = exception 5 | @message = message 6 | end 7 | 8 | def evaluate(invocation) 9 | invocation.raised(@exception) 10 | raise @exception, @exception.to_s if @exception.is_a?(Module) && (@exception < Interrupt) 11 | raise @exception, @message if @message 12 | raise @exception 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/stub_matcher.rb: -------------------------------------------------------------------------------- 1 | module Stub 2 | class Matcher 3 | attr_accessor :value 4 | 5 | def initialize(matches) 6 | @matches = matches 7 | end 8 | 9 | def matches?(available_parameters) 10 | value = available_parameters.shift 11 | @value = value 12 | @matches 13 | end 14 | 15 | def mocha_inspect 16 | "matcher(#{@matches})" 17 | end 18 | 19 | def to_matcher 20 | self 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/mocha/integration/minitest/exception_translation.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/expectation_error' 2 | 3 | module Mocha 4 | module Integration 5 | module Minitest 6 | def self.translate(exception) 7 | return exception unless exception.is_a?(::Mocha::ExpectationError) 8 | translated_exception = ::Minitest::Assertion.new(exception.message) 9 | translated_exception.set_backtrace(exception.backtrace) 10 | translated_exception 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/unit/single_return_value_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'mocha/invocation' 4 | require 'mocha/single_return_value' 5 | 6 | class SingleReturnValueTest < Mocha::TestCase 7 | include Mocha 8 | 9 | def new_invocation 10 | Invocation.new(:irrelevant, :irrelevant) 11 | end 12 | 13 | def test_should_return_value 14 | value = SingleReturnValue.new('value') 15 | assert_equal 'value', value.evaluate(new_invocation) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/unit/module_methods_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require 'mocha/object_methods' 3 | 4 | class ModuleMethodsTest < Mocha::TestCase 5 | def setup 6 | @module = Module.new.extend(Mocha::ObjectMethods) 7 | end 8 | 9 | def test_should_use_stubba_module_method_for_module 10 | assert_equal Mocha::InstanceMethod, @module.stubba_method 11 | end 12 | 13 | def test_should_stub_self_for_module 14 | assert_equal @module, @module.stubba_object 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/mocha/yield_parameters.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | class YieldParameters 3 | def initialize 4 | @parameter_groups = [] 5 | end 6 | 7 | def next_invocation 8 | case @parameter_groups.length 9 | when 0 then [] 10 | when 1 then @parameter_groups.first 11 | else @parameter_groups.shift 12 | end 13 | end 14 | 15 | def add(*parameter_groups) 16 | @parameter_groups << parameter_groups.map do |pg| 17 | pg.is_a?(Array) ? pg : [pg] 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/mocha/deprecation.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/backtrace_filter' 2 | 3 | module Mocha 4 | class Deprecation 5 | class << self 6 | attr_accessor :mode, :messages 7 | 8 | def warning(*messages) 9 | message = messages.join 10 | @messages << message 11 | return if mode == :disabled 12 | filter = BacktraceFilter.new 13 | location = filter.filtered(caller)[0] 14 | warn "Mocha deprecation warning at #{location}: #{message}" 15 | end 16 | end 17 | 18 | self.mode = :enabled 19 | self.messages = [] 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/acceptance/bug_21563_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class Bug21563Test < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_allow_stubbing_of_verified_method 15 | test_result = run_as_test do 16 | object = Object.new 17 | object.stubs(:verified?).returns(false) 18 | assert !object.verified? 19 | end 20 | assert_passed(test_result) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/test_unit_result.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit/testresult' 2 | 3 | class TestUnitResult 4 | def self.build_test_result 5 | test_result = Test::Unit::TestResult.new 6 | class << test_result 7 | attr_reader :failures, :errors 8 | def failure_messages 9 | failures.map(&:message) 10 | end 11 | 12 | def failure_message_lines 13 | failure_messages.map { |message| message.split("\n") }.flatten 14 | end 15 | 16 | def error_messages 17 | errors.map(&:message) 18 | end 19 | end 20 | test_result 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/anything_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | require 'mocha/parameter_matchers/anything' 4 | require 'mocha/inspect' 5 | 6 | class AnythingTest < Mocha::TestCase 7 | include Mocha::ParameterMatchers 8 | 9 | def test_should_match_anything 10 | matcher = anything 11 | assert matcher.matches?([:something]) 12 | assert matcher.matches?([{ 'x' => 'y' }]) 13 | end 14 | 15 | def test_should_describe_matcher 16 | matcher = anything 17 | assert_equal 'anything', matcher.mocha_inspect 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/method_definer.rb: -------------------------------------------------------------------------------- 1 | module MethodDefiner 2 | def define_instance_method(object, method_symbol, &block) 3 | object.singleton_class.send(:define_method, method_symbol, block) 4 | end 5 | 6 | def replace_instance_method(object, method_symbol, &block) 7 | raise "Cannot replace #{method_symbol} as #{self} does not respond to it." unless object.respond_to?(method_symbol) 8 | define_instance_method(object, method_symbol, &block) 9 | end 10 | 11 | def define_instance_accessor(object, *symbols) 12 | symbols.each { |symbol| object.singleton_class.send(:attr_accessor, symbol) } 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/mocha/detection/minitest.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | module Detection 3 | module Minitest 4 | def self.testcase 5 | if defined?(::Minitest::Test) 6 | ::Minitest::Test 7 | elsif defined?(::Minitest::Unit::TestCase) 8 | ::Minitest::Unit::TestCase 9 | end 10 | end 11 | 12 | def self.version 13 | if defined?(::Minitest::Unit::VERSION) 14 | ::Minitest::Unit::VERSION 15 | elsif defined?(::Minitest::VERSION) 16 | ::Minitest::VERSION 17 | else 18 | '0.0.0' 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/unit/array_inspect_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require 'mocha/inspect' 3 | 4 | class ArrayInspectTest < Mocha::TestCase 5 | def test_should_return_string_representation_of_array 6 | array = [1, 2] 7 | assert_equal '[1, 2]', array.mocha_inspect 8 | end 9 | 10 | def test_should_return_unwrapped_array_when_wrapped_is_false 11 | array = [1, 2] 12 | assert_equal '1, 2', array.mocha_inspect(false) 13 | end 14 | 15 | def test_should_use_mocha_inspect_on_each_item 16 | array = [1, 2, 'chris'] 17 | assert_equal %([1, 2, "chris"]), array.mocha_inspect 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/mocha/block_matcher.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | module BlockMatchers 3 | class OptionalBlock 4 | def match?(_actual_block) 5 | true 6 | end 7 | 8 | def mocha_inspect; end 9 | end 10 | 11 | class BlockGiven 12 | def match?(actual_block) 13 | !actual_block.nil? 14 | end 15 | 16 | def mocha_inspect 17 | 'with block given' 18 | end 19 | end 20 | 21 | class NoBlockGiven 22 | def match?(actual_block) 23 | actual_block.nil? 24 | end 25 | 26 | def mocha_inspect 27 | 'with no block given' 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /docs/frames.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha 2.4.5 6 | 7 | 18 | 22 | 23 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --output-dir docs 2 | --no-private 3 | lib/mocha/api.rb 4 | lib/mocha/hooks.rb 5 | lib/mocha/mock.rb 6 | lib/mocha/expectation.rb 7 | lib/mocha/object_methods.rb 8 | lib/mocha/class_methods.rb 9 | lib/mocha/parameter_matchers.rb 10 | lib/mocha/parameter_matchers 11 | lib/mocha/state_machine.rb 12 | lib/mocha/sequence.rb 13 | lib/mocha/configuration.rb 14 | lib/mocha/expectation_error_factory.rb 15 | lib/mocha/expectation_error.rb 16 | lib/mocha/stubbing_error.rb 17 | lib/mocha/unexpected_invocation.rb 18 | lib/mocha/integration/test_unit/adapter.rb 19 | lib/mocha/integration/minitest/adapter.rb 20 | - 21 | RELEASE.md 22 | COPYING.md 23 | MIT-LICENSE.md 24 | -------------------------------------------------------------------------------- /lib/mocha/return_values.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/single_return_value' 2 | 3 | module Mocha 4 | class ReturnValues 5 | def self.build(*values) 6 | new(*values.map { |value| SingleReturnValue.new(value) }) 7 | end 8 | 9 | attr_accessor :values 10 | 11 | def initialize(*values) 12 | @values = values 13 | end 14 | 15 | def next(invocation) 16 | case @values.length 17 | when 0 then nil 18 | when 1 then @values.first.evaluate(invocation) 19 | else @values.shift.evaluate(invocation) 20 | end 21 | end 22 | 23 | def +(other) 24 | self.class.new(*(@values + other.values)) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/unit/date_time_inspect_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require 'mocha/inspect' 3 | 4 | class DateTimeInspectTest < Mocha::TestCase 5 | def test_should_use_include_date_in_seconds 6 | time = Time.now 7 | assert_equal "#{time.inspect} (#{time.to_f} secs)", time.mocha_inspect 8 | end 9 | 10 | def test_should_use_to_s_for_date 11 | date = Date.new(2006, 1, 1) 12 | assert_equal date.to_s, date.mocha_inspect 13 | end 14 | 15 | def test_should_use_to_s_for_datetime 16 | datetime = DateTime.new(2006, 1, 1) # rubocop:disable Style/DateTime 17 | assert_equal datetime.to_s, datetime.mocha_inspect 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/equals_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | require 'mocha/parameter_matchers/equals' 4 | require 'mocha/inspect' 5 | 6 | class EqualsTest < Mocha::TestCase 7 | include Mocha::ParameterMatchers 8 | 9 | def test_should_match_object_that_equals_value 10 | matcher = equals('x') 11 | assert matcher.matches?(['x']) 12 | end 13 | 14 | def test_should_not_match_object_that_does_not_equal_value 15 | matcher = equals('x') 16 | assert !matcher.matches?(['y']) 17 | end 18 | 19 | def test_should_describe_matcher 20 | matcher = equals('x') 21 | assert_equal %("x"), matcher.mocha_inspect 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/unit/thrower_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'mocha/invocation' 4 | require 'mocha/thrower' 5 | 6 | class ThrowerTest < Mocha::TestCase 7 | include Mocha 8 | 9 | def new_invocation 10 | Invocation.new(:irrelevant, :irrelevant) 11 | end 12 | 13 | def test_should_throw_tag 14 | thrower = Thrower.new(:tag) 15 | assert_throws(:tag) { thrower.evaluate(new_invocation) } 16 | end 17 | 18 | def test_should_throw_tag_with_return_value 19 | thrower = Thrower.new(:tag, 'return-value') 20 | return_value = catch(:tag) { thrower.evaluate(new_invocation) } 21 | assert_equal 'return-value', return_value 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | # rubocop:disable Bundler/DuplicatedGem 6 | if RUBY_VERSION < '2.2' 7 | gem 'rake', '~> 12.3.3' 8 | else 9 | gem 'rake' 10 | end 11 | # rubocop:enable Bundler/DuplicatedGem 12 | 13 | gem 'introspection', '~> 0.0.1' 14 | 15 | # Avoid breaking change in psych v4 (https://bugs.ruby-lang.org/issues/17866) 16 | if RUBY_VERSION >= '3.1.0' 17 | gem 'psych', '< 4' 18 | end 19 | 20 | if RUBY_VERSION >= '2.2.0' 21 | # No test libraries in standard library 22 | gem 'minitest' 23 | end 24 | if RUBY_VERSION >= '2.2.0' 25 | gem 'rubocop', '<= 0.58.2' 26 | end 27 | if ENV['MOCHA_GENERATE_DOCS'] 28 | gem 'redcarpet' 29 | gem 'yard' 30 | end 31 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/is_a_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | require 'mocha/parameter_matchers/is_a' 4 | require 'mocha/inspect' 5 | 6 | class IsATest < Mocha::TestCase 7 | include Mocha::ParameterMatchers 8 | 9 | def test_should_match_object_that_is_a_specified_class 10 | matcher = is_a(Integer) 11 | assert matcher.matches?([99]) 12 | end 13 | 14 | def test_should_not_match_object_that_is_not_a_specified_class 15 | matcher = is_a(Integer) 16 | assert !matcher.matches?(['string']) 17 | end 18 | 19 | def test_should_describe_matcher 20 | matcher = is_a(Integer) 21 | assert_equal 'is_a(Integer)', matcher.mocha_inspect 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/kind_of_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | require 'mocha/parameter_matchers/kind_of' 4 | require 'mocha/inspect' 5 | 6 | class KindOfTest < Mocha::TestCase 7 | include Mocha::ParameterMatchers 8 | 9 | def test_should_match_object_that_is_a_kind_of_specified_class 10 | matcher = kind_of(Integer) 11 | assert matcher.matches?([99]) 12 | end 13 | 14 | def test_should_not_match_object_that_is_not_a_kind_of_specified_class 15 | matcher = kind_of(Integer) 16 | assert !matcher.matches?(['string']) 17 | end 18 | 19 | def test_should_describe_matcher 20 | matcher = kind_of(Integer) 21 | assert_equal 'kind_of(Integer)', matcher.mocha_inspect 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/acceptance/unexpected_invocation_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class UnexpectedInvocationTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_avoid_recursion_when_unexpected_invocation_exception_message_depends_on_uninspectable_object 15 | test_result = run_as_test do 16 | instance = Class.new.new 17 | instance.expects(:inspect).never 18 | instance.inspect(1, 2, 'foo') 19 | end 20 | assert_failed(test_result) 21 | assert_equal 'unexpected invocation: inspect(1, 2, foo)', test_result.failure_message_lines[0] 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/instance_of_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | require 'mocha/parameter_matchers/instance_of' 4 | require 'mocha/inspect' 5 | 6 | class InstanceOfTest < Mocha::TestCase 7 | include Mocha::ParameterMatchers 8 | 9 | def test_should_match_object_that_is_an_instance_of_specified_class 10 | matcher = instance_of(String) 11 | assert matcher.matches?(['string']) 12 | end 13 | 14 | def test_should_not_match_object_that_is_not_an_instance_of_specified_class 15 | matcher = instance_of(String) 16 | assert !matcher.matches?([99]) 17 | end 18 | 19 | def test_should_describe_matcher 20 | matcher = instance_of(String) 21 | assert_equal 'instance_of(String)', matcher.mocha_inspect 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/not_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | require 'mocha/parameter_matchers/not' 4 | require 'mocha/inspect' 5 | require 'stub_matcher' 6 | 7 | class NotTest < Mocha::TestCase 8 | include Mocha::ParameterMatchers 9 | 10 | def test_should_match_if_matcher_does_not_match 11 | matcher = Not(Stub::Matcher.new(false)) 12 | assert matcher.matches?(['any_old_value']) 13 | end 14 | 15 | def test_should_not_match_if_matcher_does_match 16 | matcher = Not(Stub::Matcher.new(true)) 17 | assert !matcher.matches?(['any_old_value']) 18 | end 19 | 20 | def test_should_describe_matcher 21 | matcher = Not(Stub::Matcher.new(true)) 22 | assert_equal 'Not(matcher(true))', matcher.mocha_inspect 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/acceptance/bug_21465_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class Bug21465Test < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_allow_expected_method_name_to_be_a_string 15 | test_result = run_as_test do 16 | mock = mock() 17 | mock.expects('wibble') 18 | mock.wibble 19 | end 20 | assert_passed(test_result) 21 | end 22 | 23 | def test_should_allow_stubbed_method_name_to_be_a_string 24 | test_result = run_as_test do 25 | mock = mock() 26 | mock.stubs('wibble') 27 | mock.wibble 28 | end 29 | assert_passed(test_result) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/instance_methods.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | require 'mocha/parameter_matchers/equals' 3 | require 'mocha/parameter_matchers/positional_or_keyword_hash' 4 | 5 | module Mocha 6 | module ParameterMatchers 7 | # @private 8 | module InstanceMethods 9 | # @private 10 | def to_matcher(expectation: nil, top_level: false) 11 | if Base === self 12 | self 13 | elsif Hash === self && top_level 14 | Mocha::ParameterMatchers::PositionalOrKeywordHash.new(self, expectation) 15 | else 16 | Mocha::ParameterMatchers::Equals.new(self) 17 | end 18 | end 19 | end 20 | end 21 | end 22 | 23 | # @private 24 | class Object 25 | include Mocha::ParameterMatchers::InstanceMethods 26 | end 27 | -------------------------------------------------------------------------------- /test/acceptance/issue_457_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class Issue457Test < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_only_inspect_objects_when_necessary 15 | test_result = run_as_test do 16 | klass = Class.new do 17 | def message 18 | raise 'Not inspectable in this state!' 19 | end 20 | 21 | def inspect 22 | message 23 | end 24 | end 25 | instance = klass.new 26 | instance.stubs(:message).returns('message') 27 | assert_equal 'message', instance.inspect 28 | end 29 | assert_passed(test_result) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/acceptance/array_flatten_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class ArrayFlattenTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_flattens_array_containing_mock_which_responds_like_active_record_model 15 | model = Class.new do 16 | # Ref: https://github.com/rails/rails/blob/7db044f38594eb43e1d241cc82025155666cc6f1/activerecord/lib/active_record/core.rb#L734-L745 17 | def to_ary 18 | nil 19 | end 20 | private :to_ary 21 | end.new 22 | test_result = run_as_test do 23 | m = mock.responds_like(model) 24 | assert_equal [m], [m].flatten 25 | end 26 | assert_passed(test_result) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/mocha/integration/monkey_patcher.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/api' 2 | 3 | module Mocha 4 | module Integration 5 | module MonkeyPatcher 6 | def self.apply(mod, run_method_patch) 7 | if mod < Mocha::API 8 | Debug.puts "Mocha::API already included in #{mod}" 9 | else 10 | mod.send(:include, Mocha::API) 11 | end 12 | if mod.method_defined?(:run_before_mocha) 13 | Debug.puts "#{mod}#run_before_mocha method already defined" 14 | elsif mod.method_defined?(:run) 15 | mod.send(:alias_method, :run_before_mocha, :run) 16 | mod.send(:remove_method, :run) 17 | mod.send(:include, run_method_patch) 18 | else 19 | raise "Unable to monkey-patch #{mod}, because it does not define a `#run` method" 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/yaml_equivalent_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | require 'mocha/parameter_matchers/yaml_equivalent' 4 | require 'mocha/inspect' 5 | 6 | class YamlEquivalentTest < Mocha::TestCase 7 | include Mocha::ParameterMatchers 8 | 9 | def test_should_match_parameter_matching_yaml_representation_of_object 10 | matcher = yaml_equivalent([1, 2, 3]) 11 | assert matcher.matches?(["--- \n- 1\n- 2\n- 3\n"]) 12 | end 13 | 14 | def test_should_not_match_parameter_matching_yaml_representation_of_object 15 | matcher = yaml_equivalent([1, 2, 3]) 16 | assert !matcher.matches?(["--- \n- 4\n- 5\n"]) 17 | end 18 | 19 | def test_should_describe_matcher 20 | matcher = yaml_equivalent([1, 2, 3]) 21 | assert_equal 'yaml_equivalent([1, 2, 3])', matcher.mocha_inspect 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/mocha/detection/test_unit.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | module Detection 3 | module TestUnit 4 | def self.testcase 5 | if defined?(::Test::Unit::TestCase) && 6 | !(defined?(::Minitest::Unit::TestCase) && (::Test::Unit::TestCase < ::Minitest::Unit::TestCase)) && 7 | !(defined?(::Minitest::Spec) && (::Test::Unit::TestCase < ::Minitest::Spec)) 8 | ::Test::Unit::TestCase 9 | end 10 | end 11 | 12 | def self.version 13 | version = '1.0.0' 14 | if testcase 15 | begin 16 | require 'test/unit/version' 17 | rescue LoadError # rubocop:disable Lint/HandleExceptions 18 | end 19 | if defined?(::Test::Unit::VERSION) 20 | version = ::Test::Unit::VERSION 21 | end 22 | end 23 | version 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/acceptance/bug_18914_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class Bug18914Test < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | class AlwaysEql 15 | def my_method 16 | true 17 | end 18 | 19 | def ==(_other) 20 | true 21 | end 22 | 23 | def eql?(_other) 24 | true 25 | end 26 | end 27 | 28 | def test_should_not_allow_stubbing_of_non_mock_instance_disrupted_by_legitimate_overriding_of_eql_method 29 | always_eql1 = AlwaysEql.new 30 | always_eql1.stubs(:my_method).returns(false) 31 | 32 | always_eql2 = AlwaysEql.new 33 | always_eql2.stubs(:my_method).returns(false) 34 | 35 | assert_equal false, always_eql2.my_method 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/execution_point.rb: -------------------------------------------------------------------------------- 1 | class ExecutionPoint 2 | attr_reader :backtrace 3 | 4 | def self.current 5 | new(caller) 6 | end 7 | 8 | def initialize(backtrace) 9 | @backtrace = backtrace 10 | end 11 | 12 | def first_relevant_line_of_backtrace 13 | @backtrace && (@backtrace.reject { |l| %r{\Aorg/jruby/}.match(l) }.first || 'unknown:0') 14 | end 15 | 16 | def file_name 17 | /\A(.*?):\d+/.match(first_relevant_line_of_backtrace)[1] 18 | end 19 | 20 | def line_number 21 | Integer(/\A.*?:(\d+)/.match(first_relevant_line_of_backtrace)[1]) 22 | end 23 | 24 | def ==(other) 25 | return false unless other.is_a?(ExecutionPoint) 26 | (file_name == other.file_name) && (line_number == other.line_number) 27 | end 28 | 29 | def to_s 30 | "file: #{file_name}; line: #{line_number}" 31 | end 32 | 33 | def inspect 34 | to_s 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/mocha/integration/minitest.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/debug' 2 | require 'mocha/detection/minitest' 3 | require 'mocha/integration/minitest/adapter' 4 | 5 | module Mocha 6 | module Integration 7 | module Minitest 8 | def self.activate 9 | target = Detection::Minitest.testcase 10 | return false unless target 11 | 12 | minitest_version = Gem::Version.new(Detection::Minitest.version) 13 | Debug.puts "Detected Minitest version: #{minitest_version}" 14 | 15 | unless Minitest::Adapter.applicable_to?(minitest_version) 16 | raise 'Versions of minitest earlier than v3.3.0 are not supported.' 17 | end 18 | 19 | unless target < Minitest::Adapter 20 | Debug.puts "Applying #{Minitest::Adapter.description}" 21 | target.send(:include, Minitest::Adapter) 22 | end 23 | 24 | true 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/mocha/integration/test_unit.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/debug' 2 | require 'mocha/detection/test_unit' 3 | require 'mocha/integration/test_unit/adapter' 4 | 5 | module Mocha 6 | module Integration 7 | module TestUnit 8 | def self.activate 9 | target = Detection::TestUnit.testcase 10 | return false unless target 11 | 12 | test_unit_version = Gem::Version.new(Detection::TestUnit.version) 13 | Debug.puts "Detected Test::Unit version: #{test_unit_version}" 14 | 15 | unless TestUnit::Adapter.applicable_to?(test_unit_version) 16 | raise 'Versions of test-unit earlier than v2.5.1 are not supported.' 17 | end 18 | 19 | unless target < TestUnit::Adapter 20 | Debug.puts "Applying #{TestUnit::Adapter.description}" 21 | target.send(:include, TestUnit::Adapter) 22 | end 23 | 24 | true 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/mocha/names.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | class ImpersonatingName 3 | def initialize(object) 4 | @object = object 5 | end 6 | 7 | def mocha_inspect 8 | @object.mocha_inspect 9 | end 10 | end 11 | 12 | class ImpersonatingAnyInstanceName 13 | def initialize(klass) 14 | @klass = klass 15 | end 16 | 17 | def mocha_inspect 18 | "#" 19 | end 20 | end 21 | 22 | class Name 23 | def initialize(name) 24 | @name = name.to_s 25 | end 26 | 27 | def mocha_inspect 28 | "#" 29 | end 30 | end 31 | 32 | class DefaultName 33 | def initialize(mock) 34 | @mock = mock 35 | end 36 | 37 | def mocha_inspect 38 | address = @mock.__id__ * 2 39 | address += 0x100000000 if address < 0 40 | "#x', address: address)}>" 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/acceptance/issue_524_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class Issue524Test < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_expects_returns_last_expectation 15 | test_result = run_as_test do 16 | object = mock 17 | object.expects(method_1: 1, method_2: 2).twice 18 | object.method_1 19 | object.method_2 20 | object.method_2 21 | end 22 | assert_passed(test_result) 23 | end 24 | 25 | def test_stubs_returns_last_expectation 26 | test_result = run_as_test do 27 | object = mock 28 | object.stubs(method_1: 1, method_2: 2).twice 29 | object.method_1 30 | object.method_2 31 | object.method_2 32 | end 33 | assert_passed(test_result) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2019-11-16 18:15:36 +0000 using RuboCop version 0.58.2. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 57 10 | Metrics/AbcSize: 11 | Max: 26 12 | 13 | # Offense count: 23 14 | # Configuration parameters: CountComments. 15 | Metrics/ClassLength: 16 | Max: 366 17 | 18 | # Offense count: 172 19 | # Configuration parameters: CountComments. 20 | Metrics/MethodLength: 21 | Max: 31 22 | 23 | # Offense count: 545 24 | # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. 25 | # URISchemes: http, https 26 | Metrics/LineLength: 27 | Max: 180 28 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/anything.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | 3 | module Mocha 4 | module ParameterMatchers 5 | # Matches any object. 6 | # 7 | # @return [Anything] parameter matcher. 8 | # 9 | # @see Expectation#with 10 | # 11 | # @example Any object will match. 12 | # object = mock() 13 | # object.expects(:method_1).with(anything) 14 | # object.method_1('foo') 15 | # object.method_1(789) 16 | # object.method_1(:bar) 17 | # # no error raised 18 | def anything 19 | Anything.new 20 | end 21 | 22 | # Parameter matcher which always matches a single parameter. 23 | class Anything < Base 24 | # @private 25 | def matches?(available_parameters) 26 | available_parameters.shift 27 | true 28 | end 29 | 30 | # @private 31 | def mocha_inspect 32 | 'anything' 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/unit/change_state_side_effect_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'mocha/change_state_side_effect' 4 | 5 | class ChangeStateSideEffectTest < Mocha::TestCase 6 | include Mocha 7 | 8 | class FakeState 9 | attr_reader :active 10 | attr_writer :description 11 | 12 | def activate 13 | @active = true 14 | end 15 | 16 | def mocha_inspect 17 | @description 18 | end 19 | end 20 | 21 | def test_should_activate_the_given_state 22 | state = FakeState.new 23 | side_effect = ChangeStateSideEffect.new(state) 24 | 25 | side_effect.perform 26 | 27 | assert state.active 28 | end 29 | 30 | def test_should_describe_itself_in_terms_of_the_activated_state 31 | state = FakeState.new 32 | state.description = 'the-new-state' 33 | side_effect = ChangeStateSideEffect.new(state) 34 | 35 | assert_equal 'then the-new-state', side_effect.mocha_inspect 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/mocha/receivers.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | class ObjectReceiver 3 | def initialize(object) 4 | @object = object 5 | end 6 | 7 | def mocks 8 | object = @object 9 | mocks = [] 10 | while object 11 | mocha = object.mocha(false) 12 | mocks << mocha if mocha 13 | object = object.is_a?(Class) ? object.superclass : nil 14 | end 15 | mocks 16 | end 17 | end 18 | 19 | class AnyInstanceReceiver 20 | def initialize(klass) 21 | @klass = klass 22 | end 23 | 24 | def mocks 25 | klass = @klass 26 | mocks = [] 27 | while klass 28 | mocha = klass.any_instance.mocha(false) 29 | mocks << mocha if mocha 30 | klass = klass.superclass 31 | end 32 | mocks 33 | end 34 | end 35 | 36 | class DefaultReceiver 37 | def initialize(mock) 38 | @mock = mock 39 | end 40 | 41 | def mocks 42 | [@mock] 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/acceptance/stubbing_same_class_method_on_parent_and_child_classes_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class StubbingSameClassMethodOnParentAndChildClassTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_stubbing_same_method_on_parent_and_child_classes 15 | parent_class = Class.new do 16 | def self.foo 17 | 'Parent.foo' 18 | end 19 | end 20 | child_class = Class.new(parent_class) 21 | test_result = run_as_tests( 22 | test_1: lambda { 23 | parent_class.stubs(:foo).returns('stubbed Parent.foo') 24 | child_class.stubs(:foo).returns('stubbed Child.foo') 25 | }, 26 | test_2: lambda { 27 | parent_class.foo 28 | child_class.foo 29 | } 30 | ) 31 | assert_passed(test_result) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/all_of_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | require 'mocha/parameter_matchers/all_of' 4 | require 'mocha/inspect' 5 | require 'stub_matcher' 6 | 7 | class AllOfTest < Mocha::TestCase 8 | include Mocha::ParameterMatchers 9 | 10 | def test_should_match_if_all_matchers_match 11 | matcher = all_of(Stub::Matcher.new(true), Stub::Matcher.new(true), Stub::Matcher.new(true)) 12 | assert matcher.matches?(['any_old_value']) 13 | end 14 | 15 | def test_should_not_match_if_any_matcher_does_not_match 16 | matcher = all_of(Stub::Matcher.new(true), Stub::Matcher.new(false), Stub::Matcher.new(true)) 17 | assert !matcher.matches?(['any_old_value']) 18 | end 19 | 20 | def test_should_describe_matcher 21 | matcher = all_of(Stub::Matcher.new(true), Stub::Matcher.new(false), Stub::Matcher.new(true)) 22 | assert_equal 'all_of(matcher(true), matcher(false), matcher(true))', matcher.mocha_inspect 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/any_of_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | require 'mocha/parameter_matchers/any_of' 4 | require 'mocha/inspect' 5 | require 'stub_matcher' 6 | 7 | class AnyOfTest < Mocha::TestCase 8 | include Mocha::ParameterMatchers 9 | 10 | def test_should_match_if_any_matchers_match 11 | matcher = any_of(Stub::Matcher.new(false), Stub::Matcher.new(true), Stub::Matcher.new(false)) 12 | assert matcher.matches?(['any_old_value']) 13 | end 14 | 15 | def test_should_not_match_if_no_matchers_match 16 | matcher = any_of(Stub::Matcher.new(false), Stub::Matcher.new(false), Stub::Matcher.new(false)) 17 | assert !matcher.matches?(['any_old_value']) 18 | end 19 | 20 | def test_should_describe_matcher 21 | matcher = any_of(Stub::Matcher.new(false), Stub::Matcher.new(true), Stub::Matcher.new(false)) 22 | assert_equal 'any_of(matcher(false), matcher(true), matcher(false))', matcher.mocha_inspect 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /mocha.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib/', __FILE__) 2 | $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib) 3 | require 'mocha/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'mocha' 7 | s.version = Mocha::VERSION 8 | s.licenses = ['MIT', 'BSD-2-Clause'] 9 | s.required_ruby_version = '>= 2.1' 10 | 11 | s.authors = ['James Mead'] 12 | s.description = 'Mocking and stubbing library with JMock/SchMock syntax, which allows mocking and stubbing of methods on real (non-mock) classes.' 13 | s.email = 'mocha-developer@googlegroups.com' 14 | 15 | s.files = Dir.chdir(File.expand_path('..', __FILE__)) do 16 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(docs|test)/}) } 17 | end 18 | s.files.delete('.circleci/config.yml') 19 | s.files.delete('.gitignore') 20 | 21 | s.homepage = 'https://mocha.jamesmead.org' 22 | s.require_paths = ['lib'] 23 | s.summary = 'Mocking and stubbing library' 24 | 25 | s.add_runtime_dependency 'ruby2_keywords', '>= 0.0.5' 26 | end 27 | -------------------------------------------------------------------------------- /test/unit/hash_inspect_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require 'mocha/inspect' 3 | 4 | class HashInspectTest < Mocha::TestCase 5 | def test_should_return_string_representation_of_hash 6 | hash = { a: true, b: false } 7 | assert_equal '{:a => true, :b => false}', hash.mocha_inspect 8 | end 9 | 10 | if Mocha::RUBY_V27_PLUS 11 | def test_should_return_unwrapped_keyword_style_hash_when_keyword_hash 12 | hash = Hash.ruby2_keywords_hash(a: true, b: false) 13 | assert_equal 'a: true, b: false', hash.mocha_inspect 14 | end 15 | 16 | def test_should_return_unwrapped_hash_when_keyword_hash_keys_are_not_symbols 17 | hash = Hash.ruby2_keywords_hash('a' => true, 'b' => false) 18 | assert_equal '"a" => true, "b" => false', hash.mocha_inspect 19 | end 20 | end 21 | 22 | def test_should_use_mocha_inspect_on_each_key_and_value 23 | hash = { a: 'mocha' } 24 | assert_equal %({:a => "mocha"}), hash.mocha_inspect 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/unit/hooks_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require 'mocha/hooks' 3 | require 'mocha/mockery' 4 | 5 | class HooksTest < Mocha::TestCase 6 | # rubocop:disable Style/ClassAndModuleChildren 7 | class Mocha::Mockery 8 | class << self 9 | attr_writer :instances 10 | end 11 | end 12 | # rubocop:enable Style/ClassAndModuleChildren 13 | 14 | class FakeMockery 15 | def verify(*args); end 16 | 17 | def teardown(_origin = nil) 18 | raise 'exception within Mockery#teardown' 19 | end 20 | end 21 | 22 | def test_ensure_mockery_instance_is_reset_even_when_an_exception_is_raised_in_mockery_teardown 23 | fake_test_case = Object.new.extend(Mocha::Hooks) 24 | mockery = FakeMockery.new 25 | Mocha::Mockery.instances = [mockery] 26 | 27 | begin 28 | fake_test_case.mocha_teardown 29 | rescue StandardError 30 | nil 31 | end 32 | 33 | assert_kind_of Mocha::Mockery::Null, Mocha::Mockery.instance 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/unit/method_matcher_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require 'mocha/method_matcher' 3 | 4 | class MethodMatcherTest < Mocha::TestCase 5 | include Mocha 6 | 7 | def test_should_match_if_actual_method_name_is_same_as_expected_method_name 8 | method_matcher = MethodMatcher.new(:method_name) 9 | assert method_matcher.match?(:method_name) 10 | end 11 | 12 | def test_should_match_if_actual_method_name_is_expected_method_name_as_string 13 | method_matcher = MethodMatcher.new(:method_name) 14 | assert method_matcher.match?('method_name') 15 | end 16 | 17 | def test_should_not_match_if_actual_method_name_is_not_same_as_expected_method_name 18 | method_matcher = MethodMatcher.new(:method_name) 19 | assert !method_matcher.match?(:different_method_name) 20 | end 21 | 22 | def test_should_describe_what_method_is_expected 23 | method_matcher = MethodMatcher.new(:method_name) 24 | assert_equal 'method_name', method_matcher.mocha_inspect 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/mocha/central.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | class Central 3 | class Null < self 4 | def initialize(&block) 5 | super 6 | @raise_not_initialized_error = block 7 | end 8 | 9 | def stub(*) 10 | @raise_not_initialized_error.call 11 | end 12 | 13 | def unstub(*) 14 | @raise_not_initialized_error.call 15 | end 16 | end 17 | 18 | attr_accessor :stubba_methods 19 | 20 | def initialize 21 | self.stubba_methods = [] 22 | end 23 | 24 | def stub(method) 25 | return if stubba_methods.detect { |m| m.matches?(method) } 26 | method.stub 27 | stubba_methods.push(method) 28 | end 29 | 30 | def unstub(method) 31 | return unless (existing = stubba_methods.detect { |m| m.matches?(method) }) 32 | existing.unstub 33 | stubba_methods.delete(existing) 34 | end 35 | 36 | def unstub_all 37 | while stubba_methods.any? 38 | unstub(stubba_methods.first) 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/unit/backtrace_filter_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require 'mocha/backtrace_filter' 3 | 4 | class BacktraceFilterTest < Mocha::TestCase 5 | include Mocha 6 | 7 | def test_should_exclude_mocha_locations_from_backtrace 8 | mocha_lib = '/username/workspace/mocha_wibble/lib/' 9 | backtrace = [mocha_lib + 'exclude/me/1', mocha_lib + 'exclude/me/2', '/keep/me', mocha_lib + 'exclude/me/3'] 10 | filter = BacktraceFilter.new(mocha_lib) 11 | assert_equal ['/keep/me'], filter.filtered(backtrace) 12 | end 13 | 14 | def test_should_determine_path_for_mocha_lib_directory 15 | assert_match Regexp.new('/lib/$'), BacktraceFilter::LIB_DIRECTORY 16 | end 17 | 18 | def test_should_handle_special_characters 19 | lib_directory = '/tmp/bundle/ruby/3.2.0+3/gems/mocha-2.0.2/lib/' 20 | filter = BacktraceFilter.new(lib_directory) 21 | backtrace = ['/keep/me', "#{lib_directory}mocha/deprecation.rb"] 22 | assert_equal ['/keep/me'], filter.filtered(backtrace) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/acceptance/acceptance_test_helper.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require 'test_runner' 3 | require 'mocha/configuration' 4 | require 'mocha/mockery' 5 | require 'introspection' 6 | 7 | if Mocha::Detection::Minitest.testcase && (ENV['MOCHA_RUN_INTEGRATION_TESTS'] != 'test-unit') 8 | require 'mocha/minitest' 9 | else 10 | require 'mocha/test_unit' 11 | end 12 | 13 | module AcceptanceTest 14 | class FakeLogger 15 | attr_reader :warnings 16 | 17 | def initialize 18 | @warnings = [] 19 | end 20 | 21 | def warn(message) 22 | @warnings << message 23 | end 24 | end 25 | 26 | attr_reader :logger 27 | 28 | include TestRunner 29 | 30 | def setup_acceptance_test 31 | Mocha::Configuration.reset_configuration 32 | @logger = FakeLogger.new 33 | mockery = Mocha::Mockery.instance 34 | mockery.logger = @logger 35 | end 36 | 37 | def teardown_acceptance_test 38 | Mocha::Configuration.reset_configuration 39 | end 40 | 41 | include Introspection::Assertions 42 | end 43 | -------------------------------------------------------------------------------- /MIT-LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006 James Mead 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /lib/mocha/parameters_matcher.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/inspect' 2 | require 'mocha/parameter_matchers' 3 | 4 | module Mocha 5 | class ParametersMatcher 6 | def initialize(expected_parameters = [ParameterMatchers::AnyParameters.new], expectation = nil, &matching_block) 7 | @expected_parameters = expected_parameters 8 | @expectation = expectation 9 | @matching_block = matching_block 10 | end 11 | 12 | def match?(actual_parameters = []) 13 | if @matching_block 14 | @matching_block.call(*actual_parameters) 15 | else 16 | parameters_match?(actual_parameters) 17 | end 18 | end 19 | 20 | def parameters_match?(actual_parameters) 21 | matchers.all? { |matcher| matcher.matches?(actual_parameters) } && actual_parameters.empty? 22 | end 23 | 24 | def mocha_inspect 25 | signature = matchers.mocha_inspect 26 | signature = signature.gsub(/^\[|\]$/, '') 27 | "(#{signature})" 28 | end 29 | 30 | def matchers 31 | @expected_parameters.map { |p| p.to_matcher(expectation: @expectation, top_level: true) } 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/acceptance/stub_method_defined_on_module_and_aliased_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class StubMethodDefinedOnModuleAndAliasedTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_stubbing_class_method_defined_by_aliasing_module_instance_method 15 | mod = Module.new do 16 | def module_instance_method 17 | 'module-instance-method' 18 | end 19 | end 20 | 21 | klass = Class.new do 22 | extend mod 23 | class << self 24 | alias_method :aliased_module_instance_method, :module_instance_method 25 | end 26 | end 27 | 28 | assert_snapshot_unchanged(klass) do 29 | test_result = run_as_test do 30 | klass.stubs(:aliased_module_instance_method).returns('stubbed-aliased-module-instance-method') 31 | assert_equal 'stubbed-aliased-module-instance-method', klass.aliased_module_instance_method 32 | end 33 | assert_passed(test_result) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/minitest_result.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | 3 | class MinitestResult 4 | class Failure 5 | extend Forwardable 6 | def_delegators :@failure, :message, :backtrace 7 | 8 | def initialize(failure) 9 | @failure = failure 10 | end 11 | 12 | def location 13 | Minitest.filter_backtrace(backtrace) 14 | end 15 | end 16 | 17 | def initialize(tests) 18 | @tests = tests 19 | end 20 | 21 | def failures 22 | @tests.map(&:failures).flatten.select { |r| r.instance_of?(Minitest::Assertion) }.map { |f| Failure.new(f) } 23 | end 24 | 25 | def failure_count 26 | failures.length 27 | end 28 | 29 | def failure_message_lines 30 | failures.map { |f| f.message.split("\n") }.flatten 31 | end 32 | 33 | def errors 34 | @tests.map(&:failures).flatten.select { |r| r.instance_of?(Minitest::UnexpectedError) } 35 | end 36 | 37 | def error_count 38 | errors.length 39 | end 40 | 41 | def error_messages 42 | errors.map { |e| e.message.split("\n") }.flatten 43 | end 44 | 45 | def assertion_count 46 | @tests.inject(0) { |total, test| total + test.assertions } 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/acceptance/throw_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class ThrowTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_throw_tag 15 | test_result = run_as_test do 16 | foo = stub('foo') 17 | foo.stubs(:bar).throws(:tag) 18 | assert_throws(:tag) { foo.bar } 19 | end 20 | assert_passed(test_result) 21 | end 22 | 23 | def test_should_throw_with_return_value 24 | test_result = run_as_test do 25 | foo = stub('foo') 26 | foo.stubs(:bar).throws(:tag, 'return-value') 27 | return_value = catch(:tag) { foo.bar } 28 | assert_equal 'return-value', return_value 29 | end 30 | assert_passed(test_result) 31 | end 32 | 33 | def test_should_throw_two_different_tags 34 | test_result = run_as_test do 35 | foo = stub('foo') 36 | foo.stubs(:bar).throws(:tag_one).then.throws(:tag_two) 37 | assert_throws(:tag_one) { foo.bar } 38 | assert_throws(:tag_two) { foo.bar } 39 | end 40 | assert_passed(test_result) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/acceptance/raise_exception_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class RaiseExceptionTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_raise_exception 15 | exception_class = Class.new(StandardError) 16 | test_result = run_as_test do 17 | foo = stub('foo') 18 | foo.stubs(:bar).raises(exception_class, 'my-message') 19 | exception = assert_raises(exception_class) { foo.bar } 20 | assert_equal 'my-message', exception.message 21 | end 22 | assert_passed(test_result) 23 | end 24 | 25 | def test_should_raise_two_different_exceptions 26 | exception_one_class = Class.new(StandardError) 27 | exception_two_class = Class.new(StandardError) 28 | test_result = run_as_test do 29 | foo = stub('foo') 30 | foo.stubs(:bar).raises(exception_one_class).then.raises(exception_two_class) 31 | assert_raises(exception_one_class) { foo.bar } 32 | assert_raises(exception_two_class) { foo.bar } 33 | end 34 | assert_passed(test_result) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/mocha/sequence.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | # Used to constrain the order in which expectations can occur. 3 | # 4 | # @see API#sequence 5 | # @see Expectation#in_sequence 6 | class Sequence 7 | # @private 8 | class InSequenceOrderingConstraint 9 | def initialize(sequence, index) 10 | @sequence = sequence 11 | @index = index 12 | end 13 | 14 | def allows_invocation_now? 15 | @sequence.satisfied_to_index?(@index) 16 | end 17 | 18 | def mocha_inspect 19 | "in sequence #{@sequence.mocha_inspect}" 20 | end 21 | end 22 | 23 | # @private 24 | def initialize(name) 25 | @name = name 26 | @expectations = [] 27 | end 28 | 29 | # @private 30 | def constrain_as_next_in_sequence(expectation) 31 | index = @expectations.length 32 | @expectations << expectation 33 | expectation.add_ordering_constraint(InSequenceOrderingConstraint.new(self, index)) 34 | end 35 | 36 | # @private 37 | def satisfied_to_index?(index) 38 | @expectations[0...index].all?(&:satisfied?) 39 | end 40 | 41 | # @private 42 | def mocha_inspect 43 | @name.mocha_inspect.to_s 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/any_parameters.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | 3 | module Mocha 4 | module ParameterMatchers 5 | # Matches any parameters. This is used as the default for a newly built expectation. 6 | # 7 | # @return [AnyParameters] parameter matcher. 8 | # 9 | # @see Expectation#with 10 | # 11 | # @example Any parameters will match. 12 | # object = mock() 13 | # object.expects(:method_1).with(any_parameters) 14 | # object.method_1(1, 2, 3, 4) 15 | # # no error raised 16 | # 17 | # object = mock() 18 | # object.expects(:method_1).with(any_parameters) 19 | # object.method_1(5, 6, 7, 8, 9, 0) 20 | # # no error raised 21 | def any_parameters 22 | AnyParameters.new 23 | end 24 | 25 | # Parameter matcher which always matches whatever the parameters. 26 | class AnyParameters < Base 27 | # @private 28 | def matches?(available_parameters) 29 | until available_parameters.empty? 30 | available_parameters.shift 31 | end 32 | true 33 | end 34 | 35 | # @private 36 | def mocha_inspect 37 | 'any_parameters' 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/unit/in_state_ordering_constraint_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'mocha/in_state_ordering_constraint' 4 | 5 | class InStateOrderingConstraintTest < Mocha::TestCase 6 | include Mocha 7 | 8 | class FakeStatePredicate 9 | attr_writer :active, :description 10 | 11 | def active? 12 | @active 13 | end 14 | 15 | def mocha_inspect 16 | @description 17 | end 18 | end 19 | 20 | def test_should_allow_invocation_when_state_is_active 21 | state_predicate = FakeStatePredicate.new 22 | ordering_constraint = InStateOrderingConstraint.new(state_predicate) 23 | 24 | state_predicate.active = true 25 | assert ordering_constraint.allows_invocation_now? 26 | 27 | state_predicate.active = false 28 | assert !ordering_constraint.allows_invocation_now? 29 | end 30 | 31 | def test_should_describe_itself_in_terms_of_the_state_predicates_description 32 | state_predicate = FakeStatePredicate.new 33 | ordering_constraint = InStateOrderingConstraint.new(state_predicate) 34 | 35 | state_predicate.description = 'the-state-predicate' 36 | 37 | assert_equal 'when the-state-predicate', ordering_constraint.mocha_inspect 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | # Used as parameters for {Expectation#with} to restrict the parameter values which will match the expectation. Can be nested. 3 | module ParameterMatchers; end 4 | end 5 | 6 | require 'mocha/parameter_matchers/instance_methods' 7 | 8 | require 'mocha/parameter_matchers/all_of' 9 | require 'mocha/parameter_matchers/any_of' 10 | require 'mocha/parameter_matchers/any_parameters' 11 | require 'mocha/parameter_matchers/anything' 12 | require 'mocha/parameter_matchers/equals' 13 | require 'mocha/parameter_matchers/has_entry' 14 | require 'mocha/parameter_matchers/has_entries' 15 | require 'mocha/parameter_matchers/has_key' 16 | require 'mocha/parameter_matchers/has_keys' 17 | require 'mocha/parameter_matchers/has_value' 18 | require 'mocha/parameter_matchers/includes' 19 | require 'mocha/parameter_matchers/instance_of' 20 | require 'mocha/parameter_matchers/is_a' 21 | require 'mocha/parameter_matchers/kind_of' 22 | require 'mocha/parameter_matchers/not' 23 | require 'mocha/parameter_matchers/optionally' 24 | require 'mocha/parameter_matchers/regexp_matches' 25 | require 'mocha/parameter_matchers/responds_with' 26 | require 'mocha/parameter_matchers/yaml_equivalent' 27 | require 'mocha/parameter_matchers/equivalent_uri' 28 | -------------------------------------------------------------------------------- /test/acceptance/partial_mocks_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class PartialMockTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_pass_if_all_expectations_are_satisfied 15 | test_result = run_as_test do 16 | partial_mock_one = 'partial_mock_one' 17 | partial_mock_two = 'partial_mock_two' 18 | 19 | partial_mock_one.expects(:first) 20 | partial_mock_one.expects(:second) 21 | partial_mock_two.expects(:third) 22 | 23 | partial_mock_one.first 24 | partial_mock_one.second 25 | partial_mock_two.third 26 | end 27 | assert_passed(test_result) 28 | end 29 | 30 | def test_should_fail_if_all_expectations_are_not_satisfied 31 | test_result = run_as_test do 32 | partial_mock_one = 'partial_mock_one' 33 | partial_mock_two = 'partial_mock_two' 34 | 35 | partial_mock_one.expects(:first) 36 | partial_mock_one.expects(:second) 37 | partial_mock_two.expects(:third) 38 | 39 | partial_mock_one.first 40 | partial_mock_two.third 41 | end 42 | assert_failed(test_result) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/acceptance/issue_272_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class Issue272Test < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | module Mod 15 | private 16 | 17 | def foo 18 | 'original-foo' 19 | end 20 | 21 | def bar 22 | 'original-bar' 23 | end 24 | end 25 | 26 | class Klass 27 | extend Mod 28 | 29 | class << self 30 | public :foo 31 | public :bar 32 | end 33 | end 34 | 35 | def test_private_methods_in_module_used_to_extend_class_and_made_public 36 | test_result = run_as_test do 37 | Klass.stubs(:foo).returns('stubbed-foo') 38 | # hangs in next line executing: 39 | # `@original_method = stubbee._method(method)` 40 | # in Mocha::StubbedMethod#hide_original_method 41 | # but only in Ruby v2.3, not v2.2 42 | Klass.stubs(:bar).returns('stubbed-bar') 43 | assert_equal 'stubbed-foo', Klass.foo 44 | assert_equal 'stubbed-bar', Klass.bar 45 | end 46 | assert_passed(test_result) 47 | assert_equal 'original-foo', Klass.foo 48 | assert_equal 'original-bar', Klass.bar 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/acceptance/stub_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class StubTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_build_stub_and_explicitly_add_an_expectation 15 | test_result = run_as_test do 16 | foo = stub 17 | foo.stubs(:bar) 18 | foo.bar 19 | end 20 | assert_passed(test_result) 21 | end 22 | 23 | def test_should_build_named_stub_and_explicitly_add_an_expectation 24 | test_result = run_as_test do 25 | foo = stub('foo') 26 | foo.stubs(:bar) 27 | foo.bar 28 | end 29 | assert_passed(test_result) 30 | end 31 | 32 | def test_should_build_stub_incorporating_two_expectations 33 | test_result = run_as_test do 34 | foo = stub(bar: 'bar', baz: 'baz') 35 | foo.bar 36 | foo.baz 37 | end 38 | assert_passed(test_result) 39 | end 40 | 41 | def test_should_build_named_stub_incorporating_two_expectations 42 | test_result = run_as_test do 43 | foo = stub('foo', bar: 'bar', baz: 'baz') 44 | foo.bar 45 | foo.baz 46 | end 47 | assert_passed(test_result) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/acceptance/exception_rescue_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class ExceptionRescueTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_unexpected_invocation_exception_is_not_caught_by_standard_rescue 15 | test_result = run_as_test do 16 | mock = mock('mock') 17 | begin 18 | mock.some_method 19 | rescue StandardError => e 20 | flunk "should not rescue #{e.class}" 21 | end 22 | end 23 | assert_failed(test_result) 24 | assert_equal 'unexpected invocation: #.some_method()', test_result.failure_message_lines[0] 25 | end 26 | 27 | def test_invocation_never_expected_exception_is_not_caught_by_standard_rescue 28 | test_result = run_as_test do 29 | mock = mock('mock') 30 | mock.expects(:some_method).never 31 | begin 32 | mock.some_method 33 | rescue StandardError => e 34 | flunk "should not rescue #{e.class}" 35 | end 36 | end 37 | assert_failed(test_result) 38 | assert_equal 'unexpected invocation: #.some_method()', test_result.failure_message_lines[0] 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/equals.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | 3 | module Mocha 4 | module ParameterMatchers 5 | # Matches any +Object+ equalling +value+. 6 | # 7 | # @param [Object] value expected value. 8 | # @return [Equals] parameter matcher. 9 | # 10 | # @see Expectation#with 11 | # @see Object#== 12 | # 13 | # @example Actual parameter equals expected parameter. 14 | # object = mock() 15 | # object.expects(:method_1).with(equals(2)) 16 | # object.method_1(2) 17 | # # no error raised 18 | # 19 | # @example Actual parameter does not equal expected parameter. 20 | # object = mock() 21 | # object.expects(:method_1).with(equals(2)) 22 | # object.method_1(3) 23 | # # error raised, because method_1 was not called with an +Object+ that equals 2 24 | def equals(value) 25 | Equals.new(value) 26 | end 27 | 28 | # Parameter matcher which matches when actual parameter equals expected value. 29 | class Equals < Base 30 | # @private 31 | def initialize(value) 32 | @value = value 33 | end 34 | 35 | # @private 36 | def matches?(available_parameters) 37 | parameter = available_parameters.shift 38 | parameter == @value 39 | end 40 | 41 | # @private 42 | def mocha_inspect 43 | @value.mocha_inspect 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/is_a.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | 3 | module Mocha 4 | module ParameterMatchers 5 | # Matches any object that is a +klass+. 6 | # 7 | # @param [Class] klass expected class. 8 | # @return [IsA] parameter matcher. 9 | # 10 | # @see Expectation#with 11 | # @see Kernel#is_a? 12 | # 13 | # @example Actual parameter is a +Integer+. 14 | # object = mock() 15 | # object.expects(:method_1).with(is_a(Integer)) 16 | # object.method_1(99) 17 | # # no error raised 18 | # 19 | # @example Actual parameter is not a +Integer+. 20 | # object = mock() 21 | # object.expects(:method_1).with(is_a(Integer)) 22 | # object.method_1('string') 23 | # # error raised, because method_1 was not called with an Integer 24 | # 25 | def is_a(klass) # rubocop:disable Naming/PredicateName 26 | IsA.new(klass) 27 | end 28 | 29 | # Parameter matcher which matches when actual parameter is a specific class. 30 | class IsA < Base 31 | # @private 32 | def initialize(klass) 33 | @klass = klass 34 | end 35 | 36 | # @private 37 | def matches?(available_parameters) 38 | parameter = available_parameters.shift 39 | parameter.is_a?(@klass) 40 | end 41 | 42 | # @private 43 | def mocha_inspect 44 | "is_a(#{@klass.mocha_inspect})" 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/acceptance/stub_everything_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class StubEverythingTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_build_stub_and_explicitly_add_an_expectation 15 | test_result = run_as_test do 16 | foo = stub_everything 17 | foo.stubs(:bar) 18 | foo.bar 19 | foo.unexpected_invocation 20 | end 21 | assert_passed(test_result) 22 | end 23 | 24 | def test_should_build_named_stub_and_explicitly_add_an_expectation 25 | test_result = run_as_test do 26 | foo = stub_everything('foo') 27 | foo.stubs(:bar) 28 | foo.bar 29 | foo.unexpected_invocation 30 | end 31 | assert_passed(test_result) 32 | end 33 | 34 | def test_should_build_stub_incorporating_two_expectations 35 | test_result = run_as_test do 36 | foo = stub_everything(bar: 'bar', baz: 'baz') 37 | foo.bar 38 | foo.baz 39 | foo.unexpected_invocation 40 | end 41 | assert_passed(test_result) 42 | end 43 | 44 | def test_should_build_named_stub_incorporating_two_expectations 45 | test_result = run_as_test do 46 | foo = stub_everything('foo', bar: 'bar', baz: 'baz') 47 | foo.bar 48 | foo.baz 49 | foo.unexpected_invocation 50 | end 51 | assert_passed(test_result) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/acceptance/return_value_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class ReturnValueTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_build_mock_and_explicitly_add_an_expectation_with_a_return_value 15 | test_result = run_as_test do 16 | foo = mock('foo') 17 | foo.expects(:bar).returns('bar') 18 | assert_equal 'bar', foo.bar 19 | end 20 | assert_passed(test_result) 21 | end 22 | 23 | def test_should_build_mock_incorporating_two_expectations_with_return_values 24 | test_result = run_as_test do 25 | foo = mock('foo', bar: 'bar', baz: 'baz') 26 | assert_equal 'bar', foo.bar 27 | assert_equal 'baz', foo.baz 28 | end 29 | assert_passed(test_result) 30 | end 31 | 32 | def test_should_build_stub_and_explicitly_add_an_expectation_with_a_return_value 33 | test_result = run_as_test do 34 | foo = stub('foo') 35 | foo.stubs(:bar).returns('bar') 36 | assert_equal 'bar', foo.bar 37 | end 38 | assert_passed(test_result) 39 | end 40 | 41 | def test_should_build_stub_incorporating_two_expectations_with_return_values 42 | test_result = run_as_test do 43 | foo = stub('foo', bar: 'bar', baz: 'baz') 44 | assert_equal 'bar', foo.bar 45 | assert_equal 'baz', foo.baz 46 | end 47 | assert_passed(test_result) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/not.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | 3 | module Mocha 4 | module ParameterMatchers 5 | # Matches if +matcher+ does *not* match. 6 | # 7 | # @param [Base] matcher matcher whose logic to invert. 8 | # @return [Not] parameter matcher. 9 | # 10 | # @see Expectation#with 11 | # 12 | # @example Actual parameter does not include the value +1+. 13 | # object = mock() 14 | # object.expects(:method_1).with(Not(includes(1))) 15 | # object.method_1([0, 2, 3]) 16 | # # no error raised 17 | # 18 | # @example Actual parameter does include the value +1+. 19 | # object = mock() 20 | # object.expects(:method_1).with(Not(includes(1))) 21 | # object.method_1([0, 1, 2, 3]) 22 | # # error raised, because method_1 was not called with object not including 1 23 | # 24 | def Not(matcher) # rubocop:disable Naming/MethodName 25 | Not.new(matcher) 26 | end 27 | 28 | # Parameter matcher which inverts the logic of the specified matcher using a logical NOT operation. 29 | class Not < Base 30 | # @private 31 | def initialize(matcher) 32 | @matcher = matcher 33 | end 34 | 35 | # @private 36 | def matches?(available_parameters) 37 | parameter = available_parameters.shift 38 | !@matcher.matches?([parameter]) 39 | end 40 | 41 | # @private 42 | def mocha_inspect 43 | "Not(#{@matcher.mocha_inspect})" 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/instance_of.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | 3 | module Mocha 4 | module ParameterMatchers 5 | # Matches any object that is an instance of +klass+ 6 | # 7 | # @param [Class] klass expected class. 8 | # @return [InstanceOf] parameter matcher. 9 | # 10 | # @see Expectation#with 11 | # @see Kernel#instance_of? 12 | # 13 | # @example Actual parameter is an instance of +String+. 14 | # object = mock() 15 | # object.expects(:method_1).with(instance_of(String)) 16 | # object.method_1('string') 17 | # # no error raised 18 | # 19 | # @example Actual parameter is not an instance of +String+. 20 | # object = mock() 21 | # object.expects(:method_1).with(instance_of(String)) 22 | # object.method_1(99) 23 | # # error raised, because method_1 was not called with an instance of String 24 | def instance_of(klass) 25 | InstanceOf.new(klass) 26 | end 27 | 28 | # Parameter matcher which matches when actual parameter is an instance of the specified class. 29 | class InstanceOf < Base 30 | # @private 31 | def initialize(klass) 32 | @klass = klass 33 | end 34 | 35 | # @private 36 | def matches?(available_parameters) 37 | parameter = available_parameters.shift 38 | parameter.instance_of?(@klass) 39 | end 40 | 41 | # @private 42 | def mocha_inspect 43 | "instance_of(#{@klass.mocha_inspect})" 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/all_of.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | 3 | module Mocha 4 | module ParameterMatchers 5 | # Matches if all +matchers+ match. 6 | # 7 | # @param [*Array] matchers parameter matchers. 8 | # @return [AllOf] parameter matcher. 9 | # 10 | # @see Expectation#with 11 | # 12 | # @example All parameter matchers match. 13 | # object = mock() 14 | # object.expects(:method_1).with(all_of(includes(1), includes(3))) 15 | # object.method_1([1, 3]) 16 | # # no error raised 17 | # 18 | # @example One of the parameter matchers does not match. 19 | # object = mock() 20 | # object.expects(:method_1).with(all_of(includes(1), includes(3))) 21 | # object.method_1([1, 2]) 22 | # # error raised, because method_1 was not called with object including 1 and 3 23 | def all_of(*matchers) 24 | AllOf.new(*matchers) 25 | end 26 | 27 | # Parameter matcher which combines a number of other matchers using a logical AND. 28 | class AllOf < Base 29 | # @private 30 | def initialize(*matchers) 31 | @matchers = matchers 32 | end 33 | 34 | # @private 35 | def matches?(available_parameters) 36 | parameter = available_parameters.shift 37 | @matchers.all? { |matcher| matcher.to_matcher.matches?([parameter]) } 38 | end 39 | 40 | # @private 41 | def mocha_inspect 42 | "all_of(#{@matchers.map(&:mocha_inspect).join(', ')})" 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/kind_of.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | 3 | module Mocha 4 | module ParameterMatchers 5 | # Matches any +Object+ that is a kind of +klass+. 6 | # 7 | # @param [Class] klass expected class. 8 | # @return [KindOf] parameter matcher. 9 | # 10 | # @see Expectation#with 11 | # @see Kernel#kind_of? 12 | # 13 | # @example Actual parameter is a kind of +Integer+. 14 | # object = mock() 15 | # object.expects(:method_1).with(kind_of(Integer)) 16 | # object.method_1(99) 17 | # # no error raised 18 | # 19 | # @example Actual parameter is not a kind of +Integer+. 20 | # object = mock() 21 | # object.expects(:method_1).with(kind_of(Integer)) 22 | # object.method_1('string') 23 | # # error raised, because method_1 was not called with a kind of Integer 24 | def kind_of(klass) 25 | KindOf.new(klass) 26 | end 27 | 28 | # Parameter matcher which matches when actual parameter is a kind of specified class. 29 | class KindOf < Base 30 | # @private 31 | def initialize(klass) 32 | @klass = klass 33 | end 34 | 35 | # @private 36 | def matches?(available_parameters) 37 | parameter = available_parameters.shift 38 | # rubocop:disable Style/ClassCheck 39 | parameter.kind_of?(@klass) 40 | # rubocop:enable Style/ClassCheck 41 | end 42 | 43 | # @private 44 | def mocha_inspect 45 | "kind_of(#{@klass.mocha_inspect})" 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/acceptance/issue_70_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class Issue70Test < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_stub_expectations_instance_method 15 | instance = Class.new do 16 | def expectations 17 | :original_return_value 18 | end 19 | end.new 20 | test_result = run_as_test do 21 | instance.stubs(:expectations).returns(:stubbed_return_value) 22 | assert_equal :stubbed_return_value, instance.expectations 23 | end 24 | assert_passed(test_result) 25 | end 26 | 27 | def test_should_stub_expectations_class_method 28 | klass = Class.new do 29 | def self.expectations 30 | :original_return_value 31 | end 32 | end 33 | test_result = run_as_test do 34 | klass.stubs(:expectations).returns(:stubbed_return_value) 35 | assert_equal :stubbed_return_value, klass.expectations 36 | end 37 | assert_passed(test_result) 38 | end 39 | 40 | def test_should_stub_expectations_any_instance_method 41 | klass = Class.new do 42 | def expectations 43 | :original_return_value 44 | end 45 | end 46 | instance = klass.new 47 | test_result = run_as_test do 48 | klass.any_instance.stubs(:expectations).returns(:stubbed_return_value) 49 | assert_equal :stubbed_return_value, instance.expectations 50 | end 51 | assert_passed(test_result) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/mocha/expectation_error_factory.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/backtrace_filter' 2 | require 'mocha/expectation_error' 3 | 4 | module Mocha 5 | # This factory determines what class of exception should be raised when Mocha detects a test failure. 6 | # 7 | # This class should only be used by authors of test libraries and not by typical "users" of Mocha. 8 | # 9 | # For example, it is used by +Mocha::Integration::Minitest::Adapter+ in order to have Mocha raise a +Minitest::Assertion+ which can then be sensibly handled by +Minitest::Unit::TestCase+. 10 | # 11 | # @see Mocha::Integration::Minitest::Adapter 12 | class ExpectationErrorFactory 13 | class << self 14 | # @!attribute exception_class 15 | # Determines what class of exception should be raised when Mocha detects a test failure. 16 | # 17 | # This attribute may be set by authors of test libraries in order to have Mocha raise exceptions of a specific class when there is an unexpected invocation or an unsatisfied expectation. 18 | # 19 | # By default a +Mocha::ExpectationError+ will be raised. 20 | # 21 | # @return [Exception] class of exception to be raised when an expectation error occurs 22 | # @see Mocha::ExpectationError 23 | attr_accessor :exception_class 24 | 25 | # @private 26 | def build(message = nil, backtrace = []) 27 | exception = exception_class.new(message) 28 | filter = BacktraceFilter.new 29 | exception.set_backtrace(filter.filtered(backtrace)) 30 | exception 31 | end 32 | end 33 | self.exception_class = ExpectationError 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/has_key.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | 3 | module Mocha 4 | module ParameterMatchers 5 | # Matches +Hash+ containing +key+. 6 | # 7 | # @param [Object] key expected key. 8 | # @return [HasKey] parameter matcher. 9 | # 10 | # @see Expectation#with 11 | # 12 | # @example Actual parameter contains entry with expected key. 13 | # object = mock() 14 | # object.expects(:method_1).with(has_key('key_1')) 15 | # object.method_1('key_1' => 1, 'key_2' => 2) 16 | # # no error raised 17 | # 18 | # @example Actual parameter does not contain entry with expected key. 19 | # object = mock() 20 | # object.expects(:method_1).with(has_key('key_1')) 21 | # object.method_1('key_2' => 2) 22 | # # error raised, because method_1 was not called with Hash containing key: 'key_1' 23 | # 24 | def has_key(key) # rubocop:disable Naming/PredicateName 25 | HasKey.new(key) 26 | end 27 | 28 | # Parameter matcher which matches when actual parameter contains +Hash+ entry with expected key. 29 | class HasKey < Base 30 | # @private 31 | def initialize(key) 32 | @key = key 33 | end 34 | 35 | # @private 36 | def matches?(available_parameters) 37 | parameter = available_parameters.shift 38 | return false unless parameter.respond_to?(:keys) 39 | parameter.keys.any? { |key| @key.to_matcher.matches?([key]) } 40 | end 41 | 42 | # @private 43 | def mocha_inspect 44 | "has_key(#{@key.mocha_inspect})" 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/regexp_matches.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | 3 | module Mocha 4 | module ParameterMatchers 5 | # Matches any object that matches +regexp+. 6 | # 7 | # @param [Regexp] regexp regular expression to match. 8 | # @return [RegexpMatches] parameter matcher. 9 | # 10 | # @see Expectation#with 11 | # 12 | # @example Actual parameter is matched by specified regular expression. 13 | # object = mock() 14 | # object.expects(:method_1).with(regexp_matches(/e/)) 15 | # object.method_1('hello') 16 | # # no error raised 17 | # 18 | # @example Actual parameter is not matched by specified regular expression. 19 | # object = mock() 20 | # object.expects(:method_1).with(regexp_matches(/a/)) 21 | # object.method_1('hello') 22 | # # error raised, because method_1 was not called with a parameter that matched the 23 | # # regular expression 24 | def regexp_matches(regexp) 25 | RegexpMatches.new(regexp) 26 | end 27 | 28 | # Parameter matcher which matches if specified regular expression matches actual paramter. 29 | class RegexpMatches < Base 30 | # @private 31 | def initialize(regexp) 32 | @regexp = regexp 33 | end 34 | 35 | # @private 36 | def matches?(available_parameters) 37 | parameter = available_parameters.shift 38 | return false unless parameter.respond_to?(:=~) 39 | parameter =~ @regexp 40 | end 41 | 42 | # @private 43 | def mocha_inspect 44 | "regexp_matches(#{@regexp.mocha_inspect})" 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/has_value.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | 3 | module Mocha 4 | module ParameterMatchers 5 | # Matches +Hash+ containing +value+. 6 | # 7 | # @param [Object] value expected value. 8 | # @return [HasValue] parameter matcher. 9 | # 10 | # @see Expectation#with 11 | # 12 | # @example Actual parameter contains entry with expected value. 13 | # object = mock() 14 | # object.expects(:method_1).with(has_value(1)) 15 | # object.method_1('key_1' => 1, 'key_2' => 2) 16 | # # no error raised 17 | # 18 | # @example Actual parameter does not contain entry with expected value. 19 | # object = mock() 20 | # object.expects(:method_1).with(has_value(1)) 21 | # object.method_1('key_2' => 2) 22 | # # error raised, because method_1 was not called with Hash containing value: 1 23 | # 24 | def has_value(value) # rubocop:disable Naming/PredicateName 25 | HasValue.new(value) 26 | end 27 | 28 | # Parameter matcher which matches when actual parameter contains +Hash+ entry with expected value. 29 | class HasValue < Base 30 | # @private 31 | def initialize(value) 32 | @value = value 33 | end 34 | 35 | # @private 36 | def matches?(available_parameters) 37 | parameter = available_parameters.shift 38 | return false unless parameter.respond_to?(:values) 39 | parameter.values.any? { |value| @value.to_matcher.matches?([value]) } 40 | end 41 | 42 | # @private 43 | def mocha_inspect 44 | "has_value(#{@value.mocha_inspect})" 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/acceptance/issue_65_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class Issue65Test < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_expectations_on_class_methods_on_same_class_should_be_verified_in_consecutive_tests 15 | klass = Class.new do 16 | def self.foo; end 17 | 18 | def self.bar; end 19 | end 20 | test1 = run_as_test do 21 | klass.expects(:foo) 22 | klass.foo 23 | end 24 | assert_passed(test1) 25 | test2 = run_as_test do 26 | klass.expects(:bar) 27 | end 28 | assert_failed(test2) 29 | end 30 | 31 | def test_expectations_on_any_instance_methods_on_same_class_should_be_verified_in_consecutive_tests 32 | klass = Class.new do 33 | def foo; end 34 | 35 | def bar; end 36 | end 37 | test1 = run_as_test do 38 | klass.any_instance.expects(:foo) 39 | klass.new.foo 40 | end 41 | assert_passed(test1) 42 | test2 = run_as_test do 43 | klass.any_instance.expects(:bar) 44 | end 45 | assert_failed(test2) 46 | end 47 | 48 | def test_expectations_on_instance_methods_on_same_object_should_be_verified_in_consecutive_tests 49 | instance = Class.new do 50 | def foo; end 51 | 52 | def bar; end 53 | end.new 54 | test1 = run_as_test do 55 | instance.expects(:foo) 56 | instance.foo 57 | end 58 | assert_passed(test1) 59 | test2 = run_as_test do 60 | instance.expects(:bar) 61 | end 62 | assert_failed(test2) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/regexp_matches_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | require 'mocha/parameter_matchers/regexp_matches' 4 | require 'mocha/inspect' 5 | 6 | class RegexpMatchesTest < Mocha::TestCase 7 | include Mocha::ParameterMatchers 8 | 9 | def test_should_match_parameter_matching_regular_expression 10 | matcher = regexp_matches(/oo/) 11 | assert matcher.matches?(['foo']) 12 | end 13 | 14 | def test_should_not_match_parameter_not_matching_regular_expression 15 | matcher = regexp_matches(/oo/) 16 | assert !matcher.matches?(['bar']) 17 | end 18 | 19 | def test_should_describe_matcher 20 | matcher = regexp_matches(/oo/) 21 | assert_equal 'regexp_matches(/oo/)', matcher.mocha_inspect 22 | end 23 | 24 | def test_should_not_raise_error_on_empty_arguments 25 | matcher = regexp_matches(/oo/) 26 | assert_nothing_raised { matcher.matches?([]) } 27 | end 28 | 29 | def test_should_not_match_on_empty_arguments 30 | matcher = regexp_matches(/oo/) 31 | assert !matcher.matches?([]) 32 | end 33 | 34 | def test_should_not_raise_error_on_argument_that_does_not_respond_to_equals_tilde 35 | matcher = regexp_matches(/oo/) 36 | assert_nothing_raised { matcher.matches?([object_not_responding_to_equals_tilde]) } 37 | end 38 | 39 | def test_should_not_match_on_argument_that_does_not_respond_to_equals_tilde 40 | matcher = regexp_matches(/oo/) 41 | assert !matcher.matches?([object_not_responding_to_equals_tilde]) 42 | end 43 | 44 | private 45 | 46 | def object_not_responding_to_equals_tilde 47 | Class.new { undef =~ if respond_to?(:=~) }.new 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/mocha/integration/test_unit/adapter.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/api' 2 | require 'mocha/integration/assertion_counter' 3 | require 'mocha/expectation_error' 4 | 5 | module Mocha 6 | module Integration 7 | module TestUnit 8 | # Integrates Mocha into recent versions of Test::Unit. 9 | # 10 | # See the source code for an example of how to integrate Mocha into a test library. 11 | module Adapter 12 | include Mocha::API 13 | 14 | # @private 15 | def self.applicable_to?(test_unit_version) 16 | Gem::Requirement.new('>= 2.5.1').satisfied_by?(test_unit_version) 17 | end 18 | 19 | # @private 20 | def self.description 21 | 'adapter for Test::Unit gem >= v2.5.1' 22 | end 23 | 24 | # @private 25 | def self.included(mod) 26 | mod.setup :mocha_setup, before: :prepend 27 | 28 | mod.exception_handler(:handle_mocha_expectation_error) 29 | 30 | mod.cleanup after: :append do 31 | assertion_counter = Integration::AssertionCounter.new(self) 32 | mocha_verify(assertion_counter) 33 | end 34 | 35 | mod.teardown :mocha_teardown, after: :append 36 | end 37 | 38 | private 39 | 40 | # @private 41 | def mocha_test_name 42 | name 43 | end 44 | 45 | # @private 46 | def handle_mocha_expectation_error(exception) 47 | return false unless exception.is_a?(Mocha::ExpectationError) 48 | problem_occurred 49 | add_failure(exception.message, exception.backtrace) 50 | true 51 | end 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/any_of.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | 3 | module Mocha 4 | module ParameterMatchers 5 | # Matches if any +matchers+ match. 6 | # 7 | # @param [*Array] matchers parameter matchers. 8 | # @return [AnyOf] parameter matcher. 9 | # 10 | # @see Expectation#with 11 | # 12 | # @example One parameter matcher matches. 13 | # object = mock() 14 | # object.expects(:method_1).with(any_of(1, 3)) 15 | # object.method_1(1) 16 | # # no error raised 17 | # 18 | # @example The other parameter matcher matches. 19 | # object = mock() 20 | # object.expects(:method_1).with(any_of(1, 3)) 21 | # object.method_1(3) 22 | # # no error raised 23 | # 24 | # @example Neither parameter matcher matches. 25 | # object = mock() 26 | # object.expects(:method_1).with(any_of(1, 3)) 27 | # object.method_1(2) 28 | # # error raised, because method_1 was not called with 1 or 3 29 | def any_of(*matchers) 30 | AnyOf.new(*matchers) 31 | end 32 | 33 | # Parameter matcher which combines a number of other matchers using a logical OR. 34 | class AnyOf < Base 35 | # @private 36 | def initialize(*matchers) 37 | @matchers = matchers 38 | end 39 | 40 | # @private 41 | def matches?(available_parameters) 42 | parameter = available_parameters.shift 43 | @matchers.any? { |matcher| matcher.to_matcher.matches?([parameter]) } 44 | end 45 | 46 | # @private 47 | def mocha_inspect 48 | "any_of(#{@matchers.map(&:mocha_inspect).join(', ')})" 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/acceptance/stubbing_method_accepting_block_parameter_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class StubbingMethodAcceptingBlockParameterTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_stubbing_class_method_accepting_block_parameter_should_restore_original_method 15 | klass = Class.new do 16 | def self.my_class_method(&block) 17 | block.call 18 | end 19 | end 20 | test_result = run_as_test do 21 | klass.stubs(:my_class_method) 22 | end 23 | assert_passed(test_result) 24 | assert_equal :return_value, (klass.my_class_method { :return_value }) 25 | end 26 | 27 | def test_stubbing_instance_method_accepting_block_parameter_should_restore_original_method 28 | instance = Class.new do 29 | def my_instance_method 30 | yield 31 | end 32 | end.new 33 | test_result = run_as_test do 34 | instance.stubs(:my_instance_method) 35 | end 36 | assert_passed(test_result) 37 | assert_equal :return_value, (instance.my_instance_method { :return_value }) 38 | end 39 | 40 | def test_stubbing_any_instance_method_accepting_block_parameter_should_restore_original_method 41 | klass = Class.new do 42 | def my_instance_method 43 | yield 44 | end 45 | end 46 | test_result = run_as_test do 47 | klass.any_instance.stubs(:my_instance_method) 48 | end 49 | assert_passed(test_result) 50 | assert_equal :return_value, (klass.new.my_instance_method { :return_value }) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/equivalent_uri_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | require 'mocha/parameter_matchers/equivalent_uri' 3 | 4 | class EquivalentUriMatchesTest < Mocha::TestCase 5 | include Mocha::ParameterMatchers 6 | 7 | def test_should_match_identical_uri 8 | matcher = equivalent_uri('http://example.com/foo?a=1&b=2') 9 | assert matcher.matches?(['http://example.com/foo?a=1&b=2']) 10 | end 11 | 12 | def test_should_match_uri_with_rearranged_query_string 13 | matcher = equivalent_uri('http://example.com/foo?b=2&a=1') 14 | assert matcher.matches?(['http://example.com/foo?a=1&b=2']) 15 | end 16 | 17 | def test_should_not_match_uri_with_different_query_string 18 | matcher = equivalent_uri('http://example.com/foo?a=1') 19 | assert !matcher.matches?(['http://example.com/foo?a=1&b=2']) 20 | end 21 | 22 | def test_should_not_match_uri_when_no_query_string_expected 23 | matcher = equivalent_uri('http://example.com/foo') 24 | assert !matcher.matches?(['http://example.com/foo?a=1&b=2']) 25 | end 26 | 27 | def test_should_not_match_uri_with_different_domain 28 | matcher = equivalent_uri('http://a.example.com/foo?a=1&b=2') 29 | assert !matcher.matches?(['http://b.example.com/foo?a=1&b=2']) 30 | end 31 | 32 | def test_should_match_uri_without_scheme_and_domain 33 | matcher = equivalent_uri('/foo?a=1&b=2') 34 | assert matcher.matches?(['/foo?a=1&b=2']) 35 | end 36 | 37 | def test_should_match_uri_with_query_string_containing_blank_value 38 | matcher = equivalent_uri('http://example.com/foo?a=&b=2') 39 | assert matcher.matches?(['http://example.com/foo?a=&b=2']) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/mocha/expectation_list.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | class ExpectationList 3 | def initialize(expectations = []) 4 | @expectations = expectations 5 | end 6 | 7 | def add(expectation) 8 | @expectations.unshift(expectation) 9 | expectation 10 | end 11 | 12 | def remove_all_matching_method(method_name) 13 | @expectations.reject! { |expectation| expectation.matches_method?(method_name) } 14 | end 15 | 16 | def matches_method?(method_name) 17 | @expectations.any? { |expectation| expectation.matches_method?(method_name) } 18 | end 19 | 20 | def match(invocation, ignoring_order: false) 21 | matching_expectations(invocation, ignoring_order: ignoring_order).first 22 | end 23 | 24 | def match_but_out_of_order(invocation) 25 | matching_expectations(invocation).first 26 | end 27 | 28 | def match_allowing_invocation(invocation) 29 | matching_expectations(invocation).detect(&:invocations_allowed?) 30 | end 31 | 32 | def verified?(assertion_counter = nil) 33 | @expectations.all? { |expectation| expectation.verified?(assertion_counter) } 34 | end 35 | 36 | def to_a 37 | @expectations 38 | end 39 | 40 | def to_set 41 | @expectations.to_set 42 | end 43 | 44 | def length 45 | @expectations.length 46 | end 47 | 48 | def any? 49 | @expectations.any? 50 | end 51 | 52 | def +(other) 53 | self.class.new(to_a + other.to_a) 54 | end 55 | 56 | private 57 | 58 | def matching_expectations(invocation, ignoring_order: false) 59 | @expectations.select { |e| e.match?(invocation, ignoring_order: ignoring_order) } 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /test/acceptance/expectations_on_multiple_methods_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class ExpectationsOnMultipleMethodsTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_expect_calls_to_multiple_methods 15 | instance = Class.new do 16 | def my_instance_method_1 17 | :original_return_value_1 18 | end 19 | 20 | def my_instance_method_2 21 | :original_return_value_2 22 | end 23 | end.new 24 | test_result = run_as_test do 25 | instance.expects( 26 | my_instance_method_1: :new_return_value_1, 27 | my_instance_method_2: :new_return_value_2 28 | ) 29 | assert_equal :new_return_value_1, instance.my_instance_method_1 30 | assert_equal :new_return_value_2, instance.my_instance_method_2 31 | end 32 | assert_passed(test_result) 33 | end 34 | 35 | def test_should_stub_calls_to_multiple_methods 36 | instance = Class.new do 37 | def my_instance_method_1 38 | :original_return_value_1 39 | end 40 | 41 | def my_instance_method_2 42 | :original_return_value_2 43 | end 44 | end.new 45 | test_result = run_as_test do 46 | instance.stubs( 47 | my_instance_method_1: :new_return_value_1, 48 | my_instance_method_2: :new_return_value_2 49 | ) 50 | assert_equal :new_return_value_1, instance.my_instance_method_1 51 | assert_equal :new_return_value_2, instance.my_instance_method_2 52 | end 53 | assert_passed(test_result) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/responds_with_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | require 'mocha/parameter_matchers/responds_with' 4 | require 'mocha/parameter_matchers/instance_methods' 5 | require 'mocha/inspect' 6 | 7 | class RespondsWithTest < Mocha::TestCase 8 | include Mocha::ParameterMatchers 9 | 10 | def test_should_match_parameter_responding_with_expected_value 11 | matcher = responds_with(:upcase, 'FOO') 12 | assert matcher.matches?(['foo']) 13 | end 14 | 15 | def test_should_not_match_parameter_responding_with_unexpected_value 16 | matcher = responds_with(:upcase, 'FOO') 17 | assert !matcher.matches?(['bar']) 18 | end 19 | 20 | def test_should_match_parameter_responding_with_nested_responds_with_matcher 21 | matcher = responds_with(:foo, responds_with(:bar, 'baz')) 22 | object = Class.new do 23 | def foo 24 | Class.new do 25 | def bar 26 | 'baz' 27 | end 28 | end.new 29 | end 30 | end.new 31 | assert matcher.matches?([object]) 32 | end 33 | 34 | def test_should_describe_matcher 35 | matcher = responds_with(:foo, :bar) 36 | assert_equal 'responds_with(:foo, :bar)', matcher.mocha_inspect 37 | end 38 | 39 | def test_should_match_parameter_responding_with_expected_values_for_given_messages 40 | matcher = responds_with(upcase: 'FOO', reverse: 'oof') 41 | assert matcher.matches?(['foo']) 42 | end 43 | 44 | def test_should_describe_matcher_with_multiple_messages_vs_results 45 | matcher = responds_with(foo: :bar, baz: 123) 46 | assert_equal 'all_of(responds_with(:foo, :bar), responds_with(:baz, 123))', matcher.mocha_inspect 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/unit/object_inspect_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require 'mocha/inspect' 3 | require 'method_definer' 4 | 5 | class ObjectInspectTest < Mocha::TestCase 6 | include MethodDefiner 7 | 8 | def test_should_return_default_string_representation_of_object_not_including_instance_variables 9 | object = Object.new 10 | class << object 11 | attr_accessor :attribute 12 | end 13 | object.attribute = 'instance_variable' 14 | assert_match Regexp.new('^#$'), object.mocha_inspect 15 | assert_no_match(/instance_variable/, object.mocha_inspect) 16 | end 17 | 18 | def test_should_return_customized_string_representation_of_object 19 | object = Object.new 20 | class << object 21 | define_method(:inspect) { 'custom_inspect' } 22 | end 23 | assert_equal 'custom_inspect', object.mocha_inspect 24 | end 25 | 26 | def test_should_use_underscored_id_instead_of_object_id_or_id_so_that_they_can_be_stubbed 27 | calls = [] 28 | object = Object.new 29 | replace_instance_method(object, :object_id) do 30 | calls << :object_id 31 | return 1 32 | end 33 | replace_instance_method(object, :__id__) do 34 | calls << :__id__ 35 | return 1 36 | end 37 | replace_instance_method(object, :inspect) { 'object-description' } 38 | 39 | object.mocha_inspect 40 | 41 | assert_equal [:__id__], calls.uniq 42 | end 43 | 44 | def test_should_not_call_object_instance_format_method 45 | object = Object.new 46 | class << object 47 | def format(*) 48 | 'internal_format' 49 | end 50 | end 51 | assert_no_match(/internal_format/, object.mocha_inspect) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/yaml_equivalent.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | require 'yaml' 3 | 4 | module Mocha 5 | module ParameterMatchers 6 | # Matches any YAML that represents the specified +object+ 7 | # 8 | # @param [Object] object object whose YAML to compare. 9 | # @return [YamlEquivalent] parameter matcher. 10 | # 11 | # @see Expectation#with 12 | # 13 | # @example Actual parameter is YAML equivalent of specified +object+. 14 | # object = mock() 15 | # object.expects(:method_1).with(yaml_equivalent(1, 2, 3)) 16 | # object.method_1("--- \n- 1\n- 2\n- 3\n") 17 | # # no error raised 18 | # 19 | # @example Actual parameter is not YAML equivalent of specified +object+. 20 | # object = mock() 21 | # object.expects(:method_1).with(yaml_equivalent(1, 2, 3)) 22 | # object.method_1("--- \n- 1\n- 2\n") 23 | # # error raised, because method_1 was not called with YAML representing the specified Array 24 | def yaml_equivalent(object) 25 | YamlEquivalent.new(object) 26 | end 27 | 28 | # Parameter matcher which matches if actual parameter is YAML equivalent of specified object. 29 | class YamlEquivalent < Base 30 | # @private 31 | def initialize(object) 32 | @object = object 33 | end 34 | 35 | # @private 36 | def matches?(available_parameters) 37 | parameter = available_parameters.shift 38 | # rubocop:disable Security/YAMLLoad 39 | @object == YAML.load(parameter) 40 | # rubocop:enable Security/YAMLLoad 41 | end 42 | 43 | # @private 44 | def mocha_inspect 45 | "yaml_equivalent(#{@object.mocha_inspect})" 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/mocha/inspect.rb: -------------------------------------------------------------------------------- 1 | require 'date' 2 | 3 | module Mocha 4 | module Inspect 5 | module ObjectMethods 6 | def mocha_inspect 7 | address = __id__ * 2 8 | address += 0x100000000 if address < 0 9 | inspect =~ /#x', address: address)}>" : inspect 10 | end 11 | end 12 | 13 | module ArrayMethods 14 | def mocha_inspect(wrapped = true) 15 | unwrapped = collect(&:mocha_inspect).join(', ') 16 | wrapped ? "[#{unwrapped}]" : unwrapped 17 | end 18 | end 19 | 20 | module HashMethods 21 | def mocha_inspect 22 | if Hash.ruby2_keywords_hash?(self) 23 | collect do |key, value| 24 | case key 25 | when Symbol 26 | "#{key}: #{value.mocha_inspect}" 27 | else 28 | "#{key.mocha_inspect} => #{value.mocha_inspect}" 29 | end 30 | end.join(', ') 31 | else 32 | unwrapped = collect { |key, value| "#{key.mocha_inspect} => #{value.mocha_inspect}" }.join(', ') 33 | "{#{unwrapped}}" 34 | end 35 | end 36 | end 37 | 38 | module TimeMethods 39 | def mocha_inspect 40 | "#{inspect} (#{to_f} secs)" 41 | end 42 | end 43 | 44 | module DateMethods 45 | def mocha_inspect 46 | to_s 47 | end 48 | end 49 | end 50 | end 51 | 52 | class Object 53 | include Mocha::Inspect::ObjectMethods 54 | end 55 | 56 | class Array 57 | include Mocha::Inspect::ArrayMethods 58 | end 59 | 60 | class Hash 61 | include Mocha::Inspect::HashMethods 62 | end 63 | 64 | class Time 65 | include Mocha::Inspect::TimeMethods 66 | end 67 | 68 | class Date 69 | include Mocha::Inspect::DateMethods 70 | end 71 | -------------------------------------------------------------------------------- /test/acceptance/multiple_yielding_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class MultipleYieldingTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_yields_values_multiple_times_when_stubbed_method_is_invoked 15 | test_result = run_as_test do 16 | m = mock('m') 17 | m.stubs(:foo).multiple_yields([1], [2, 3]) 18 | yielded = [] 19 | m.foo { |*args| yielded << args } 20 | assert_equal [[1], [2, 3]], yielded 21 | end 22 | assert_passed(test_result) 23 | end 24 | 25 | def test_yields_values_multiple_times_when_multiple_yields_arguments_are_not_arrays 26 | test_result = run_as_test do 27 | m = mock('m') 28 | m.stubs(:foo).multiple_yields(1, { b: 2 }, '3') 29 | yielded = [] 30 | m.foo { |*args| yielded << args } 31 | assert_equal [[1], [{ b: 2 }], ['3']], yielded 32 | end 33 | assert_passed(test_result) 34 | end 35 | 36 | def test_raises_local_jump_error_if_instructed_to_multiple_yield_but_no_block_given 37 | test_result = run_as_test do 38 | m = mock('m') 39 | m.stubs(:foo).multiple_yields([]) 40 | assert_raises(LocalJumpError) { m.foo } 41 | end 42 | assert_passed(test_result) 43 | end 44 | 45 | def test_yields_different_values_on_consecutive_invocations 46 | test_result = run_as_test do 47 | m = mock('m') 48 | m.stubs(:foo).multiple_yields([0], [1, 2]).then.multiple_yields([3], [4, 5]) 49 | yielded = [] 50 | m.foo { |*args| yielded << args } 51 | m.foo { |*args| yielded << args } 52 | assert_equal [[0], [1, 2], [3], [4, 5]], yielded 53 | end 54 | assert_passed(test_result) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/has_key_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | require 'mocha/parameter_matchers/has_key' 4 | require 'mocha/parameter_matchers/instance_methods' 5 | require 'mocha/inspect' 6 | 7 | class HasKeyTest < Mocha::TestCase 8 | include Mocha::ParameterMatchers 9 | 10 | def test_should_match_hash_including_specified_key 11 | matcher = has_key(:key_1) 12 | assert matcher.matches?([{ key_1: 1, key_2: 2 }]) 13 | end 14 | 15 | def test_should_not_match_hash_not_including_specified_key 16 | matcher = has_key(:key_1) 17 | assert !matcher.matches?([{ key_2: 2 }]) 18 | end 19 | 20 | def test_should_describe_matcher 21 | matcher = has_key(:key) 22 | assert_equal 'has_key(:key)', matcher.mocha_inspect 23 | end 24 | 25 | def test_should_match_hash_including_specified_key_with_nested_key_matcher 26 | matcher = has_key(equals(:key_1)) 27 | assert matcher.matches?([{ key_1: 1, key_2: 2 }]) 28 | end 29 | 30 | def test_should_not_match_hash_not_including_specified_key_with_nested_key_matcher 31 | matcher = has_key(equals(:key_1)) 32 | assert !matcher.matches?([{ key_2: 2 }]) 33 | end 34 | 35 | def test_should_not_raise_error_on_empty_arguments 36 | matcher = has_key(:key) 37 | assert_nothing_raised { matcher.matches?([]) } 38 | end 39 | 40 | def test_should_not_match_on_empty_arguments 41 | matcher = has_key(:key) 42 | assert !matcher.matches?([]) 43 | end 44 | 45 | def test_should_not_raise_error_on_argument_that_does_not_respond_to_keys 46 | matcher = has_key(:key) 47 | assert_nothing_raised { matcher.matches?([:key]) } 48 | end 49 | 50 | def test_should_not_match_on_argument_that_does_not_respond_to_keys 51 | matcher = has_key(:key) 52 | assert !matcher.matches?([:key]) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/has_keys.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | 3 | module Mocha 4 | module ParameterMatchers 5 | # Matches +Hash+ containing +keys+. 6 | # 7 | # @param [*Array] keys expected keys. 8 | # @return [HasKeys] parameter matcher. 9 | # 10 | # @see Expectation#with 11 | # 12 | # @example Actual parameter contains entry with expected keys. 13 | # object = mock() 14 | # object.expects(:method_1).with(has_keys(:key_1, :key_2)) 15 | # object.method_1(:key_1 => 1, :key_2 => 2, :key_3 => 3) 16 | # # no error raised 17 | # 18 | # @example Actual parameter does not contain all expected keys. 19 | # object = mock() 20 | # object.expects(:method_1).with(has_keys(:key_1, :key_2)) 21 | # object.method_1(:key_2 => 2) 22 | # # error raised, because method_1 was not called with Hash containing key: :key_1 23 | # 24 | def has_keys(*keys) # rubocop:disable Naming/PredicateName 25 | HasKeys.new(*keys) 26 | end 27 | 28 | # Parameter matcher which matches when actual parameter contains +Hash+ with all expected keys. 29 | class HasKeys < Base 30 | # @private 31 | def initialize(*keys) 32 | raise ArgumentError, 'No arguments. Expecting at least one.' if keys.empty? 33 | 34 | @keys = keys 35 | end 36 | 37 | # @private 38 | def matches?(available_parameters) 39 | parameter = available_parameters.shift 40 | return false unless parameter.respond_to?(:keys) 41 | 42 | @keys.map(&:to_matcher).all? do |matcher| 43 | parameter.keys.any? { |key| matcher.matches?([key]) } 44 | end 45 | end 46 | 47 | # @private 48 | def mocha_inspect 49 | "has_keys(#{@keys.mocha_inspect(false)})" 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/unit/exception_raiser_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | require 'mocha/invocation' 4 | require 'mocha/exception_raiser' 5 | require 'timeout' 6 | 7 | class ExceptionRaiserTest < Mocha::TestCase 8 | include Mocha 9 | 10 | def new_invocation 11 | Invocation.new(:irrelevant, :irrelevant) 12 | end 13 | 14 | def test_should_raise_exception_with_specified_class_and_default_message 15 | exception_class = Class.new(StandardError) 16 | raiser = ExceptionRaiser.new(exception_class, nil) 17 | exception = assert_raises(exception_class) { raiser.evaluate(new_invocation) } 18 | assert_equal exception_class.to_s, exception.message 19 | end 20 | 21 | def test_should_raise_exception_with_specified_class_and_message 22 | exception_class = Class.new(StandardError) 23 | raiser = ExceptionRaiser.new(exception_class, 'message') 24 | exception = assert_raises(exception_class) { raiser.evaluate(new_invocation) } 25 | assert_equal 'message', exception.message 26 | end 27 | 28 | def test_should_raise_exception_instance 29 | exception_class = Class.new(StandardError) 30 | raiser = ExceptionRaiser.new(exception_class.new('message'), nil) 31 | exception = assert_raises(exception_class) { raiser.evaluate(new_invocation) } 32 | assert_equal 'message', exception.message 33 | end 34 | 35 | def test_should_raise_interrupt_exception_with_default_message_so_it_works_in_ruby_1_8_6 36 | raiser = ExceptionRaiser.new(Interrupt, nil) 37 | assert_raises(Interrupt) { raiser.evaluate(new_invocation) } 38 | end 39 | 40 | def test_should_raise_subclass_of_interrupt_exception_with_default_message_so_it_works_in_ruby_1_8_6 41 | exception_class = Class.new(Interrupt) 42 | raiser = ExceptionRaiser.new(exception_class, nil) 43 | assert_raises(exception_class) { raiser.evaluate(new_invocation) } 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/mocha/integration/minitest/adapter.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/api' 2 | require 'mocha/integration/assertion_counter' 3 | require 'mocha/expectation_error_factory' 4 | 5 | module Mocha 6 | module Integration 7 | module Minitest 8 | # Integrates Mocha into recent versions of Minitest. 9 | # 10 | # See the source code for an example of how to integrate Mocha into a test library. 11 | module Adapter 12 | include Mocha::API 13 | 14 | # @private 15 | def self.applicable_to?(minitest_version) 16 | Gem::Requirement.new('>= 3.3.0').satisfied_by?(minitest_version) 17 | end 18 | 19 | # @private 20 | def self.description 21 | 'adapter for Minitest gem >= v3.3.0' 22 | end 23 | 24 | # @private 25 | def self.included(_mod) 26 | Mocha::ExpectationErrorFactory.exception_class = ::Minitest::Assertion 27 | end 28 | 29 | # @private 30 | def before_setup 31 | mocha_setup 32 | super 33 | end 34 | 35 | # @private 36 | def before_teardown 37 | return unless passed? 38 | assertion_counter = Integration::AssertionCounter.new(self) 39 | mocha_verify(assertion_counter) 40 | ensure 41 | super 42 | end 43 | 44 | # @private 45 | def after_teardown 46 | super 47 | mocha_teardown 48 | end 49 | 50 | # @private 51 | def mocha_test_name 52 | if respond_to?(:name) 53 | test_name = name 54 | elsif respond_to?(:__name__) # Older minitest 55 | test_name = __name__ 56 | end 57 | 58 | if test_name 59 | "#{self.class.name}##{test_name}" 60 | else 61 | self.class.name 62 | end 63 | end 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/equivalent_uri.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | require 'uri' 3 | require 'cgi' 4 | 5 | module Mocha 6 | module ParameterMatchers 7 | # Matches a URI without regard to the ordering of parameters in the query string. 8 | # 9 | # @param [String] uri URI to match. 10 | # @return [EquivalentUri] parameter matcher. 11 | # 12 | # @see Expectation#with 13 | # 14 | # @example Actual URI is equivalent. 15 | # object = mock() 16 | # object.expects(:method_1).with(equivalent_uri('http://example.com/foo?a=1&b=2)) 17 | # object.method_1('http://example.com/foo?b=2&a=1') 18 | # # no error raised 19 | # 20 | # @example Actual URI is not equivalent. 21 | # object = mock() 22 | # object.expects(:method_1).with(equivalent_uri('http://example.com/foo?a=1&b=2)) 23 | # object.method_1('http://example.com/foo?a=1&b=3') 24 | # # error raised, because the query parameters were different 25 | def equivalent_uri(uri) 26 | EquivalentUri.new(uri) 27 | end 28 | 29 | # Parameter matcher which matches URIs with equivalent query strings. 30 | class EquivalentUri < Base 31 | # @private 32 | def initialize(uri) 33 | @uri = URI.parse(uri) 34 | end 35 | 36 | # @private 37 | def matches?(available_parameters) 38 | actual = explode(URI.parse(available_parameters.shift)) 39 | expected = explode(@uri) 40 | actual == expected 41 | end 42 | 43 | # @private 44 | def mocha_inspect 45 | "equivalent_uri(#{@uri.mocha_inspect})" 46 | end 47 | 48 | private 49 | 50 | # @private 51 | def explode(uri) 52 | query_hash = CGI.parse(uri.query || '') 53 | URI::Generic::COMPONENT.inject({}) { |h, k| h.merge(k => uri.__send__(k)) }.merge(query: query_hash) 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | AllCops: 4 | TargetRubyVersion: 2.2 # closest to required_ruby_version of '>= 2.1' 5 | 6 | # Even the reference in the documentation suggests that you should prefer 7 | # `alias_method` vs `alias`, so I don't understand why that isn't the default. 8 | Style/Alias: 9 | EnforcedStyle: prefer_alias_method 10 | 11 | Style/Documentation: 12 | Enabled: false 13 | 14 | # Kernel#__dir__ has only been available since Ruby v2.0 15 | Style/ExpandPathArguments: 16 | Enabled: false 17 | 18 | # I'm not keen on this cop, because it's easy to miss the conditional 19 | # I think the results are particularly unhelpful when Metrics/LineLength is big 20 | Style/IfUnlessModifier: 21 | Enabled: false 22 | 23 | # Lambda literal syntax has only been supported since Ruby v2.0 24 | Style/Lambda: 25 | EnforcedStyle: lambda 26 | 27 | # Symbol array literal syntax has only been supported since Ruby v2.0 28 | Style/SymbolArray: 29 | Enabled: false 30 | 31 | # I'm not keen on this cop, because it's easy to miss the while/until 32 | Style/WhileUntilModifier: 33 | Enabled: false 34 | 35 | # This recently introduced cop seems to have stirred up some controversy 36 | Style/AccessModifierDeclarations: 37 | Enabled: false 38 | 39 | # `Module#===` is useful in presence of objects such as mocks 40 | # that may have a `is_a?` implementation that lies. 41 | Style/CaseEquality: 42 | Enabled: false 43 | 44 | # This is useful when using `ExecutionPoint.current` to make tests more robust 45 | Style/Semicolon: 46 | Enabled: false 47 | 48 | # Enabling this cop results in an "Infinite loop detected" exception 49 | Layout/AccessModifierIndentation: 50 | Enabled: false 51 | 52 | # Allow long comment lines, e.g. YARD documentation 53 | Metrics/LineLength: 54 | IgnoredPatterns: ['\A\s*#'] 55 | 56 | # It's not possible to set TargetRubyVersion to Ruby < v2.2 57 | Gemspec/RequiredRubyVersion: 58 | Enabled: false 59 | -------------------------------------------------------------------------------- /test/acceptance/states_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class StatesTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_constrain_expectations_to_occur_within_a_given_state 15 | test_result = run_as_test do 16 | mock = mock() 17 | readiness = states('readiness') 18 | 19 | mock.stubs(:first).when(readiness.is('ready')) 20 | mock.stubs(:second).then(readiness.is('ready')) 21 | 22 | mock.first 23 | end 24 | assert_failed(test_result) 25 | end 26 | 27 | def test_should_allow_expectations_to_occur_in_correct_state 28 | test_result = run_as_test do 29 | mock = mock() 30 | readiness = states('readiness') 31 | 32 | mock.stubs(:first).when(readiness.is('ready')) 33 | mock.stubs(:second).then(readiness.is('ready')) 34 | 35 | mock.second 36 | mock.first 37 | end 38 | assert_passed(test_result) 39 | end 40 | 41 | def test_should_be_able_to_start_in_a_specific_state 42 | test_result = run_as_test do 43 | mock = mock() 44 | readiness = states('readiness') 45 | 46 | mock.stubs(:first).when(readiness.is('ready')) 47 | 48 | readiness.starts_as('ready') 49 | mock.first 50 | end 51 | assert_passed(test_result) 52 | end 53 | 54 | def test_should_switch_state_when_method_raises_an_exception 55 | test_result = run_as_test do 56 | mock = mock() 57 | readiness = states('readiness') 58 | 59 | mock.expects(:first).raises.then(readiness.is('ready')) 60 | mock.expects(:second).when(readiness.is('ready')) 61 | 62 | begin 63 | mock.first 64 | rescue StandardError 65 | nil 66 | end 67 | mock.second 68 | end 69 | assert_passed(test_result) 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /test/acceptance/stub_instance_method_defined_on_active_record_association_proxy_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class StubInstanceMethodDefinedOnActiveRecordAssociationProxyTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_be_able_to_stub_method_if_ruby19_public_methods_include_method_but_method_does_not_exist 15 | ruby19_instance = Class.new do 16 | def public_methods(_include_superclass = true) 17 | [:my_instance_method] 18 | end 19 | end.new 20 | test_result = run_as_test do 21 | ruby19_instance.stubs(:my_instance_method).returns(:new_return_value) 22 | assert_equal :new_return_value, ruby19_instance.my_instance_method 23 | end 24 | assert_passed(test_result) 25 | end 26 | 27 | def test_should_be_able_to_stub_method_if_ruby19_protected_methods_include_method_but_method_does_not_exist 28 | ruby19_instance = Class.new do 29 | def protected_methods(_include_superclass = true) 30 | [:my_instance_method] 31 | end 32 | end.new 33 | test_result = run_as_test do 34 | ruby19_instance.stubs(:my_instance_method).returns(:new_return_value) 35 | assert_equal :new_return_value, ruby19_instance.my_instance_method 36 | end 37 | assert_passed(test_result) 38 | end 39 | 40 | def test_should_be_able_to_stub_method_if_ruby19_private_methods_include_method_but_method_does_not_exist 41 | ruby19_instance = Class.new do 42 | def private_methods(_include_superclass = true) 43 | [:my_instance_method] 44 | end 45 | end.new 46 | test_result = run_as_test do 47 | ruby19_instance.stubs(:my_instance_method).returns(:new_return_value) 48 | assert_equal :new_return_value, ruby19_instance.my_instance_method 49 | end 50 | assert_passed(test_result) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/unit/object_methods_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require 'mocha/object_methods' 3 | require 'mocha/mockery' 4 | require 'mocha/mock' 5 | require 'mocha/expectation_error_factory' 6 | require 'mocha/names' 7 | 8 | class ObjectMethodsTest < Mocha::TestCase 9 | def setup 10 | Mocha::Mockery.setup 11 | @object = Object.new.extend(Mocha::ObjectMethods) 12 | end 13 | 14 | def teardown 15 | Mocha::Mockery.teardown 16 | end 17 | 18 | def test_should_build_mocha_referring_to_self 19 | mocha = @object.mocha 20 | assert_not_nil mocha 21 | assert mocha.is_a?(Mocha::Mock) 22 | expected_name = Mocha::ImpersonatingName.new(@object).mocha_inspect 23 | assert_equal expected_name, mocha.mocha_inspect 24 | end 25 | 26 | def test_should_not_build_mocha_if_instantiate_is_false 27 | assert_nil @object.mocha(false) 28 | end 29 | 30 | def test_should_reuse_existing_mocha 31 | mocha1 = @object.mocha 32 | mocha2 = @object.mocha 33 | assert_equal mocha1, mocha2 34 | end 35 | 36 | def test_should_reuse_existing_mocha_even_if_instantiate_is_false 37 | mocha1 = @object.mocha 38 | mocha2 = @object.mocha(false) 39 | assert_equal mocha1, mocha2 40 | end 41 | 42 | def test_should_reset_mocha 43 | assert_nil @object.reset_mocha 44 | end 45 | 46 | def test_should_use_stubba_instance_method_for_object 47 | assert_equal Mocha::InstanceMethod, @object.stubba_method 48 | end 49 | 50 | def test_should_stub_self_for_object 51 | assert_equal @object, @object.stubba_object 52 | end 53 | 54 | def test_nobody_expects_the_spanish_inquisition 55 | assert_raises(Mocha::ExpectationErrorFactory.exception_class) { @object.expects(:the_spanish_inquisition) } 56 | end 57 | 58 | def test_should_alias_object_method 59 | klass = Class.new { def self.method_x; end } 60 | klass.extend(Mocha::ObjectMethods) 61 | assert_equal klass._method(:method_x), klass.method(:method_x) 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/has_value_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | require 'mocha/parameter_matchers/has_value' 4 | require 'mocha/parameter_matchers/instance_methods' 5 | require 'mocha/parameter_matchers/equals' 6 | require 'mocha/inspect' 7 | 8 | class HasValueTest < Mocha::TestCase 9 | include Mocha::ParameterMatchers 10 | 11 | def test_should_match_hash_including_specified_value 12 | matcher = has_value('value_1') 13 | assert matcher.matches?([{ key_1: 'value_1', key_2: 'value_2' }]) 14 | end 15 | 16 | def test_should_not_match_hash_not_including_specified_value 17 | matcher = has_value('value_1') 18 | assert !matcher.matches?([{ key_2: 'value_2' }]) 19 | end 20 | 21 | def test_should_describe_matcher 22 | matcher = has_value('value_1') 23 | assert_equal %{has_value("value_1")}, matcher.mocha_inspect 24 | end 25 | 26 | def test_should_match_hash_including_specified_value_with_nested_value_matcher 27 | matcher = has_value(equals('value_1')) 28 | assert matcher.matches?([{ key_1: 'value_1', key_2: 'value_2' }]) 29 | end 30 | 31 | def test_should_not_match_hash_not_including_specified_value_with_nested_value_matcher 32 | matcher = has_value(equals('value_1')) 33 | assert !matcher.matches?([{ key_2: 'value_2' }]) 34 | end 35 | 36 | def test_should_not_raise_error_on_empty_arguments 37 | matcher = has_value('value_1') 38 | assert_nothing_raised { matcher.matches?([]) } 39 | end 40 | 41 | def test_should_not_match_empty_arguments 42 | matcher = has_value('value_1') 43 | assert !matcher.matches?([]) 44 | end 45 | 46 | def test_should_not_raise_error_on_argument_that_does_not_respond_to_values 47 | matcher = has_value('value_1') 48 | assert_nothing_raised { matcher.matches?(['value_1']) } 49 | end 50 | 51 | def test_should_not_match_on_argument_that_does_not_respond_to_values 52 | matcher = has_value('value_1') 53 | assert !matcher.matches?(['value_1']) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | unless defined?(STANDARD_OBJECT_PUBLIC_INSTANCE_METHODS) 2 | STANDARD_OBJECT_PUBLIC_INSTANCE_METHODS = Object.instance_methods + Object.private_instance_methods 3 | end 4 | 5 | $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) 6 | $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__))) 7 | $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), 'unit')) 8 | $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), 'unit', 'parameter_matchers')) 9 | $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), 'acceptance')) 10 | 11 | require 'mocha/detection/minitest' 12 | 13 | begin 14 | require 'minitest' 15 | # rubocop:disable Lint/HandleExceptions 16 | rescue LoadError 17 | end 18 | # rubocop:enable Lint/HandleExceptions 19 | 20 | module Mocha; end 21 | 22 | if (minitest_testcase = Mocha::Detection::Minitest.testcase) && (ENV['MOCHA_RUN_INTEGRATION_TESTS'] != 'test-unit') 23 | begin 24 | require 'minitest/autorun' 25 | rescue LoadError 26 | Minitest::Unit.autorun 27 | end 28 | # rubocop:disable Style/ClassAndModuleChildren 29 | class Mocha::TestCase < minitest_testcase 30 | def assert_nothing_raised(exception = StandardError) 31 | yield 32 | rescue exception => e 33 | flunk "Unexpected exception raised: #{e}" 34 | end 35 | 36 | alias_method :assert_not_nil, :refute_nil 37 | alias_method :assert_raise, :assert_raises 38 | alias_method :assert_not_same, :refute_same 39 | alias_method :assert_no_match, :refute_match 40 | end 41 | # rubocop:enable Style/ClassAndModuleChildren 42 | else 43 | require 'test/unit' 44 | # rubocop:disable Style/ClassAndModuleChildren 45 | class Mocha::TestCase < Test::Unit::TestCase 46 | def test_dummy 47 | # Some versions (?) of Test::Unit try to run this base class as a test case 48 | # and it fails because it has no test methods, so I'm adding a dummy test. 49 | end 50 | end 51 | # rubocop:enable Style/ClassAndModuleChildren 52 | end 53 | -------------------------------------------------------------------------------- /lib/mocha/invocation.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameters_matcher' 2 | require 'mocha/raised_exception' 3 | require 'mocha/return_values' 4 | require 'mocha/thrown_object' 5 | require 'mocha/yield_parameters' 6 | 7 | module Mocha 8 | class Invocation 9 | attr_reader :method_name, :block 10 | 11 | def initialize(mock, method_name, arguments = [], block = nil) 12 | @mock = mock 13 | @method_name = method_name 14 | @arguments = arguments 15 | @block = block 16 | @yields = [] 17 | @result = nil 18 | end 19 | 20 | def call(yield_parameters = YieldParameters.new, return_values = ReturnValues.new) 21 | yield_parameters.next_invocation.each do |yield_args| 22 | @yields << ParametersMatcher.new(yield_args) 23 | raise LocalJumpError unless @block 24 | @block.call(*yield_args) 25 | end 26 | return_values.next(self) 27 | end 28 | 29 | def returned(value) 30 | @result = value 31 | end 32 | 33 | def raised(exception) 34 | @result = RaisedException.new(exception) 35 | end 36 | 37 | def threw(tag, value) 38 | @result = ThrownObject.new(tag, value) 39 | end 40 | 41 | def arguments 42 | @arguments.dup 43 | end 44 | 45 | def call_description 46 | description = "#{@mock.mocha_inspect}.#{@method_name}#{argument_description}" 47 | description << ' { ... }' unless @block.nil? 48 | description 49 | end 50 | 51 | def short_call_description 52 | "#{@method_name}(#{@arguments.join(', ')})" 53 | end 54 | 55 | def result_description 56 | desc = "# => #{@result.mocha_inspect}" 57 | desc << " after yielding #{@yields.map(&:mocha_inspect).join(', then ')}" if @yields.any? 58 | desc 59 | end 60 | 61 | def full_description 62 | "\n - #{call_description} #{result_description}" 63 | end 64 | 65 | private 66 | 67 | def argument_description 68 | signature = arguments.mocha_inspect 69 | signature = signature.gsub(/^\[|\]$/, '') 70 | "(#{signature})" 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /test/acceptance/stubba_test_result_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | require 'execution_point' 3 | 4 | class StubbaTestResultTest < Mocha::TestCase 5 | include AcceptanceTest 6 | 7 | def setup 8 | setup_acceptance_test 9 | end 10 | 11 | def teardown 12 | teardown_acceptance_test 13 | end 14 | 15 | def test_should_include_expectation_verification_in_assertion_count 16 | test_result = run_as_test do 17 | object = Class.new do 18 | def message; end 19 | end.new 20 | object.expects(:message) 21 | object.message 22 | end 23 | assert_equal 1, test_result.assertion_count 24 | end 25 | 26 | def test_should_include_assertions_in_assertion_count 27 | test_result = run_as_test do 28 | assert true 29 | end 30 | assert_equal 1, test_result.assertion_count 31 | end 32 | 33 | def test_should_not_include_stubbing_expectation_verification_in_assertion_count 34 | test_result = run_as_test do 35 | object = Class.new do 36 | def message; end 37 | end.new 38 | object.stubs(:message) 39 | object.message 40 | end 41 | assert_equal 0, test_result.assertion_count 42 | end 43 | 44 | def test_should_include_expectation_verification_failure_in_failure_count 45 | test_result = run_as_test do 46 | object = Class.new do 47 | def message; end 48 | end.new 49 | object.expects(:message) 50 | end 51 | assert_equal 1, test_result.failure_count 52 | end 53 | 54 | def test_should_include_assertion_failure_in_failure_count 55 | test_result = run_as_test do 56 | flunk 57 | end 58 | assert_equal 1, test_result.failure_count 59 | end 60 | 61 | def test_should_display_backtrace_indicating_line_number_where_failing_assertion_was_called 62 | execution_point = nil 63 | test_result = run_as_test do 64 | execution_point = ExecutionPoint.current; flunk 65 | end 66 | assert_equal 1, test_result.failure_count 67 | assert_equal execution_point, ExecutionPoint.new(test_result.failures[0].location) 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /test/acceptance/stub_any_instance_method_defined_on_superclass_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class StubAnyInstanceMethodDefinedOnSuperclassTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_stub_method_and_leave_it_unchanged_after_test 15 | superklass = Class.new do 16 | def my_superclass_method 17 | :original_return_value 18 | end 19 | public :my_superclass_method 20 | end 21 | klass = Class.new(superklass) 22 | instance = klass.new 23 | assert_snapshot_unchanged(instance) do 24 | test_result = run_as_test do 25 | superklass.any_instance.stubs(:my_superclass_method).returns(:new_return_value) 26 | assert_equal :new_return_value, instance.my_superclass_method 27 | end 28 | assert_passed(test_result) 29 | end 30 | assert_equal :original_return_value, instance.my_superclass_method 31 | end 32 | 33 | def test_expect_method_on_any_instance_of_superclass_even_if_preceded_by_test_expecting_method_on_any_instance_of_subclass 34 | superklass = Class.new do 35 | def self.inspect 36 | 'superklass' 37 | end 38 | 39 | def my_instance_method; end 40 | end 41 | klass = Class.new(superklass) do 42 | def self.inspect 43 | 'klass' 44 | end 45 | 46 | def my_instance_method; end 47 | end 48 | test_result = run_as_tests( 49 | test_1: lambda { 50 | klass.any_instance.expects(:my_instance_method) 51 | klass.new.my_instance_method 52 | }, 53 | test_2: lambda { 54 | superklass.any_instance.expects(:my_instance_method) 55 | } 56 | ) 57 | assert_failed(test_result) 58 | assert_equal [ 59 | 'not all expectations were satisfied', 60 | 'unsatisfied expectations:', 61 | '- expected exactly once, invoked never: #.my_instance_method(any_parameters)' 62 | ], test_result.failure_message_lines 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/acceptance/stubbing_nil_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | require 'mocha/configuration' 3 | 4 | class StubbingNilTest < Mocha::TestCase 5 | include AcceptanceTest 6 | 7 | def setup 8 | setup_acceptance_test 9 | end 10 | 11 | def teardown 12 | teardown_acceptance_test 13 | end 14 | 15 | if RUBY_VERSION < '2.2.0' 16 | def test_should_allow_stubbing_method_on_nil 17 | Mocha.configure { |c| c.stubbing_method_on_nil = :allow } 18 | test_result = run_as_test do 19 | nil.stubs(:stubbed_method) 20 | end 21 | assert_passed(test_result) 22 | assert !@logger.warnings.include?('stubbing method on nil: nil.stubbed_method') 23 | end 24 | 25 | def test_should_warn_on_stubbing_method_on_nil 26 | Mocha.configure { |c| c.stubbing_method_on_nil = :warn } 27 | test_result = run_as_test do 28 | nil.stubs(:stubbed_method) 29 | end 30 | assert_passed(test_result) 31 | assert @logger.warnings.include?('stubbing method on nil: nil.stubbed_method') 32 | end 33 | 34 | def test_should_prevent_stubbing_method_on_nil 35 | Mocha.configure { |c| c.stubbing_method_on_nil = :prevent } 36 | test_result = run_as_test do 37 | nil.stubs(:stubbed_method) 38 | end 39 | assert_errored(test_result) 40 | assert test_result.error_messages.include?('Mocha::StubbingError: stubbing method on nil: nil.stubbed_method') 41 | end 42 | 43 | def test_should_default_to_prevent_stubbing_method_on_non_mock_object 44 | test_result = run_as_test do 45 | nil.stubs(:stubbed_method) 46 | end 47 | assert_errored(test_result) 48 | assert test_result.error_messages.include?('Mocha::StubbingError: stubbing method on nil: nil.stubbed_method') 49 | end 50 | 51 | def test_should_allow_stubbing_method_on_non_nil_object 52 | Mocha.configure { |c| c.stubbing_method_on_nil = :prevent } 53 | object = Object.new 54 | test_result = run_as_test do 55 | object.stubs(:stubbed_method) 56 | end 57 | assert_passed(test_result) 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/acceptance/prepend_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | require 'mocha/ruby_version' 3 | 4 | class PrependTest < Mocha::TestCase 5 | include AcceptanceTest 6 | 7 | def setup 8 | setup_acceptance_test 9 | end 10 | 11 | def teardown 12 | teardown_acceptance_test 13 | end 14 | 15 | module Mod1 16 | def my_method 17 | super + ' World' 18 | end 19 | end 20 | 21 | module Mod2 22 | def my_method 23 | super + ' Wide' 24 | end 25 | end 26 | 27 | class Klass1 28 | prepend Mod1 29 | prepend Mod2 30 | 31 | def my_method 32 | 'Hello' 33 | end 34 | end 35 | 36 | class Klass2 37 | class << self 38 | prepend Mod1 39 | prepend Mod2 40 | 41 | def my_method 42 | 'Hello' 43 | end 44 | end 45 | end 46 | 47 | def test_stubbing_any_instance_with_multiple_prepended_methods 48 | assert_snapshot_unchanged(Klass1) do 49 | test_result = run_as_test do 50 | Klass1.any_instance.stubs(:my_method).returns('Bye World') 51 | assert_equal 'Bye World', Klass1.new.my_method 52 | end 53 | assert_passed(test_result) 54 | end 55 | assert_equal 'Hello World Wide', Klass1.new.my_method 56 | end 57 | 58 | def test_stubbing_instance_with_multiple_prepended_methods 59 | object = Klass1.new 60 | 61 | assert_snapshot_unchanged(object) do 62 | test_result = run_as_test do 63 | object.stubs(:my_method).returns('Bye World') 64 | assert_equal 'Bye World', object.my_method 65 | assert_equal 'Hello World Wide', Klass1.new.my_method 66 | end 67 | assert_passed(test_result) 68 | end 69 | assert_equal 'Hello World Wide', object.my_method 70 | end 71 | 72 | def test_stubbing_a_prepended_class_method 73 | assert_snapshot_unchanged(Klass2) do 74 | test_result = run_as_test do 75 | Klass2.stubs(:my_method).returns('Bye World') 76 | assert_equal 'Bye World', Klass2.my_method 77 | end 78 | assert_passed(test_result) 79 | end 80 | assert_equal 'Hello World Wide', Klass2.my_method 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/has_entries.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/parameter_matchers/base' 2 | require 'mocha/parameter_matchers/all_of' 3 | require 'mocha/parameter_matchers/has_entry' 4 | 5 | module Mocha 6 | module ParameterMatchers 7 | # Matches +Hash+ containing all +entries+. 8 | # 9 | # @param [Hash] entries expected +Hash+ entries. 10 | # @return [HasEntries] parameter matcher. 11 | # 12 | # @see Expectation#with 13 | # 14 | # @example Actual parameter contains all expected entries. 15 | # object = mock() 16 | # object.expects(:method_1).with(has_entries('key_1' => 1, 'key_2' => 2)) 17 | # object.method_1('key_1' => 1, 'key_2' => 2, 'key_3' => 3) 18 | # # no error raised 19 | # 20 | # @example Actual parameter does not contain all expected entries. 21 | # object = mock() 22 | # object.expects(:method_1).with(has_entries('key_1' => 1, 'key_2' => 2)) 23 | # object.method_1('key_1' => 1, 'key_2' => 99) 24 | # # error raised, because method_1 was not called with Hash containing entries: 'key_1' => 1, 'key_2' => 2 25 | # 26 | def has_entries(entries) # rubocop:disable Naming/PredicateName 27 | HasEntries.new(entries) 28 | end 29 | 30 | # Parameter matcher which matches when actual parameter contains all expected +Hash+ entries. 31 | class HasEntries < Base 32 | # @private 33 | def initialize(entries, exact: false) 34 | @entries = entries 35 | @exact = exact 36 | end 37 | 38 | # @private 39 | def matches?(available_parameters) 40 | parameter = available_parameters.shift 41 | return false unless parameter 42 | return false unless parameter.respond_to?(:keys) 43 | return false if @exact && @entries.length != parameter.keys.length 44 | 45 | has_entry_matchers = @entries.map { |key, value| HasEntry.new(key, value) } 46 | AllOf.new(*has_entry_matchers).matches?([parameter]) 47 | end 48 | 49 | # @private 50 | def mocha_inspect 51 | @exact ? @entries.mocha_inspect : "has_entries(#{@entries.mocha_inspect})" 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /docs/file.COPYING.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | File: COPYING 8 | 9 | — Mocha 2.4.5 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 35 |
36 | 59 | 60 |

Copyright James Mead 2006

61 | 62 |

You may use, copy and redistribute this library under the same terms as Ruby itself or under the MIT license.

63 |
64 | 65 | 70 | 71 |
72 | 73 | -------------------------------------------------------------------------------- /test/acceptance/stub_class_method_defined_on_active_record_association_proxy_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class StubClassMethodDefinedOnActiveRecordAssociationProxyTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_be_able_to_stub_method_if_ruby19_public_methods_include_method_but_method_does_not_actually_exist_like_active_record_association_proxy 15 | ruby19_klass = Class.new do 16 | class << self 17 | def public_methods(_include_superclass = true) 18 | [:my_class_method] 19 | end 20 | end 21 | end 22 | test_result = run_as_test do 23 | ruby19_klass.stubs(:my_class_method).returns(:new_return_value) 24 | assert_equal :new_return_value, ruby19_klass.my_class_method 25 | end 26 | assert_passed(test_result) 27 | end 28 | 29 | def test_should_be_able_to_stub_method_if_ruby19_protected_methods_include_method_but_method_does_not_actually_exist_like_active_record_association_proxy 30 | ruby19_klass = Class.new do 31 | class << self 32 | def protected_methods(_include_superclass = true) 33 | [:my_class_method] 34 | end 35 | end 36 | end 37 | test_result = run_as_test do 38 | ruby19_klass.stubs(:my_class_method).returns(:new_return_value) 39 | assert_equal :new_return_value, ruby19_klass.my_class_method 40 | end 41 | assert_passed(test_result) 42 | end 43 | 44 | def test_should_be_able_to_stub_method_if_ruby19_private_methods_include_method_but_method_does_not_actually_exist_like_active_record_association_proxy 45 | ruby19_klass = Class.new do 46 | class << self 47 | def private_methods(_include_superclass = true) 48 | [:my_class_method] 49 | end 50 | end 51 | end 52 | test_result = run_as_test do 53 | ruby19_klass.stubs(:my_class_method).returns(:new_return_value) 54 | assert_equal :new_return_value, ruby19_klass.my_class_method 55 | end 56 | assert_passed(test_result) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/mocha/class_methods.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/mockery' 2 | require 'mocha/any_instance_method' 3 | 4 | module Mocha 5 | # Methods added to all classes to allow mocking and stubbing on real (i.e. non-mock) objects. 6 | module ClassMethods 7 | # @private 8 | class AnyInstance 9 | def initialize(klass) 10 | @stubba_object = klass 11 | end 12 | 13 | def mocha(instantiate = true) 14 | if instantiate 15 | @mocha ||= Mocha::Mockery.instance.mock_impersonating_any_instance_of(@stubba_object) 16 | else 17 | defined?(@mocha) ? @mocha : nil 18 | end 19 | end 20 | 21 | def stubba_method 22 | Mocha::AnyInstanceMethod 23 | end 24 | 25 | def stubba_class 26 | @stubba_object 27 | end 28 | 29 | def respond_to?(symbol, include_all = false) 30 | @stubba_object.allocate.respond_to?(symbol.to_sym, include_all) 31 | end 32 | 33 | attr_reader :stubba_object 34 | end 35 | 36 | # @return [Mock] a mock object which will detect calls to any instance of this class. 37 | # @raise [StubbingError] if attempting to stub method which is not allowed. 38 | # 39 | # @example Return false to invocation of +Product#save+ for any instance of +Product+. 40 | # Product.any_instance.stubs(:save).returns(false) 41 | # product_1 = Product.new 42 | # assert_equal false, product_1.save 43 | # product_2 = Product.new 44 | # assert_equal false, product_2.save 45 | def any_instance 46 | if frozen? 47 | raise StubbingError.new("can't stub method on frozen object: #{mocha_inspect}.any_instance", caller) 48 | end 49 | @any_instance ||= AnyInstance.new(self) 50 | end 51 | 52 | # @private 53 | # rubocop:disable Metrics/CyclomaticComplexity 54 | def __method_visibility__(method, include_public_methods = true) 55 | (include_public_methods && public_method_defined?(method) && :public) || 56 | (protected_method_defined?(method) && :protected) || 57 | (private_method_defined?(method) && :private) 58 | end 59 | # rubocop:enable Metrics/CyclomaticComplexity 60 | alias_method :__method_exists__?, :__method_visibility__ 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /docs/file_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | File List 19 | 20 | 21 | 22 |
23 |
24 |

File List

25 |
26 | 27 | 28 | Classes 29 | 30 | 31 | 32 | Methods 33 | 34 | 35 | 36 | Files 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 | -------------------------------------------------------------------------------- /test/acceptance/optional_parameters_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class OptionalParameterMatcherTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_pass_if_all_required_parameters_match_and_no_optional_parameters_are_supplied 15 | test_result = run_as_test do 16 | mock = mock() 17 | mock.expects(:method).with(1, 2, optionally(3, 4)) 18 | mock.method(1, 2) 19 | end 20 | assert_passed(test_result) 21 | end 22 | 23 | def test_should_pass_if_all_required_and_optional_parameters_match_and_some_optional_parameters_are_supplied 24 | test_result = run_as_test do 25 | mock = mock() 26 | mock.expects(:method).with(1, 2, optionally(3, 4)) 27 | mock.method(1, 2, 3) 28 | end 29 | assert_passed(test_result) 30 | end 31 | 32 | def test_should_pass_if_all_required_and_optional_parameters_match_and_all_optional_parameters_are_supplied 33 | test_result = run_as_test do 34 | mock = mock() 35 | mock.expects(:method).with(1, 2, optionally(3, 4)) 36 | mock.method(1, 2, 3, 4) 37 | end 38 | assert_passed(test_result) 39 | end 40 | 41 | def test_should_fail_if_all_required_and_optional_parameters_match_but_too_many_optional_parameters_are_supplied 42 | test_result = run_as_test do 43 | mock = mock() 44 | mock.expects(:method).with(1, 2, optionally(3, 4)) 45 | mock.method(1, 2, 3, 4, 5) 46 | end 47 | assert_failed(test_result) 48 | end 49 | 50 | def test_should_fail_if_all_required_parameters_match_but_some_optional_parameters_do_not_match 51 | test_result = run_as_test do 52 | mock = mock() 53 | mock.expects(:method).with(1, 2, optionally(3, 4)) 54 | mock.method(1, 2, 4) 55 | end 56 | assert_failed(test_result) 57 | end 58 | 59 | def test_should_fail_if_all_required_parameters_match_but_no_optional_parameters_match 60 | test_result = run_as_test do 61 | mock = mock() 62 | mock.expects(:method).with(1, 2, optionally(3, 4)) 63 | mock.method(1, 2, 4, 5) 64 | end 65 | assert_failed(test_result) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /test/acceptance/yielding_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class YieldingTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_yields_when_stubbed_method_is_invoked 15 | test_result = run_as_test do 16 | m = mock('m') 17 | m.stubs(:foo).yields 18 | yielded = false 19 | m.foo { yielded = true } 20 | assert yielded 21 | end 22 | assert_passed(test_result) 23 | end 24 | 25 | def test_raises_local_jump_error_if_instructed_to_yield_but_no_block_given 26 | test_result = run_as_test do 27 | m = mock('m') 28 | m.stubs(:foo).yields 29 | assert_raises(LocalJumpError) { m.foo } 30 | end 31 | assert_passed(test_result) 32 | end 33 | 34 | def test_yields_when_block_expected_and_block_given 35 | test_result = run_as_test do 36 | m = mock('m') 37 | m.stubs(:foo).with_block_given.yields 38 | m.stubs(:foo).with_no_block_given.returns(:bar) 39 | yielded = false 40 | m.foo { yielded = true } 41 | assert yielded 42 | end 43 | assert_passed(test_result) 44 | end 45 | 46 | def test_returns_when_no_block_expected_and_no_block_given 47 | test_result = run_as_test do 48 | m = mock('m') 49 | m.stubs(:foo).with_block_given.yields 50 | m.stubs(:foo).with_no_block_given.returns(:bar) 51 | assert_equal :bar, m.foo 52 | end 53 | assert_passed(test_result) 54 | end 55 | 56 | def test_yields_values_when_stubbed_method_is_invoked 57 | test_result = run_as_test do 58 | m = mock('m') 59 | m.stubs(:foo).yields(0, 1) 60 | yielded = [] 61 | m.foo { |*args| yielded << args } 62 | assert_equal [[0, 1]], yielded 63 | end 64 | assert_passed(test_result) 65 | end 66 | 67 | def test_yields_different_values_on_consecutive_invocations 68 | test_result = run_as_test do 69 | m = mock('m') 70 | m.stubs(:foo).yields(0, 1).then.yields(2, 3) 71 | yielded = [] 72 | m.foo { |*args| yielded << args } 73 | m.foo { |*args| yielded << args } 74 | assert_equal [[0, 1], [2, 3]], yielded 75 | end 76 | assert_passed(test_result) 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/mocha/hooks.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/mockery' 2 | 3 | module Mocha 4 | # Integration hooks for test library authors. 5 | # 6 | # The methods in this module should be called from test libraries wishing to integrate with Mocha. 7 | # 8 | # This module is provided as part of the +Mocha::API+ module and is therefore part of the public API, but should only be used by authors of test libraries and not by typical "users" of Mocha. 9 | # 10 | # Integration with Test::Unit and Minitest are provided as part of Mocha, because they are (or were once) part of the Ruby standard library. Integration with other test libraries is not provided as *part* of Mocha, but is supported by means of the methods in this module. 11 | # 12 | # See the code in the +Adapter+ modules for examples of how to use the methods in this module. +Mocha::ExpectationErrorFactory+ may be used if you want +Mocha+ to raise a different type of exception. 13 | # 14 | # @see Mocha::Integration::TestUnit::Adapter 15 | # @see Mocha::Integration::Minitest::Adapter 16 | # @see Mocha::ExpectationErrorFactory 17 | # @see Mocha::API 18 | module Hooks 19 | # Prepares Mocha before a test (only for use by authors of test libraries). 20 | # 21 | # This method should be called before each individual test starts (including before any "setup" code). 22 | def mocha_setup 23 | Mockery.setup 24 | end 25 | 26 | # Verifies that all mock expectations have been met (only for use by authors of test libraries). 27 | # 28 | # This is equivalent to a series of "assertions". 29 | # 30 | # This method should be called at the end of each individual test, before it has been determined whether or not the test has passed. 31 | def mocha_verify(assertion_counter = nil) 32 | Mockery.verify(assertion_counter) 33 | end 34 | 35 | # Resets Mocha after a test (only for use by authors of test libraries). 36 | # 37 | # This method should be called after each individual test has finished (including after any "teardown" code). 38 | def mocha_teardown(origin = mocha_test_name) 39 | Mockery.teardown(origin) 40 | end 41 | 42 | # Returns a string representing the unit test name, to be included in some Mocha 43 | # to help track down potential bugs. 44 | def mocha_test_name 45 | nil 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/optionally.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | module ParameterMatchers 3 | # Matches optional parameters if available. 4 | # 5 | # @param [*Array] matchers matchers for optional parameters. 6 | # @return [Optionally] parameter matcher. 7 | # 8 | # @see Expectation#with 9 | # 10 | # @example Only the two required parameters are supplied and they both match their expected value. 11 | # object = mock() 12 | # object.expects(:method_1).with(1, 2, optionally(3, 4)) 13 | # object.method_1(1, 2) 14 | # # no error raised 15 | # 16 | # @example Both required parameters and one of the optional parameters are supplied and they all match their expected value. 17 | # object = mock() 18 | # object.expects(:method_1).with(1, 2, optionally(3, 4)) 19 | # object.method_1(1, 2, 3) 20 | # # no error raised 21 | # 22 | # @example Both required parameters and both of the optional parameters are supplied and they all match their expected value. 23 | # object = mock() 24 | # object.expects(:method_1).with(1, 2, optionally(3, 4)) 25 | # object.method_1(1, 2, 3, 4) 26 | # # no error raised 27 | # 28 | # @example One of the actual optional parameters does not match the expected value. 29 | # object = mock() 30 | # object.expects(:method_1).with(1, 2, optionally(3, 4)) 31 | # object.method_1(1, 2, 3, 5) 32 | # # error raised, because optional parameters did not match 33 | def optionally(*matchers) 34 | Optionally.new(*matchers) 35 | end 36 | 37 | # Parameter matcher which allows optional parameters to be specified. 38 | class Optionally < Base 39 | # @private 40 | def initialize(*parameters) 41 | @matchers = parameters.map(&:to_matcher) 42 | end 43 | 44 | # @private 45 | def matches?(available_parameters) 46 | index = 0 47 | while !available_parameters.empty? && (index < @matchers.length) 48 | matcher = @matchers[index] 49 | return false unless matcher.matches?(available_parameters) 50 | index += 1 51 | end 52 | true 53 | end 54 | 55 | # @private 56 | def mocha_inspect 57 | "optionally(#{@matchers.map(&:mocha_inspect).join(', ')})" 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/unit/parameter_matchers/has_keys_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../test_helper', __FILE__) 2 | 3 | require 'mocha/parameter_matchers/has_keys' 4 | require 'mocha/parameter_matchers/instance_methods' 5 | require 'mocha/inspect' 6 | 7 | class HasKeysTest < Mocha::TestCase 8 | include Mocha::ParameterMatchers 9 | 10 | def test_should_match_hash_including_specified_keys 11 | matcher = has_keys(:key_1, :key_2) 12 | assert matcher.matches?([{ key_1: 1, key_2: 2, key_3: 3 }]) 13 | end 14 | 15 | def test_should_not_match_hash_not_including_specified_keys 16 | matcher = has_keys(:key_1, :key_2) 17 | assert !matcher.matches?([{ key_3: 3 }]) 18 | end 19 | 20 | def test_should_not_match_hash_not_including_all_keys 21 | matcher = has_keys(:key_1, :key_2) 22 | assert !matcher.matches?([{ key_1: 1, key_3: 3 }]) 23 | end 24 | 25 | def test_should_describe_matcher 26 | matcher = has_keys(:key_1, :key_2) 27 | assert_equal 'has_keys(:key_1, :key_2)', matcher.mocha_inspect 28 | end 29 | 30 | def test_should_match_hash_including_specified_key_with_nested_key_matcher 31 | matcher = has_keys(equals(:key_1), equals(:key_2)) 32 | assert matcher.matches?([{ key_1: 1, key_2: 2 }]) 33 | end 34 | 35 | def test_should_not_match_hash_not_including_specified_keys_with_nested_key_matchers 36 | matcher = has_keys(equals(:key_1), equals(:key2)) 37 | assert !matcher.matches?([{ key_2: 2 }]) 38 | end 39 | 40 | def test_should_not_raise_error_on_empty_arguments 41 | matcher = has_keys(:key_1, :key_2) 42 | assert_nothing_raised { matcher.matches?([]) } 43 | end 44 | 45 | def test_should_not_match_on_empty_arguments 46 | matcher = has_keys(:key_1, :key_2) 47 | assert !matcher.matches?([]) 48 | end 49 | 50 | def test_should_not_raise_error_on_argument_that_does_not_respond_to_keys 51 | matcher = has_keys(:key_1, :key_2) 52 | assert_nothing_raised { matcher.matches?([:key_1]) } 53 | end 54 | 55 | def test_should_not_match_on_argument_that_does_not_respond_to_keys 56 | matcher = has_keys(:key_1, :key_2) 57 | assert !matcher.matches?([:key_1]) 58 | end 59 | 60 | def test_should_raise_argument_error_if_no_keys_are_supplied 61 | e = assert_raises(ArgumentError) { has_keys } 62 | assert_equal 'No arguments. Expecting at least one.', e.message 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/acceptance/stub_class_method_defined_on_module_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class StubClassMethodDefinedOnModuleTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_stub_public_method_and_leave_it_unchanged_after_test 15 | mod = Module.new do 16 | def my_class_method 17 | :original_return_value 18 | end 19 | public :my_class_method 20 | end 21 | klass = Class.new do 22 | extend mod 23 | end 24 | assert_snapshot_unchanged(klass) do 25 | test_result = run_as_test do 26 | klass.stubs(:my_class_method).returns(:new_return_value) 27 | assert_equal :new_return_value, klass.my_class_method 28 | end 29 | assert_passed(test_result) 30 | end 31 | assert_equal :original_return_value, klass.my_class_method 32 | end 33 | 34 | def test_should_stub_protected_method_and_leave_it_unchanged_after_test 35 | mod = Module.new do 36 | def my_class_method 37 | :original_return_value 38 | end 39 | protected :my_class_method 40 | end 41 | klass = Class.new do 42 | extend mod 43 | end 44 | assert_snapshot_unchanged(klass) do 45 | test_result = run_as_test do 46 | klass.stubs(:my_class_method).returns(:new_return_value) 47 | assert_equal :new_return_value, klass.send(:my_class_method) 48 | end 49 | assert_passed(test_result) 50 | end 51 | assert_equal :original_return_value, klass.send(:my_class_method) 52 | end 53 | 54 | def test_should_stub_private_method_and_leave_it_unchanged_after_test 55 | mod = Module.new do 56 | def my_class_method 57 | :original_return_value 58 | end 59 | private :my_class_method 60 | end 61 | klass = Class.new do 62 | extend mod 63 | end 64 | assert_snapshot_unchanged(klass) do 65 | test_result = run_as_test do 66 | klass.stubs(:my_class_method).returns(:new_return_value) 67 | assert_equal :new_return_value, klass.send(:my_class_method) 68 | end 69 | assert_passed(test_result) 70 | end 71 | assert_equal :original_return_value, klass.send(:my_class_method) 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /test/unit/class_methods_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require 'mocha/class_methods' 3 | require 'mocha/object_methods' 4 | require 'mocha/mockery' 5 | require 'mocha/names' 6 | 7 | class ClassMethodsTest < Mocha::TestCase 8 | def setup 9 | Mocha::Mockery.setup 10 | @klass = Class.new.extend(Mocha::ClassMethods, Mocha::ObjectMethods) 11 | end 12 | 13 | def teardown 14 | Mocha::Mockery.teardown 15 | end 16 | 17 | def test_should_build_any_instance_object 18 | any_instance = @klass.any_instance 19 | assert_not_nil any_instance 20 | assert any_instance.is_a?(Mocha::ClassMethods::AnyInstance) 21 | end 22 | 23 | def test_should_return_same_any_instance_object 24 | any_instance1 = @klass.any_instance 25 | any_instance2 = @klass.any_instance 26 | assert_equal any_instance1, any_instance2 27 | end 28 | 29 | def test_any_instance_should_build_mocha_referring_to_klass 30 | mocha = @klass.any_instance.mocha 31 | assert_not_nil mocha 32 | assert mocha.is_a?(Mocha::Mock) 33 | expected_name = Mocha::ImpersonatingAnyInstanceName.new(@klass).mocha_inspect 34 | assert_equal expected_name, mocha.mocha_inspect 35 | end 36 | 37 | def test_any_instance_should_not_build_mocha_if_instantiate_is_false 38 | assert_nil @klass.any_instance.mocha(false) 39 | end 40 | 41 | def test_any_instance_should_reuse_existing_mocha 42 | mocha1 = @klass.any_instance.mocha 43 | mocha2 = @klass.any_instance.mocha 44 | assert_equal mocha1, mocha2 45 | end 46 | 47 | def test_any_instance_should_reuse_existing_mocha_even_if_instantiate_is_false 48 | mocha1 = @klass.any_instance.mocha 49 | mocha2 = @klass.any_instance.mocha(false) 50 | assert_equal mocha1, mocha2 51 | end 52 | 53 | def test_should_use_stubba_class_method_for_class 54 | assert_equal Mocha::InstanceMethod, @klass.stubba_method 55 | end 56 | 57 | def test_should_use_stubba_class_method_for_any_instance 58 | assert_equal Mocha::AnyInstanceMethod, @klass.any_instance.stubba_method 59 | end 60 | 61 | def test_should_stub_self_for_class 62 | assert_equal @klass, @klass.stubba_object 63 | end 64 | 65 | def test_should_stub_relevant_class_for_any_instance 66 | any_instance = @klass.any_instance 67 | assert_equal @klass, any_instance.stubba_object 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /test/acceptance/stubbing_method_unnecessarily_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | require 'mocha/configuration' 3 | 4 | class StubbingMethodUnnecessarilyTest < Mocha::TestCase 5 | include AcceptanceTest 6 | 7 | def setup 8 | setup_acceptance_test 9 | end 10 | 11 | def teardown 12 | teardown_acceptance_test 13 | end 14 | 15 | def test_should_allow_stubbing_method_unnecessarily 16 | Mocha.configure { |c| c.stubbing_method_unnecessarily = :allow } 17 | test_result = run_as_test do 18 | mock = mock('mock') 19 | mock.stubs(:public_method) 20 | end 21 | assert_passed(test_result) 22 | assert !@logger.warnings.include?('stubbing method unnecessarily: #.public_method(any_parameters)') 23 | end 24 | 25 | def test_should_warn_when_stubbing_method_unnecessarily 26 | Mocha.configure { |c| c.stubbing_method_unnecessarily = :warn } 27 | test_result = run_as_test do 28 | mock = mock('mock') 29 | mock.stubs(:public_method) 30 | end 31 | assert_passed(test_result) 32 | assert @logger.warnings.include?('stubbing method unnecessarily: #.public_method(any_parameters)') 33 | end 34 | 35 | def test_should_prevent_stubbing_method_unnecessarily 36 | Mocha.configure { |c| c.stubbing_method_unnecessarily = :prevent } 37 | test_result = run_as_test do 38 | mock = mock('mock') 39 | mock.stubs(:public_method) 40 | end 41 | assert_errored(test_result) 42 | assert test_result.error_messages.include?('Mocha::StubbingError: stubbing method unnecessarily: #.public_method(any_parameters)') 43 | end 44 | 45 | def test_should_default_to_allow_stubbing_method_unnecessarily 46 | test_result = run_as_test do 47 | mock = mock('mock') 48 | mock.stubs(:public_method) 49 | end 50 | assert_passed(test_result) 51 | assert !@logger.warnings.include?('stubbing method unnecessarily: #.public_method(any_parameters)') 52 | end 53 | 54 | def test_should_allow_stubbing_method_when_stubbed_method_is_invoked 55 | Mocha.configure { |c| c.stubbing_method_unnecessarily = :prevent } 56 | test_result = run_as_test do 57 | mock = mock('mock') 58 | mock.stubs(:public_method) 59 | mock.public_method 60 | end 61 | assert_passed(test_result) 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/acceptance/stub_instance_method_defined_on_singleton_class_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class StubInstanceMethodDefinedOnSingletonClassTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_stub_public_method_and_leave_it_unchanged_after_test 15 | instance = Class.new.new 16 | class << instance 17 | def my_singleton_method 18 | :original_return_value 19 | end 20 | public :my_singleton_method 21 | end 22 | assert_snapshot_unchanged(instance) do 23 | test_result = run_as_test do 24 | instance.stubs(:my_singleton_method).returns(:stubbed_return_value) 25 | assert_equal :stubbed_return_value, instance.my_singleton_method 26 | end 27 | assert_passed(test_result) 28 | end 29 | assert_equal :original_return_value, instance.my_singleton_method 30 | end 31 | 32 | def test_should_stub_protected_method_and_leave_it_unchanged_after_test 33 | instance = Class.new.new 34 | class << instance 35 | def my_singleton_method 36 | :original_return_value 37 | end 38 | protected :my_singleton_method 39 | end 40 | assert_snapshot_unchanged(instance) do 41 | test_result = run_as_test do 42 | instance.stubs(:my_singleton_method).returns(:stubbed_return_value) 43 | assert_equal :stubbed_return_value, instance.send(:my_singleton_method) 44 | end 45 | assert_passed(test_result) 46 | end 47 | assert_equal :original_return_value, instance.send(:my_singleton_method) 48 | end 49 | 50 | def test_should_stub_private_method_and_leave_it_unchanged_after_test 51 | instance = Class.new.new 52 | class << instance 53 | def my_singleton_method 54 | :original_return_value 55 | end 56 | private :my_singleton_method 57 | end 58 | assert_snapshot_unchanged(instance) do 59 | test_result = run_as_test do 60 | instance.stubs(:my_singleton_method).returns(:stubbed_return_value) 61 | assert_equal :stubbed_return_value, instance.send(:my_singleton_method) 62 | end 63 | assert_passed(test_result) 64 | end 65 | assert_equal :original_return_value, instance.send(:my_singleton_method) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/positional_or_keyword_hash.rb: -------------------------------------------------------------------------------- 1 | require 'mocha/configuration' 2 | require 'mocha/deprecation' 3 | require 'mocha/parameter_matchers/base' 4 | require 'mocha/parameter_matchers/has_entries' 5 | 6 | module Mocha 7 | module ParameterMatchers 8 | # @private 9 | class PositionalOrKeywordHash < Base 10 | def initialize(value, expectation) 11 | @value = value 12 | @expectation = expectation 13 | end 14 | 15 | def matches?(available_parameters) 16 | parameter, is_last_parameter = extract_parameter(available_parameters) 17 | 18 | return false unless HasEntries.new(@value, exact: true).matches?([parameter]) 19 | 20 | if is_last_parameter && !same_type_of_hash?(parameter, @value) 21 | return false if Mocha.configuration.strict_keyword_argument_matching? 22 | 23 | deprecation_warning(parameter, @value) if Mocha::RUBY_V27_PLUS 24 | end 25 | 26 | true 27 | end 28 | 29 | def mocha_inspect 30 | @value.mocha_inspect 31 | end 32 | 33 | private 34 | 35 | def extract_parameter(available_parameters) 36 | [available_parameters.shift, available_parameters.empty?] 37 | end 38 | 39 | def same_type_of_hash?(actual, expected) 40 | ruby2_keywords_hash?(actual) == ruby2_keywords_hash?(expected) 41 | end 42 | 43 | def deprecation_warning(actual, expected) 44 | details1 = "Expectation #{expectation_definition} expected #{hash_type(expected)} (#{expected.mocha_inspect}),".squeeze(' ') 45 | details2 = "but received #{hash_type(actual)} (#{actual.mocha_inspect})." 46 | sentence1 = 'These will stop matching when strict keyword argument matching is enabled.' 47 | sentence2 = 'See the documentation for Mocha::Configuration#strict_keyword_argument_matching=.' 48 | Deprecation.warning([details1, details2, sentence1, sentence2].join(' ')) 49 | end 50 | 51 | def hash_type(hash) 52 | ruby2_keywords_hash?(hash) ? 'keyword arguments' : 'positional hash' 53 | end 54 | 55 | def ruby2_keywords_hash?(hash) 56 | hash.is_a?(Hash) && ::Hash.ruby2_keywords_hash?(hash) 57 | end 58 | 59 | def expectation_definition 60 | return nil unless @expectation 61 | 62 | "defined at #{@expectation.definition_location}" 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /test/acceptance/mocked_methods_dispatch_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class MockedMethodDispatchTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_find_latest_matching_expectation 15 | test_result = run_as_test do 16 | mock = mock() 17 | mock.stubs(:method).returns(1) 18 | mock.stubs(:method).returns(2) 19 | assert_equal 2, mock.method 20 | assert_equal 2, mock.method 21 | assert_equal 2, mock.method 22 | end 23 | assert_passed(test_result) 24 | end 25 | 26 | def test_should_find_latest_expectation_which_has_not_stopped_matching 27 | test_result = run_as_test do 28 | mock = mock() 29 | mock.stubs(:method).returns(1) 30 | mock.stubs(:method).once.returns(2) 31 | assert_equal 2, mock.method 32 | assert_equal 1, mock.method 33 | assert_equal 1, mock.method 34 | end 35 | assert_passed(test_result) 36 | end 37 | 38 | def test_should_keep_finding_later_stub_and_so_never_satisfy_earlier_expectation 39 | test_result = run_as_test do 40 | mock = mock() 41 | mock.expects(:method).returns(1) 42 | mock.stubs(:method).returns(2) 43 | assert_equal 2, mock.method 44 | assert_equal 2, mock.method 45 | assert_equal 2, mock.method 46 | end 47 | assert_failed(test_result) 48 | end 49 | 50 | def test_should_find_later_expectation_until_it_stops_matching_then_find_earlier_stub 51 | test_result = run_as_test do 52 | mock = mock() 53 | mock.stubs(:method).returns(1) 54 | mock.expects(:method).returns(2) 55 | assert_equal 2, mock.method 56 | assert_equal 1, mock.method 57 | assert_equal 1, mock.method 58 | end 59 | assert_passed(test_result) 60 | end 61 | 62 | def test_should_find_latest_expectation_with_range_of_expected_invocation_count_which_has_not_stopped_matching 63 | test_result = run_as_test do 64 | mock = mock() 65 | mock.stubs(:method).returns(1) 66 | mock.stubs(:method).times(2..3).returns(2) 67 | assert_equal 2, mock.method 68 | assert_equal 2, mock.method 69 | assert_equal 2, mock.method 70 | assert_equal 1, mock.method 71 | assert_equal 1, mock.method 72 | end 73 | assert_passed(test_result) 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /test/acceptance/stub_instance_method_defined_on_class_and_aliased_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class StubInstanceMethodDefinedOnClassAndAliasedTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_stub_public_method_and_leave_it_unchanged_after_test 15 | instance = Class.new do 16 | def my_instance_method 17 | :original_return_value 18 | end 19 | public :my_instance_method 20 | alias_method :my_aliased_method, :my_instance_method 21 | end.new 22 | assert_snapshot_unchanged(instance) do 23 | test_result = run_as_test do 24 | instance.stubs(:my_aliased_method).returns(:new_return_value) 25 | assert_equal :new_return_value, instance.my_aliased_method 26 | end 27 | assert_passed(test_result) 28 | end 29 | assert_equal :original_return_value, instance.my_aliased_method 30 | end 31 | 32 | def test_should_stub_protected_method_and_leave_it_unchanged_after_test 33 | instance = Class.new do 34 | def my_instance_method 35 | :original_return_value 36 | end 37 | protected :my_instance_method 38 | alias_method :my_aliased_method, :my_instance_method 39 | end.new 40 | assert_snapshot_unchanged(instance) do 41 | test_result = run_as_test do 42 | instance.stubs(:my_aliased_method).returns(:new_return_value) 43 | assert_equal :new_return_value, instance.send(:my_aliased_method) 44 | end 45 | assert_passed(test_result) 46 | end 47 | assert_equal :original_return_value, instance.send(:my_aliased_method) 48 | end 49 | 50 | def test_should_stub_private_method_and_leave_it_unchanged_after_test 51 | instance = Class.new do 52 | def my_instance_method 53 | :original_return_value 54 | end 55 | private :my_instance_method 56 | alias_method :my_aliased_method, :my_instance_method 57 | end.new 58 | assert_snapshot_unchanged(instance) do 59 | test_result = run_as_test do 60 | instance.stubs(:my_aliased_method).returns(:new_return_value) 61 | assert_equal :new_return_value, instance.send(:my_aliased_method) 62 | end 63 | assert_passed(test_result) 64 | end 65 | assert_equal :original_return_value, instance.send(:my_aliased_method) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /test/unit/configuration_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require 'mocha/configuration' 3 | require 'mocha/ruby_version' 4 | 5 | class ConfigurationTest < Mocha::TestCase 6 | def teardown 7 | Mocha::Configuration.reset_configuration 8 | end 9 | 10 | def test_allow_temporarily_changes_config_when_given_block 11 | Mocha.configure { |c| c.stubbing_method_unnecessarily = :warn } 12 | yielded = false 13 | Mocha::Configuration.override(stubbing_method_unnecessarily: :allow) do 14 | yielded = true 15 | assert_equal :allow, Mocha.configuration.stubbing_method_unnecessarily 16 | end 17 | assert yielded 18 | assert_equal :warn, Mocha.configuration.stubbing_method_unnecessarily 19 | end 20 | 21 | def test_prevent_temporarily_changes_config_when_given_block 22 | Mocha.configure { |c| c.stubbing_method_unnecessarily = :allow } 23 | yielded = false 24 | Mocha::Configuration.override(stubbing_method_unnecessarily: :prevent) do 25 | yielded = true 26 | assert_equal :prevent, Mocha.configuration.stubbing_method_unnecessarily 27 | end 28 | assert yielded 29 | assert_equal :allow, Mocha.configuration.stubbing_method_unnecessarily 30 | end 31 | 32 | def test_warn_when_temporarily_changes_config_when_given_block 33 | Mocha.configure { |c| c.stubbing_method_unnecessarily = :allow } 34 | yielded = false 35 | Mocha::Configuration.override(stubbing_method_unnecessarily: :warn) do 36 | yielded = true 37 | assert_equal :warn, Mocha.configuration.stubbing_method_unnecessarily 38 | end 39 | assert yielded 40 | assert_equal :allow, Mocha.configuration.stubbing_method_unnecessarily 41 | end 42 | 43 | def test_strict_keyword_argument_matching_works_is_false_by_default 44 | assert !Mocha.configuration.strict_keyword_argument_matching? 45 | end 46 | 47 | if Mocha::RUBY_V27_PLUS 48 | def test_enabling_strict_keyword_argument_matching_works_in_ruby_2_7_and_above 49 | Mocha.configure { |c| c.strict_keyword_argument_matching = true } 50 | assert Mocha.configuration.strict_keyword_argument_matching? 51 | end 52 | else 53 | def test_enabling_strict_keyword_argument_matching_raises_error_if_below_ruby_2_7 54 | assert_raises(RuntimeError, 'Strict keyword argument matching requires Ruby 2.7 and above.') do 55 | Mocha.configure { |c| c.strict_keyword_argument_matching = true } 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /test/acceptance/stub_instance_method_defined_on_class_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class StubInstanceMethodDefinedOnClassTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_stub_public_method_and_leave_it_unchanged_after_test 15 | instance = Class.new do 16 | def my_instance_method 17 | :original_return_value 18 | end 19 | public :my_instance_method 20 | end.new 21 | assert_snapshot_unchanged(instance) do 22 | test_result = run_as_test do 23 | instance.stubs(:my_instance_method).returns(:new_return_value) 24 | assert_method_visibility instance, :my_instance_method, :public 25 | assert_equal :new_return_value, instance.my_instance_method 26 | end 27 | assert_passed(test_result) 28 | end 29 | assert_equal :original_return_value, instance.my_instance_method 30 | end 31 | 32 | def test_should_stub_protected_method_and_leave_it_unchanged_after_test 33 | instance = Class.new do 34 | def my_instance_method 35 | :original_return_value 36 | end 37 | protected :my_instance_method 38 | end.new 39 | assert_snapshot_unchanged(instance) do 40 | test_result = run_as_test do 41 | instance.stubs(:my_instance_method).returns(:new_return_value) 42 | assert_method_visibility instance, :my_instance_method, :protected 43 | assert_equal :new_return_value, instance.send(:my_instance_method) 44 | end 45 | assert_passed(test_result) 46 | end 47 | assert_equal :original_return_value, instance.send(:my_instance_method) 48 | end 49 | 50 | def test_should_stub_private_method_and_leave_it_unchanged_after_test 51 | instance = Class.new do 52 | def my_instance_method 53 | :original_return_value 54 | end 55 | private :my_instance_method 56 | end.new 57 | assert_snapshot_unchanged(instance) do 58 | test_result = run_as_test do 59 | instance.stubs(:my_instance_method).returns(:new_return_value) 60 | assert_method_visibility instance, :my_instance_method, :private 61 | assert_equal :new_return_value, instance.send(:my_instance_method) 62 | end 63 | assert_passed(test_result) 64 | end 65 | assert_equal :original_return_value, instance.send(:my_instance_method) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /test/acceptance/stub_instance_method_defined_on_superclass_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class StubInstanceMethodDefinedOnSuperclassTest < Mocha::TestCase 4 | include AcceptanceTest 5 | 6 | def setup 7 | setup_acceptance_test 8 | end 9 | 10 | def teardown 11 | teardown_acceptance_test 12 | end 13 | 14 | def test_should_stub_public_method_and_leave_it_unchanged_after_test 15 | superklass = Class.new do 16 | def my_superclass_method 17 | :original_return_value 18 | end 19 | public :my_superclass_method 20 | end 21 | klass = Class.new(superklass) 22 | instance = klass.new 23 | assert_snapshot_unchanged(instance) do 24 | test_result = run_as_test do 25 | instance.stubs(:my_superclass_method).returns(:new_return_value) 26 | assert_equal :new_return_value, instance.my_superclass_method 27 | end 28 | assert_passed(test_result) 29 | end 30 | assert_equal :original_return_value, instance.my_superclass_method 31 | end 32 | 33 | def test_should_stub_protected_method_and_leave_it_unchanged_after_test 34 | superklass = Class.new do 35 | def my_superclass_method 36 | :original_return_value 37 | end 38 | protected :my_superclass_method 39 | end 40 | klass = Class.new(superklass) 41 | instance = klass.new 42 | assert_snapshot_unchanged(instance) do 43 | test_result = run_as_test do 44 | instance.stubs(:my_superclass_method).returns(:new_return_value) 45 | assert_equal :new_return_value, instance.send(:my_superclass_method) 46 | end 47 | assert_passed(test_result) 48 | end 49 | assert_equal :original_return_value, instance.send(:my_superclass_method) 50 | end 51 | 52 | def test_should_stub_private_method_and_leave_it_unchanged_after_test 53 | superklass = Class.new do 54 | def my_superclass_method 55 | :original_return_value 56 | end 57 | private :my_superclass_method 58 | end 59 | klass = Class.new(superklass) 60 | instance = klass.new 61 | assert_snapshot_unchanged(instance) do 62 | test_result = run_as_test do 63 | instance.stubs(:my_superclass_method).returns(:new_return_value) 64 | assert_equal :new_return_value, instance.send(:my_superclass_method) 65 | end 66 | assert_passed(test_result) 67 | end 68 | assert_equal :original_return_value, instance.send(:my_superclass_method) 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /test/unit/receivers_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require 'mocha/receivers' 3 | 4 | class ObjectReceiverTest < Mocha::TestCase 5 | include Mocha 6 | 7 | class FakeObject 8 | def initialize(mocha) 9 | @mocha = mocha 10 | end 11 | 12 | def mocha(_instantiate) 13 | @mocha 14 | end 15 | 16 | def is_a?(_klass) 17 | false 18 | end 19 | end 20 | 21 | class FakeClass 22 | attr_reader :superclass 23 | 24 | def initialize(superclass, mocha) 25 | @superclass = superclass 26 | @mocha = mocha 27 | end 28 | 29 | def mocha(_instantiate) 30 | @mocha 31 | end 32 | 33 | def is_a?(klass) 34 | klass == Class 35 | end 36 | end 37 | 38 | def test_mocks_returns_mock_for_object 39 | object = FakeObject.new(:mocha) 40 | receiver = ObjectReceiver.new(object) 41 | assert_equal [:mocha], receiver.mocks 42 | end 43 | 44 | def test_mocks_returns_mocks_for_class_and_its_superclasses 45 | grandparent = FakeClass.new(nil, :grandparent_mocha) 46 | parent = FakeClass.new(grandparent, :parent_mocha) 47 | klass = FakeClass.new(parent, :mocha) 48 | receiver = ObjectReceiver.new(klass) 49 | assert_equal [:mocha, :parent_mocha, :grandparent_mocha], receiver.mocks 50 | end 51 | end 52 | 53 | class AnyInstanceReceiverTest < Mocha::TestCase 54 | include Mocha 55 | 56 | class FakeAnyInstanceClass 57 | class AnyInstance 58 | def initialize(mocha) 59 | @mocha = mocha 60 | end 61 | 62 | def mocha(_instantiate) 63 | @mocha 64 | end 65 | end 66 | 67 | attr_reader :superclass 68 | 69 | def initialize(superclass, mocha) 70 | @superclass = superclass 71 | @mocha = mocha 72 | end 73 | 74 | def any_instance 75 | AnyInstance.new(@mocha) 76 | end 77 | end 78 | 79 | def test_mocks_returns_mocks_for_class_and_its_superclasses 80 | grandparent = FakeAnyInstanceClass.new(nil, :grandparent_mocha) 81 | parent = FakeAnyInstanceClass.new(grandparent, :parent_mocha) 82 | klass = FakeAnyInstanceClass.new(parent, :mocha) 83 | receiver = AnyInstanceReceiver.new(klass) 84 | assert_equal [:mocha, :parent_mocha, :grandparent_mocha], receiver.mocks 85 | end 86 | end 87 | 88 | class DefaultReceiverTest < Mocha::TestCase 89 | include Mocha 90 | 91 | def test_mocks_returns_mock 92 | mock = :mocha 93 | receiver = DefaultReceiver.new(mock) 94 | assert_equal [:mocha], receiver.mocks 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /test/test_runner.rb: -------------------------------------------------------------------------------- 1 | require 'assertions' 2 | 3 | require 'mocha/detection/minitest' 4 | 5 | module TestRunner 6 | def run_as_test(&block) 7 | run_as_tests(test_me: block) 8 | end 9 | 10 | # rubocop:disable Metrics/AbcSize, Metrics/MethodLength 11 | def run_as_tests(methods = {}) 12 | base_class = Mocha::TestCase 13 | test_class = Class.new(base_class) do 14 | def self.name; 15 | 'FakeTest' 16 | end 17 | 18 | include Assertions 19 | 20 | methods.each do |(method_name, proc)| 21 | define_method(method_name, proc) 22 | end 23 | end 24 | 25 | tests = methods.keys.select { |m| m.to_s[/^test/] }.map { |m| test_class.new(m) } 26 | 27 | if Mocha::Detection::Minitest.testcase && (ENV['MOCHA_RUN_INTEGRATION_TESTS'] != 'test-unit') 28 | minitest_version = Gem::Version.new(Mocha::Detection::Minitest.version) 29 | if Gem::Requirement.new('>= 5.0.0').satisfied_by?(minitest_version) 30 | require File.expand_path('../minitest_result', __FILE__) 31 | tests.each(&:run) 32 | Minitest::Runnable.runnables.delete(test_class) 33 | test_result = MinitestResult.new(tests) 34 | elsif Gem::Requirement.new('> 0.0.0', '< 5.0.0').satisfied_by?(minitest_version) 35 | require File.expand_path('../minitest_result_pre_v5', __FILE__) 36 | runner = Minitest::Unit.new 37 | tests.each do |test| 38 | test.run(runner) 39 | end 40 | test_result = MinitestResult.new(runner, tests) 41 | end 42 | else 43 | require File.expand_path('../test_unit_result', __FILE__) 44 | test_result = TestUnitResult.build_test_result 45 | tests.each do |test| 46 | test.run(test_result) {} 47 | end 48 | end 49 | 50 | test_result 51 | end 52 | # rubocop:enable Metrics/AbcSize, Metrics/MethodLength 53 | 54 | def assert_passed(test_result) 55 | flunk "Test errored unexpectedly with message: #{test_result.errors.map(&:exception)}" if test_result.error_count > 0 56 | flunk "Test failed unexpectedly with message: #{test_result.failures}" if test_result.failure_count > 0 57 | end 58 | 59 | def assert_failed(test_result) 60 | flunk "Test errored unexpectedly with message: #{test_result.errors.map(&:exception)}" if test_result.error_count > 0 61 | flunk 'Test passed unexpectedly' unless test_result.failure_count > 0 62 | end 63 | 64 | def assert_errored(test_result) 65 | flunk 'Test did not error as expected' unless test_result.error_count > 0 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /docs/top-level-namespace.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Top Level Namespace 8 | 9 | — Mocha 2.4.5 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 35 |
36 | 61 | 62 |

Top Level Namespace 63 | 64 | 65 | 66 |

67 |
68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
80 | 81 |

Defined Under Namespace

82 |

83 | 84 | 85 | Modules: Mocha 86 | 87 | 88 | 89 | 90 |

91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
101 | 102 | 107 | 108 |
109 | 110 | -------------------------------------------------------------------------------- /test/acceptance/stubbing_error_backtrace_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | require 'execution_point' 3 | 4 | class StubbingErrorBacktraceTest < Mocha::TestCase 5 | include AcceptanceTest 6 | 7 | def setup 8 | setup_acceptance_test 9 | end 10 | 11 | def teardown 12 | teardown_acceptance_test 13 | end 14 | 15 | def test_should_display_backtrace_indicating_line_number_where_attempt_to_stub_non_existent_method_was_made 16 | execution_point = nil 17 | object = Object.new 18 | Mocha.configure { |c| c.stubbing_non_existent_method = :prevent } 19 | test_result = run_as_test do 20 | execution_point = ExecutionPoint.current; object.stubs(:non_existent_method) 21 | end 22 | assert_errored(test_result) 23 | assert_equal execution_point, ExecutionPoint.new(test_result.errors[0].exception.backtrace) 24 | end 25 | 26 | def test_should_display_backtrace_indicating_line_number_where_attempt_to_stub_non_public_method_was_made 27 | execution_point = nil 28 | object = Class.new do 29 | def non_public_method; end 30 | private :non_public_method 31 | end.new 32 | Mocha.configure { |c| c.stubbing_non_public_method = :prevent } 33 | test_result = run_as_test do 34 | execution_point = ExecutionPoint.current; object.stubs(:non_public_method) 35 | end 36 | assert_errored(test_result) 37 | assert_equal execution_point, ExecutionPoint.new(test_result.errors[0].exception.backtrace) 38 | end 39 | 40 | def test_should_display_backtrace_indicating_line_number_where_attempt_to_stub_method_on_non_mock_object_was_made 41 | execution_point = nil 42 | object = Object.new 43 | Mocha.configure { |c| c.stubbing_method_on_non_mock_object = :prevent } 44 | test_result = run_as_test do 45 | execution_point = ExecutionPoint.current; object.stubs(:any_method) 46 | end 47 | assert_errored(test_result) 48 | assert_equal execution_point, ExecutionPoint.new(test_result.errors[0].exception.backtrace) 49 | end 50 | 51 | def test_should_display_backtrace_indicating_line_number_where_method_was_unnecessarily_stubbed 52 | execution_point = nil 53 | object = Object.new 54 | Mocha.configure { |c| c.stubbing_method_unnecessarily = :prevent } 55 | test_result = run_as_test do 56 | execution_point = ExecutionPoint.current; object.stubs(:unused_method) 57 | end 58 | assert_errored(test_result) 59 | assert_equal execution_point, ExecutionPoint.new(test_result.errors[0].exception.backtrace) 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/mocha/stubbed_method.rb: -------------------------------------------------------------------------------- 1 | require 'ruby2_keywords' 2 | require 'mocha/ruby_version' 3 | 4 | module Mocha 5 | class StubbedMethod 6 | PrependedModule = Class.new(Module) 7 | 8 | attr_reader :stubbee, :method_name 9 | 10 | def initialize(stubbee, method_name) 11 | @stubbee = stubbee 12 | @original_method = nil 13 | @original_visibility = nil 14 | @method_name = method_name.to_sym 15 | end 16 | 17 | def stub 18 | hide_original_method 19 | define_new_method 20 | end 21 | 22 | def unstub 23 | remove_new_method 24 | mock.unstub(method_name.to_sym) 25 | return if mock.any_expectations? 26 | reset_mocha 27 | end 28 | 29 | def mock 30 | mock_owner.mocha 31 | end 32 | 33 | def reset_mocha 34 | mock_owner.reset_mocha 35 | end 36 | 37 | def hide_original_method 38 | return unless original_method_owner.__method_exists__?(method_name) 39 | store_original_method_visibility 40 | use_prepended_module_for_stub_method 41 | end 42 | 43 | def define_new_method 44 | self_in_scope = self 45 | method_name_in_scope = method_name 46 | stub_method_owner.send(:define_method, method_name) do |*args, &block| 47 | self_in_scope.mock.handle_method_call(method_name_in_scope, args, block) 48 | end 49 | stub_method_owner.send(:ruby2_keywords, method_name) 50 | retain_original_visibility(stub_method_owner) 51 | end 52 | 53 | def remove_new_method 54 | stub_method_owner.send(:remove_method, method_name) 55 | end 56 | 57 | def matches?(other) 58 | return false unless other.class == self.class 59 | (stubbee.object_id == other.stubbee.object_id) && (method_name == other.method_name) 60 | end 61 | 62 | alias_method :==, :eql? 63 | 64 | def to_s 65 | "#{stubbee}.#{method_name}" 66 | end 67 | 68 | private 69 | 70 | def retain_original_visibility(method_owner) 71 | return unless @original_visibility 72 | Module.instance_method(@original_visibility).bind(method_owner).call(method_name) 73 | end 74 | 75 | def store_original_method_visibility 76 | @original_visibility = original_method_owner.__method_visibility__(method_name) 77 | end 78 | 79 | def use_prepended_module_for_stub_method 80 | @stub_method_owner = PrependedModule.new 81 | original_method_owner.__send__ :prepend, @stub_method_owner 82 | end 83 | 84 | def stub_method_owner 85 | @stub_method_owner ||= original_method_owner 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/mocha/parameter_matchers/base.rb: -------------------------------------------------------------------------------- 1 | module Mocha 2 | module ParameterMatchers 3 | # @abstract Subclass and implement +#matches?+ and +#mocha_inspect+ to define a custom matcher. Also add a suitably named instance method to {ParameterMatchers} to build an instance of the new matcher c.f. {#equals}. 4 | class Base 5 | # A shorthand way of combining two matchers when both must match. 6 | # 7 | # Returns a new {AllOf} parameter matcher combining two matchers using a logical AND. 8 | # 9 | # This shorthand will not work with an implicit equals match. Instead, an explicit {Equals} matcher should be used. 10 | # 11 | # @param [Base] other parameter matcher. 12 | # @return [AllOf] parameter matcher. 13 | # 14 | # @see Expectation#with 15 | # 16 | # @example Alternative ways to combine matchers with a logical AND. 17 | # object = mock() 18 | # object.expects(:run).with(all_of(has_key(:foo), has_key(:bar))) 19 | # object.run(foo: 'foovalue', bar: 'barvalue') 20 | # 21 | # # is exactly equivalent to 22 | # 23 | # object.expects(:run).with(has_key(:foo) & has_key(:bar)) 24 | # object.run(foo: 'foovalue', bar: 'barvalue) 25 | def &(other) 26 | AllOf.new(self, other) 27 | end 28 | 29 | # A shorthand way of combining two matchers when at least one must match. 30 | # 31 | # Returns a new +AnyOf+ parameter matcher combining two matchers using a logical OR. 32 | # 33 | # This shorthand will not work with an implicit equals match. Instead, an explicit {Equals} matcher should be used. 34 | # 35 | # @param [Base] other parameter matcher. 36 | # @return [AnyOf] parameter matcher. 37 | # 38 | # @see Expectation#with 39 | # 40 | # @example Alternative ways to combine matchers with a logical OR. 41 | # object = mock() 42 | # object.expects(:run).with(any_of(has_key(:foo), has_key(:bar))) 43 | # object.run(foo: 'foovalue') 44 | # 45 | # # is exactly equivalent to 46 | # 47 | # object.expects(:run).with(has_key(:foo) | has_key(:bar)) 48 | # object.run(foo: 'foovalue') 49 | # 50 | # @example Using an explicit {Equals} matcher in combination with {#|}. 51 | # object.expects(:run).with(equals(1) | equals(2)) 52 | # object.run(1) # passes 53 | # object.run(2) # passes 54 | # object.run(3) # fails 55 | def |(other) 56 | AnyOf.new(self, other) 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/acceptance/mocha_test_result_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | require 'execution_point' 3 | 4 | class MochaTestResultTest < Mocha::TestCase 5 | include AcceptanceTest 6 | 7 | def setup 8 | setup_acceptance_test 9 | end 10 | 11 | def teardown 12 | teardown_acceptance_test 13 | end 14 | 15 | def test_should_include_expectation_verification_in_assertion_count 16 | test_result = run_as_test do 17 | object = mock 18 | object.expects(:message) 19 | object.message 20 | end 21 | assert_equal 1, test_result.assertion_count 22 | end 23 | 24 | def test_should_include_assertions_in_assertion_count 25 | test_result = run_as_test do 26 | assert true 27 | end 28 | assert_equal 1, test_result.assertion_count 29 | end 30 | 31 | def test_should_not_include_stubbing_expectation_verification_in_assertion_count 32 | test_result = run_as_test do 33 | object = mock 34 | object.stubs(:message) 35 | object.message 36 | end 37 | assert_equal 0, test_result.assertion_count 38 | end 39 | 40 | def test_should_include_expectation_verification_failure_in_failure_count 41 | test_result = run_as_test do 42 | object = mock 43 | object.expects(:message) 44 | end 45 | assert_equal 1, test_result.failure_count 46 | end 47 | 48 | def test_should_include_unexpected_verification_failure_in_failure_count 49 | test_result = run_as_test do 50 | object = mock 51 | object.message 52 | end 53 | assert_equal 1, test_result.failure_count 54 | end 55 | 56 | def test_should_include_assertion_failure_in_failure_count 57 | test_result = run_as_test do 58 | flunk 59 | end 60 | assert_equal 1, test_result.failure_count 61 | end 62 | 63 | def test_should_display_backtrace_indicating_line_number_where_unexpected_method_was_called 64 | execution_point = nil 65 | test_result = run_as_test do 66 | object = mock 67 | execution_point = ExecutionPoint.current; object.message 68 | end 69 | assert_equal 1, test_result.failure_count 70 | assert_equal execution_point, ExecutionPoint.new(test_result.failures[0].location) 71 | end 72 | 73 | def test_should_display_backtrace_indicating_line_number_where_failing_assertion_was_called 74 | execution_point = nil 75 | test_result = run_as_test do 76 | execution_point = ExecutionPoint.current; flunk 77 | end 78 | assert_equal 1, test_result.failure_count 79 | assert_equal execution_point, ExecutionPoint.new(test_result.failures[0].location) 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /test/acceptance/failure_messages_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../acceptance_test_helper', __FILE__) 2 | 3 | class FailureMessagesTest < Mocha::TestCase 4 | OBJECT_ADDRESS_PATTERN = '0x[0-9A-Fa-f]{1,12}'.freeze 5 | 6 | include AcceptanceTest 7 | 8 | def setup 9 | setup_acceptance_test 10 | end 11 | 12 | def teardown 13 | teardown_acceptance_test 14 | end 15 | 16 | class Foo; end 17 | 18 | def test_should_display_class_name_when_expectation_was_on_class 19 | test_result = run_as_test do 20 | Foo.expects(:bar) 21 | end 22 | assert_match Regexp.new('FailureMessagesTest::Foo'), test_result.failures[0].message 23 | end 24 | 25 | def test_should_display_class_name_and_address_when_expectation_was_on_instance 26 | test_result = run_as_test do 27 | Foo.new.expects(:bar) 28 | end 29 | assert_match Regexp.new("#"), test_result.failures[0].message 30 | end 31 | 32 | def test_should_display_class_name_and_any_instance_prefix_when_expectation_was_on_any_instance 33 | test_result = run_as_test do 34 | Foo.any_instance.expects(:bar) 35 | end 36 | assert_match Regexp.new('#'), test_result.failures[0].message 37 | end 38 | 39 | def test_should_display_mock_name_when_expectation_was_on_named_mock 40 | test_result = run_as_test do 41 | foo = mock('foo') 42 | foo.expects(:bar) 43 | end 44 | assert_match Regexp.new('#'), test_result.failures[0].message 45 | end 46 | 47 | def test_should_display_mock_address_when_expectation_was_on_unnamed_mock 48 | test_result = run_as_test do 49 | foo = mock 50 | foo.expects(:bar) 51 | end 52 | assert_match Regexp.new("#"), test_result.failures[0].message 53 | end 54 | 55 | def test_should_display_string_when_expectation_was_on_string 56 | test_result = run_as_test do 57 | 'Foo'.expects(:bar) 58 | end 59 | assert_match Regexp.new(%("Foo")), test_result.failures[0].message 60 | end 61 | 62 | def test_should_display_that_block_was_expected 63 | test_result = run_as_test do 64 | foo = mock 65 | foo.expects(:bar).with_block_given 66 | end 67 | assert_match Regexp.new(' with block given$'), test_result.failures[0].message 68 | end 69 | 70 | def test_should_display_that_block_was_not_expected 71 | test_result = run_as_test do 72 | foo = mock 73 | foo.expects(:bar).with_no_block_given 74 | end 75 | assert_match Regexp.new(' with no block given$'), test_result.failures[0].message 76 | end 77 | end 78 | --------------------------------------------------------------------------------