├── .autotest ├── .document ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .rubocop_rspec_base.yml ├── .rubocop_todo.yml ├── .yardopts ├── BUILD_DETAIL.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Changelog.md ├── DEV-README.md ├── DEVELOPMENT.md ├── Gemfile ├── Gemfile-custom.sample ├── Guardfile ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── README.md ├── REPORT_TEMPLATE.md ├── Rakefile ├── SECURITY.md ├── benchmarks ├── accessing_configuration_via_method_vs_cache.rb ├── allocations │ ├── 1_object_1000_mocks.rb │ ├── helper.rb │ └── lambda_vs_block_with_select.rb ├── boot_time.sh ├── double_creation.rb ├── each_value_v_values_each.rb ├── find_original_method_early.rb ├── method_defined_at_any_visibility.rb ├── thread_safety.rb └── transfer_nested_constants.rb ├── cucumber.yml ├── features ├── .nav ├── README.md ├── basics │ ├── allowing_messages.feature │ ├── expecting_messages.feature │ ├── null_object_doubles.feature │ ├── partial_test_doubles.feature │ ├── scope.feature │ ├── spies.feature │ └── test_doubles.feature ├── configuring_responses │ ├── README.md │ ├── block_implementation.feature │ ├── calling_the_original_implementation.feature │ ├── mixed_responses.feature │ ├── raising_an_error.feature │ ├── returning_a_value.feature │ ├── throwing.feature │ ├── wrapping_the_original_implementation.feature │ └── yielding.feature ├── mutating_constants │ ├── README.md │ ├── hide_defined_constant.feature │ ├── hide_undefined_constant.feature │ ├── stub_defined_constant.feature │ └── stub_undefined_constant.feature ├── old_syntax │ ├── README.md │ ├── any_instance.feature │ ├── should_receive.feature │ ├── stub.feature │ ├── stub_chain.feature │ └── unstub.feature ├── outside_rspec │ ├── any_test_framework.feature │ ├── minitest.feature │ └── standalone.feature ├── setting_constraints │ ├── README.md │ ├── matching_arguments.feature │ ├── message_order.feature │ └── receive_counts.feature ├── step_definitions │ └── additional_cli_steps.rb ├── support │ ├── disallow_certain_apis.rb │ ├── env.rb │ └── rubinius.rb ├── verifying_doubles │ ├── README.md │ ├── class_doubles.feature │ ├── dynamic_classes.feature │ ├── instance_doubles.feature │ ├── object_doubles.feature │ └── partial_doubles.feature └── working_with_legacy_code │ ├── README.md │ ├── any_instance.feature │ └── message_chains.feature ├── lib └── rspec │ ├── mocks.rb │ └── mocks │ ├── any_instance.rb │ ├── any_instance │ ├── chain.rb │ ├── error_generator.rb │ ├── expect_chain_chain.rb │ ├── expectation_chain.rb │ ├── message_chains.rb │ ├── proxy.rb │ ├── recorder.rb │ ├── stub_chain.rb │ └── stub_chain_chain.rb │ ├── argument_list_matcher.rb │ ├── argument_matchers.rb │ ├── configuration.rb │ ├── error_generator.rb │ ├── example_methods.rb │ ├── instance_method_stasher.rb │ ├── marshal_extension.rb │ ├── matchers │ ├── expectation_customization.rb │ ├── have_received.rb │ ├── receive.rb │ ├── receive_message_chain.rb │ └── receive_messages.rb │ ├── message_chain.rb │ ├── message_expectation.rb │ ├── method_double.rb │ ├── method_reference.rb │ ├── minitest_integration.rb │ ├── mutate_const.rb │ ├── object_reference.rb │ ├── order_group.rb │ ├── proxy.rb │ ├── space.rb │ ├── standalone.rb │ ├── syntax.rb │ ├── targets.rb │ ├── test_double.rb │ ├── verifying_double.rb │ ├── verifying_message_expectation.rb │ ├── verifying_proxy.rb │ └── version.rb ├── maintenance-branch ├── rspec-mocks.gemspec ├── script ├── ci_functions.sh ├── clone_all_rspec_repos ├── cucumber.sh ├── functions.sh ├── ignores ├── legacy_setup.sh ├── list_method_cache_busters.sh ├── predicate_functions.sh ├── run_build ├── run_rubocop └── update_rubygems_and_install_bundler ├── spec ├── integration │ └── rails_support_spec.rb ├── rspec │ ├── mocks │ │ ├── and_call_original_spec.rb │ │ ├── and_invoke_spec.rb │ │ ├── and_return_spec.rb │ │ ├── and_wrap_original_spec.rb │ │ ├── and_yield_spec.rb │ │ ├── any_instance │ │ │ └── message_chains_spec.rb │ │ ├── any_instance_spec.rb │ │ ├── argument_matchers_spec.rb │ │ ├── array_including_matcher_spec.rb │ │ ├── at_least_spec.rb │ │ ├── at_most_spec.rb │ │ ├── before_all_spec.rb │ │ ├── block_return_value_spec.rb │ │ ├── combining_implementation_instructions_spec.rb │ │ ├── configuration_spec.rb │ │ ├── diffing_spec.rb │ │ ├── double_spec.rb │ │ ├── error_generator_spec.rb │ │ ├── example_methods_spec.rb │ │ ├── expiration_spec.rb │ │ ├── failure_notification_spec.rb │ │ ├── formatting_spec.rb │ │ ├── hash_excluding_matcher_spec.rb │ │ ├── hash_including_matcher_spec.rb │ │ ├── instance_method_stasher_spec.rb │ │ ├── marshal_extension_spec.rb │ │ ├── matchers │ │ │ ├── have_received_spec.rb │ │ │ ├── receive_message_chain_spec.rb │ │ │ ├── receive_messages_spec.rb │ │ │ └── receive_spec.rb │ │ ├── message_expectation_string_representation_spec.rb │ │ ├── methods_spec.rb │ │ ├── mock_expectation_error_spec.rb │ │ ├── mock_ordering_spec.rb │ │ ├── modifying_invoked_expectations_spec.rb │ │ ├── multiple_invoke_handler_spec.rb │ │ ├── multiple_return_value_spec.rb │ │ ├── mutate_const_spec.rb │ │ ├── mutex_spec.rb │ │ ├── nil_expectation_warning_spec.rb │ │ ├── null_object_double_spec.rb │ │ ├── once_counts_spec.rb │ │ ├── order_group_spec.rb │ │ ├── partial_double_spec.rb │ │ ├── partial_double_using_mocks_directly_spec.rb │ │ ├── precise_counts_spec.rb │ │ ├── reraising_eager_raises_spec.rb │ │ ├── serialization_spec.rb │ │ ├── should_syntax_spec.rb │ │ ├── space_spec.rb │ │ ├── spy_spec.rb │ │ ├── standalone_spec.rb │ │ ├── stash_spec.rb │ │ ├── stub_chain_spec.rb │ │ ├── stub_implementation_spec.rb │ │ ├── stub_spec.rb │ │ ├── stubbed_message_expectations_spec.rb │ │ ├── syntax_agnostic_message_matchers_spec.rb │ │ ├── syntax_spec.rb │ │ ├── test_double_spec.rb │ │ ├── thrice_counts_spec.rb │ │ ├── to_ary_spec.rb │ │ ├── twice_counts_spec.rb │ │ └── verifying_doubles │ │ │ ├── class_double_with_class_loaded_spec.rb │ │ │ ├── class_double_with_class_not_loaded_spec.rb │ │ │ ├── construction_spec.rb │ │ │ ├── expected_arg_verification_spec.rb │ │ │ ├── instance_double_with_class_loaded_spec.rb │ │ │ ├── instance_double_with_class_not_loaded_spec.rb │ │ │ ├── method_visibility_spec.rb │ │ │ ├── naming_spec.rb │ │ │ └── object_double_spec.rb │ └── mocks_spec.rb ├── spec_helper.rb └── support │ ├── aruba.rb │ ├── before_all_shared_example_group.rb │ ├── doubled_classes.rb │ └── matchers.rb └── tmp └── .gitkeep /.autotest: -------------------------------------------------------------------------------- 1 | Autotest.add_hook :initialize do |at| 2 | at.clear_mappings 3 | at.add_mapping(%r%\.rb$%) { 4 | at.files_matching %r%^spec/.*_spec\.rb$% 5 | } 6 | end 7 | 8 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | - 3 | README.md 4 | LICENSE.md 5 | Changelog.md 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # This file was generated on 2023-04-16T20:53:23+01:00 from the rspec-dev repo. 2 | # DO NOT modify it by hand as your changes will get lost the next time it is generated. 3 | 4 | github: [JonRowe, benoittgt] 5 | open_collective: rspec 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # This file was generated on 2024-09-03T15:05:50+01:00 from the rspec-dev repo. 2 | # DO NOT modify it by hand as your changes will get lost the next time it is generated. 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "github-actions" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | .DS_Store 3 | coverage 4 | rdoc 5 | pkg 6 | doc 7 | tmp 8 | rerun.txt 9 | Gemfile.lock 10 | .bundle 11 | .idea 12 | *.rbc 13 | .yardoc 14 | bin 15 | Gemfile-custom 16 | bundle 17 | .rbx 18 | .rspec-local 19 | spec/examples.txt 20 | specs.out 21 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --warnings 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: 2 | - .rubocop_todo.yml 3 | - .rubocop_rspec_base.yml 4 | 5 | # All these metrics should go down over time. 6 | 7 | Metrics/ClassLength: 8 | Max: 279 9 | 10 | Metrics/CyclomaticComplexity: 11 | Max: 18 12 | 13 | Layout/LineLength: 14 | Max: 193 15 | Exclude: 16 | - features/**/* 17 | - spec/**/* 18 | 19 | Metrics/MethodLength: 20 | Max: 49 21 | 22 | Metrics/AbcSize: 23 | Max: 47 24 | 25 | Metrics/BlockLength: 26 | Max: 45 27 | Exclude: 28 | - spec/**/* 29 | 30 | Metrics/ModuleLength: 31 | Max: 220 32 | Exclude: 33 | - spec/**/* 34 | 35 | Metrics/PerceivedComplexity: 36 | Max: 19 37 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --exclude features 2 | --no-private 3 | --markup markdown 4 | - 5 | Changelog.md 6 | LICENSE.md 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # Contributor Code of Conduct 7 | 8 | For the purpose of building a welcoming, harassment-free community that 9 | values contributions from anyone, the RSpec project has adopted the 10 | following code of conduct. All contributors and participants (including 11 | maintainers!) are expected to abide by its terms. 12 | 13 | As contributors and maintainers of this project, and in the interest of 14 | fostering an open and welcoming community, we pledge to respect all people who 15 | contribute through reporting issues, posting feature requests, updating 16 | documentation, submitting pull requests or patches, and other activities. 17 | 18 | We are committed to making participation in this project a harassment-free 19 | experience for everyone, regardless of level of experience, gender, gender 20 | identity and expression, sexual orientation, disability, personal appearance, 21 | body size, race, ethnicity, age, religion, or nationality. 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery 26 | * Personal attacks 27 | * Trolling or insulting/derogatory comments 28 | * Public or private harassment 29 | * Publishing other's private information, such as physical or electronic 30 | addresses, without explicit permission 31 | * Other unethical or unprofessional conduct 32 | 33 | Project maintainers have the right and responsibility to remove, edit, or 34 | reject comments, commits, code, wiki edits, issues, and other contributions 35 | that are not aligned to this Code of Conduct, or to ban temporarily or 36 | permanently any contributor for other behaviors that they deem inappropriate, 37 | threatening, offensive, or harmful. 38 | 39 | By adopting this Code of Conduct, project maintainers commit themselves to 40 | fairly and consistently applying these principles to every aspect of managing 41 | this project. Project maintainers who do not follow or enforce the Code of 42 | Conduct may be permanently removed from the project team. 43 | 44 | This Code of Conduct applies both within project spaces and in public spaces 45 | when an individual is representing the project or its community. 46 | 47 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 48 | reported by contacting one of the project maintainers listed at 49 | https://rspec.info/about/. All complaints will be reviewed and investigated 50 | and will result in a response that is deemed necessary and appropriate to the 51 | circumstances. Maintainers are obligated to maintain confidentiality with 52 | regard to the reporter of an incident. 53 | 54 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 55 | version 1.3.0, available at 56 | [https://contributor-covenant.org/version/1/3/0/][version] 57 | 58 | [homepage]: https://contributor-covenant.org 59 | [version]: https://contributor-covenant.org/version/1/3/0/ 60 | -------------------------------------------------------------------------------- /DEV-README.md: -------------------------------------------------------------------------------- 1 | ## Set up the dev environment 2 | 3 | git clone https://github.com/rspec/rspec-mocks.git 4 | cd rspec-mocks 5 | gem install bundler 6 | bundle install 7 | 8 | Now you should be able to run any of: 9 | 10 | rake 11 | rake spec 12 | rake cucumber 13 | 14 | Or, if you prefer to use the rspec and cucumber commands directly, you can either: 15 | 16 | bundle exec rspec 17 | 18 | Or ... 19 | 20 | bundle install --binstubs 21 | bin/rspec 22 | 23 | ## Customize the dev environment 24 | 25 | The Gemfile includes the gems you'll need to be able to run specs. If you want 26 | to customize your dev environment with additional tools like guard or 27 | ruby-debug, add any additional gem declarations to Gemfile-custom (see 28 | Gemfile-custom.sample for some examples). 29 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | branch = File.read(File.expand_path("../maintenance-branch", __FILE__)).chomp 6 | %w[rspec rspec-core rspec-expectations rspec-support].each do |lib| 7 | library_path = File.expand_path("../../#{lib}", __FILE__) 8 | if File.exist?(library_path) && !ENV['USE_GIT_REPOS'] 9 | gem lib, :path => library_path 10 | else 11 | if lib == 'rspec' 12 | gem 'rspec', :git => "https://github.com/rspec/rspec-metagem.git", :branch => branch 13 | else 14 | gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => branch 15 | end 16 | end 17 | end 18 | 19 | if RUBY_VERSION < '1.9.3' 20 | gem 'rake', '< 11.0.0' # rake 11 requires Ruby 1.9.3 or later 21 | elsif RUBY_VERSION < '2.0.0' 22 | gem 'rake', '< 12.0.0' # rake 12 requires Ruby 2.0.0 or later 23 | else 24 | gem 'rake', '> 12.3.2' 25 | end 26 | 27 | if ENV['DIFF_LCS_VERSION'] 28 | gem 'diff-lcs', ENV['DIFF_LCS_VERSION'] 29 | else 30 | gem 'diff-lcs', '~> 1.4', '>= 1.4.3' 31 | end 32 | 33 | gem 'yard', '~> 0.9.24', :require => false 34 | 35 | # No need to run rubocop on earlier versions 36 | if RUBY_VERSION >= '2.4' && RUBY_ENGINE == 'ruby' 37 | gem "rubocop", "~> 1.0", "< 1.12" 38 | end 39 | 40 | # allow gems to be installed on older rubies and/or windows 41 | if RUBY_VERSION < '2.2.0' && !!(RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/) 42 | gem 'ffi', '< 1.10' 43 | elsif RUBY_VERSION < '2.4.0' && !!(RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/) 44 | gem 'ffi', '< 1.15' 45 | elsif RUBY_VERSION < '1.9' 46 | gem 'ffi', '< 1.9.19' # ffi dropped Ruby 1.8 support in 1.9.19 47 | elsif RUBY_VERSION < '2.0' 48 | gem 'ffi', '< 1.11.0' # ffi dropped Ruby 1.9 support in 1.11.0 49 | elsif RUBY_VERSION < '2.3.0' 50 | gem 'ffi', '~> 1.12.0' 51 | else 52 | gem 'ffi', '~> 1.15.0' 53 | end 54 | 55 | if RUBY_VERSION <= '2.3.0' && !!(RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/) 56 | gem "childprocess", "< 1.0.0" 57 | elsif RUBY_VERSION < '2.0.0' 58 | gem "childprocess", "< 1.0.0" 59 | elsif RUBY_VERSION < '2.3.0' 60 | gem "childprocess", "< 3.0.0" 61 | else 62 | gem "childprocess", ">= 3.0.0" 63 | end 64 | 65 | if RUBY_VERSION < '1.9.2' 66 | gem 'contracts', '~> 0.15.0' # is a dependency of aruba 67 | end 68 | 69 | # Version 5.12 of minitest requires Ruby 2.4 70 | if RUBY_VERSION < '2.4.0' 71 | gem 'minitest', '< 5.12.0' 72 | end 73 | 74 | if RUBY_VERSION < '2.0.0' 75 | gem 'thor', '< 1.0.0' 76 | else 77 | gem 'thor', '> 1.0.0' 78 | end 79 | 80 | ### deps for rdoc.info 81 | group :documentation do 82 | gem 'redcarpet', :platform => :mri 83 | gem 'github-markup', :platform => :mri 84 | end 85 | 86 | group :coverage do 87 | gem 'simplecov', '~> 0.8' 88 | end 89 | 90 | if RUBY_VERSION < '2.0.0' || RUBY_ENGINE == 'java' 91 | gem 'json', '< 2.0.0' # this is a dependency of simplecov 92 | else 93 | gem 'json', '> 2.3.0' 94 | end 95 | 96 | if RUBY_VERSION < '2.0.0' 97 | gem 'cucumber', "<= 1.3.22" 98 | elsif !ENV['DIFF_LCS_VERSION'].to_s.empty? && ENV['DIFF_LCS_VERSION'].scan(/\d\.\d/).first.to_f < 1.5 99 | # Older version of diff-lcs cause a downstream error with cucumber and modern rails 100 | gem "activesupport", "< 7" 101 | end 102 | 103 | platforms :jruby do 104 | if RUBY_VERSION < '1.9.0' 105 | # Pin jruby-openssl on older J Ruby 106 | gem "jruby-openssl", "< 0.10.0" 107 | # Pin child-process on older J Ruby 108 | gem "childprocess", "< 1.0.0" 109 | else 110 | gem "jruby-openssl" 111 | end 112 | end 113 | 114 | eval File.read('Gemfile-custom') if File.exist?('Gemfile-custom') 115 | -------------------------------------------------------------------------------- /Gemfile-custom.sample: -------------------------------------------------------------------------------- 1 | group :development do 2 | gem 'interactive_rspec' 3 | gem 'guard-rspec', '~> 1.2.1' 4 | gem 'growl', '1.0.3' 5 | gem 'spork', '0.9.0' 6 | 7 | platform :mri do 8 | gem 'rb-fsevent', '~> 0.9.0' 9 | gem 'ruby-prof', '~> 0.10.0' 10 | 11 | case RUBY_VERSION 12 | when /^1.8/ 13 | gem 'ruby-debug' 14 | when /^1.9/ 15 | gem 'debugger' 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at http://github.com/guard/guard#readme 3 | 4 | guard 'rspec', :version => 2 do 5 | watch(/^spec\/(.*)_spec.rb/) 6 | watch(/^lib\/(.*)\.rb/) { "spec" } 7 | watch(/^spec\/spec_helper.rb/) { "spec" } 8 | end 9 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | ### Subject of the issue 7 | 10 | 11 | ### Your environment 12 | * Ruby version: 13 | * rspec-mocks version: 14 | 15 | ### Steps to reproduce 16 | 20 | 21 | ### Expected behavior 22 | 25 | 26 | ### Actual behavior 27 | 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | * Copyright © 2012 David Chelimsky, Myron Marston 5 | * Copyright © 2006 David Chelimsky, The RSpec Development Team 6 | * Copyright © 2005 Steven Baker 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /REPORT_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | # Report template 7 | 8 | ```ruby 9 | # frozen_string_literal: true 10 | 11 | begin 12 | require "bundler/inline" 13 | rescue LoadError => e 14 | $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler" 15 | raise e 16 | end 17 | 18 | gemfile(true) do 19 | source "https://rubygems.org" 20 | 21 | gem "rspec", "3.7.0" # Activate the gem and version you are reporting the issue against. 22 | end 23 | 24 | puts "Ruby version is: #{RUBY_VERSION}" 25 | require 'rspec/autorun' 26 | 27 | RSpec.describe 'additions' do 28 | it 'returns 2' do 29 | expect(1 + 1).to eq(2) 30 | end 31 | 32 | it 'returns 1' do 33 | expect(3 - 1).to eq(-1) 34 | end 35 | end 36 | ``` 37 | 38 | Simply copy the content of the appropriate template into a `.rb` file on your computer 39 | and make the necessary changes to demonstrate the issue. You can execute it by running 40 | `ruby rspec_report.rb` in your terminal. 41 | 42 | You can then share your executable test case as a [gist](https://gist.github.com), or 43 | simply paste the content into the issue description. 44 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.setup 3 | Bundler::GemHelper.install_tasks 4 | 5 | require 'rake' 6 | require 'rspec/core/rake_task' 7 | require 'rspec/mocks/version' 8 | require 'cucumber/rake/task' 9 | 10 | desc "Run all examples" 11 | RSpec::Core::RakeTask.new(:spec) do |t| 12 | t.ruby_opts = %w[-w] 13 | t.rspec_opts = %w[--color] 14 | end 15 | 16 | Cucumber::Rake::Task.new(:cucumber) 17 | 18 | task :clobber do 19 | rm_rf 'pkg' 20 | rm_rf 'tmp' 21 | rm_rf 'coverage' 22 | rm_rf '.yardoc' 23 | rm_rf 'doc' 24 | end 25 | 26 | namespace :clobber do 27 | desc "remove generated rbc files" 28 | task :rbc do 29 | Dir['**/*.rbc'].each { |f| File.delete(f) } 30 | end 31 | end 32 | 33 | task :default => [:spec, :cucumber] 34 | 35 | task :verify_private_key_present do 36 | private_key = File.expand_path('~/.gem/rspec-gem-private_key.pem') 37 | unless File.exist?(private_key) 38 | raise "Your private key is not present. This gem should not be built without it." 39 | end 40 | end 41 | 42 | task :build => :verify_private_key_present 43 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security contact information 2 | 3 | To report a security vulnerability, please use the 4 | [Tidelift security contact](https://tidelift.com/security). 5 | Tidelift will coordinate the fix and disclosure. 6 | -------------------------------------------------------------------------------- /benchmarks/accessing_configuration_via_method_vs_cache.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark' 2 | 3 | n = 10_000 4 | 5 | require 'rspec/mocks' 6 | 7 | # precache config 8 | RSpec::Mocks.configuration 9 | 10 | Benchmark.benchmark do |bm| 11 | puts "#{n} times - ruby #{RUBY_VERSION}" 12 | 13 | puts 14 | puts "directly" 15 | 16 | 3.times do 17 | bm.report do 18 | n.times do 19 | original_state = RSpec::Mocks.configuration.temporarily_suppress_partial_double_verification 20 | RSpec::Mocks.configuration.temporarily_suppress_partial_double_verification = true 21 | RSpec::Mocks.configuration.temporarily_suppress_partial_double_verification = original_state 22 | end 23 | end 24 | end 25 | 26 | puts 27 | puts "with cached value" 28 | 29 | 3.times do 30 | bm.report do 31 | n.times do 32 | config = RSpec::Mocks.configuration 33 | original_state = config.temporarily_suppress_partial_double_verification 34 | config.temporarily_suppress_partial_double_verification = true 35 | config.temporarily_suppress_partial_double_verification = original_state 36 | end 37 | end 38 | end 39 | end 40 | 41 | __END__ 42 | 10000 times - ruby 2.3.1 43 | 44 | directly 45 | 0.000000 0.000000 0.000000 ( 0.002654) 46 | 0.000000 0.000000 0.000000 ( 0.002647) 47 | 0.010000 0.000000 0.010000 ( 0.002645) 48 | 49 | with cached value 50 | 0.000000 0.000000 0.000000 ( 0.001386) 51 | 0.000000 0.000000 0.000000 ( 0.001387) 52 | 0.000000 0.000000 0.000000 ( 0.001399) 53 | -------------------------------------------------------------------------------- /benchmarks/allocations/helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path("../../../lib", __FILE__) 2 | require "allocation_stats" 3 | require 'rspec/mocks/standalone' 4 | 5 | def benchmark_allocations(burn: 1) 6 | stats = AllocationStats.new(:burn => burn).trace do 7 | yield 8 | end 9 | 10 | columns = if ENV['DETAIL'] 11 | [:sourcefile, :sourceline, :class_plus] 12 | else 13 | [:class_plus] 14 | end 15 | 16 | puts stats.allocations(:alias_paths => true).group_by(*columns).from_pwd.sort_by_size.to_text 17 | end 18 | -------------------------------------------------------------------------------- /benchmarks/allocations/lambda_vs_block_with_select.rb: -------------------------------------------------------------------------------- 1 | require 'memory_profiler' 2 | 3 | n = 10_000 4 | 5 | def find_with_proc(argument) 6 | lambda do |lambda_arg| 7 | lambda_arg == argument 8 | end 9 | end 10 | 11 | def find(argument, lambda_arg) 12 | lambda_arg == argument 13 | end 14 | 15 | puts "#{n} items - ruby #{RUBY_VERSION}" 16 | 17 | puts 18 | puts "find_with_proc" 19 | 20 | MemoryProfiler.report do 21 | 100.times do 22 | 1.upto(n).select(&find_with_proc(50)) 23 | end 24 | end.pretty_print 25 | 26 | puts 27 | puts "find" 28 | 29 | MemoryProfiler.report do 30 | 100.times do 31 | 1.upto(n).select { |i| find(50, i) } 32 | end 33 | end.pretty_print 34 | 35 | # $ ruby benchmarks/allocations/2_lambda_ref_find.rb 36 | # 10000 items - ruby 3.2.2 37 | # 38 | # find_with_proc 39 | # Total allocated: 29600 bytes (400 objects) 40 | # Total retained: 0 bytes (0 objects) 41 | # 42 | # allocated memory by gem 43 | # ----------------------------------- 44 | # 29600 other 45 | # 46 | # allocated memory by file 47 | # ----------------------------------- 48 | # 29600 benchmarks/allocations/2_lambda_ref_find.rb 49 | # 50 | # allocated memory by location 51 | # ----------------------------------- 52 | # 21600 benchmarks/allocations/2_lambda_ref_find.rb:22 53 | # 8000 benchmarks/allocations/2_lambda_ref_find.rb:6 54 | # 55 | # allocated memory by class 56 | # ----------------------------------- 57 | # 13600 Enumerator 58 | # 8000 Array 59 | # 8000 Proc 60 | # 61 | # allocated objects by gem 62 | # ----------------------------------- 63 | # 400 other 64 | # 65 | # allocated objects by file 66 | # ----------------------------------- 67 | # 400 benchmarks/allocations/2_lambda_ref_find.rb 68 | # 69 | # allocated objects by location 70 | # ----------------------------------- 71 | # 300 benchmarks/allocations/2_lambda_ref_find.rb:22 72 | # 100 benchmarks/allocations/2_lambda_ref_find.rb:6 73 | # 74 | # allocated objects by class 75 | # ----------------------------------- 76 | # 200 Array 77 | # 100 Enumerator 78 | # 100 Proc 79 | # 80 | # 81 | # find 82 | # Total allocated: 21600 bytes (300 objects) 83 | # Total retained: 0 bytes (0 objects) 84 | # 85 | # allocated memory by gem 86 | # ----------------------------------- 87 | # 21600 other 88 | # 89 | # allocated memory by file 90 | # ----------------------------------- 91 | # 21600 benchmarks/allocations/2_lambda_ref_find.rb 92 | # 93 | # allocated memory by location 94 | # ----------------------------------- 95 | # 21600 benchmarks/allocations/2_lambda_ref_find.rb:31 96 | # 97 | # allocated memory by class 98 | # ----------------------------------- 99 | # 13600 Enumerator 100 | # 8000 Array 101 | # 102 | # allocated objects by gem 103 | # ----------------------------------- 104 | # 300 other 105 | # 106 | # allocated objects by file 107 | # ----------------------------------- 108 | # 300 benchmarks/allocations/2_lambda_ref_find.rb 109 | # 110 | # allocated objects by location 111 | # ----------------------------------- 112 | # 300 benchmarks/allocations/2_lambda_ref_find.rb:31 113 | # 114 | # allocated objects by class 115 | # ----------------------------------- 116 | # 200 Array 117 | # 100 Enumerator 118 | -------------------------------------------------------------------------------- /benchmarks/boot_time.sh: -------------------------------------------------------------------------------- 1 | time (for i in {1..100}; do ruby -Ilib:../rspec-support/lib -rrspec/mocks -e ""; done) 2 | 3 | # 3 runs before our autoload changes 4 | # real 0m4.497s 5 | # user 0m3.662s 6 | # sys 0m0.677s 7 | # 8 | # real 0m4.472s 9 | # user 0m3.644s 10 | # sys 0m0.671s 11 | # 12 | # real 0m4.465s 13 | # user 0m3.640s 14 | # sys 0m0.668s 15 | 16 | # 3 runs after our autoload changes: 17 | # 18 | # real 0m4.038s 19 | # user 0m3.274s 20 | # sys 0m0.609s 21 | # 22 | # real 0m4.038s 23 | # user 0m3.274s 24 | # sys 0m0.609s 25 | # 26 | # real 0m4.038s 27 | # user 0m3.274s 28 | # sys 0m0.609s 29 | 30 | # It's modest, but that's about a 10% improvement: an average 31 | # of about 40ms to load rather than 45 ms to load. 32 | -------------------------------------------------------------------------------- /benchmarks/double_creation.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__)) 2 | 3 | require 'benchmark' 4 | require 'rspec/mocks' 5 | 6 | n = 1000 7 | 8 | puts "#{n} times - ruby #{RUBY_VERSION}" 9 | puts 10 | 11 | Benchmark.bm do |bm| 12 | RSpec::Mocks.setup(self) 13 | 14 | (0..9).each do |m| 15 | attrs = m.times.inject({}) {|h, x| 16 | h["method_#{x}"] = x 17 | h 18 | } 19 | 20 | bm.report("#{m} attrs") do 21 | n.times do 22 | double(attrs) 23 | end 24 | end 25 | end 26 | end 27 | # $ export OLD_REV=d483e0a893d97c7b8e612e878a9f3562a210df9f 28 | # $ git checkout $OLD_REV 29 | # $ ruby benchmarks/double_creation.rb 30 | # 1000 times - ruby 2.0.0 31 | # 32 | # user system total real 33 | # 0 attrs 0.010000 0.000000 0.010000 ( 0.003686) 34 | # 1 attrs 0.110000 0.000000 0.110000 ( 0.143132) 35 | # 2 attrs 0.230000 0.010000 0.240000 ( 0.311358) 36 | # 3 attrs 0.400000 0.020000 0.420000 ( 0.465994) 37 | # 4 attrs 0.570000 0.010000 0.580000 ( 0.597902) 38 | # 5 attrs 0.920000 0.010000 0.930000 ( 1.060219) 39 | # 6 attrs 1.350000 0.020000 1.370000 ( 1.388386) 40 | # 7 attrs 1.770000 0.030000 1.800000 ( 1.805518) 41 | # 8 attrs 2.620000 0.030000 2.650000 ( 2.681484) 42 | # 9 attrs 3.320000 0.030000 3.350000 ( 3.380757) 43 | # 44 | # $ export NEW_REV=13e9d11542a6b60c5dc7ffa4527c98bb255d0a1f 45 | # $ git checkout $NEW_REV 46 | # $ ruby benchmarks/double_creation.rb 47 | # 1000 times - ruby 2.0.0 48 | # 49 | # user system total real 50 | # 0 attrs 0.010000 0.000000 0.010000 ( 0.001544) 51 | # 1 attrs 0.040000 0.000000 0.040000 ( 0.043522) 52 | # 2 attrs 0.060000 0.000000 0.060000 ( 0.081742) 53 | # 3 attrs 0.090000 0.010000 0.100000 ( 0.104526) 54 | # 4 attrs 0.120000 0.010000 0.130000 ( 0.132472) 55 | # 5 attrs 0.150000 0.010000 0.160000 ( 0.162368) 56 | # 6 attrs 0.190000 0.010000 0.200000 ( 0.204610) 57 | # 7 attrs 0.220000 0.010000 0.230000 ( 0.237983) 58 | # 8 attrs 0.260000 0.010000 0.270000 ( 0.281562) 59 | # 9 attrs 0.310000 0.020000 0.330000 ( 0.334489) 60 | # 61 | # $ git log $OLD_REV..$NEW_REV --oneline 62 | # 13e9d11 Remove unused arguments from simple stub interface. 63 | # 009a697 Extract CallerFilter class to unify caller manipulation. 64 | # 46c1eb0 Introduce "simple" stub as an optimization over using a normal stub. 65 | # 4a04b3e Extract constant ArgumentListMatcher::MATCH_ALL. 66 | # 860d591 Speed up double creation with multiple attributes by caching caller. 67 | -------------------------------------------------------------------------------- /benchmarks/each_value_v_values_each.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark' 2 | 3 | n = 10_000 4 | 5 | m = 1.upto(1000).inject({}) { |m, i| m[i] = i; m } 6 | 7 | Benchmark.benchmark do |bm| 8 | puts "#{n} times - ruby #{RUBY_VERSION}" 9 | 10 | puts 11 | puts "each_value" 12 | 13 | 3.times do 14 | bm.report do 15 | n.times do 16 | m.each_value {} 17 | end 18 | end 19 | end 20 | 21 | puts 22 | puts "values.each" 23 | 24 | 3.times do 25 | bm.report do 26 | n.times do 27 | m.values.each {} 28 | end 29 | end 30 | end 31 | end 32 | 33 | # $ ruby benchmarks/values_each_v_each_value.rb 34 | # 10000 times - ruby 1.9.3 35 | # 36 | # each_value 37 | # 0.720000 0.000000 0.720000 ( 0.720237) 38 | # 0.720000 0.000000 0.720000 ( 0.724956) 39 | # 0.730000 0.000000 0.730000 ( 0.730352) 40 | # 41 | # values.each 42 | # 0.910000 0.000000 0.910000 ( 0.917496) 43 | # 0.910000 0.010000 0.920000 ( 0.909319) 44 | # 0.910000 0.000000 0.910000 ( 0.911225) 45 | 46 | # $ ruby benchmarks/values_each_v_each_value.rb 47 | # 10000 times - ruby 2.0.0 48 | # 49 | # each_value 50 | # 0.730000 0.000000 0.730000 ( 0.738443) 51 | # 0.720000 0.000000 0.720000 ( 0.720183) 52 | # 0.720000 0.000000 0.720000 ( 0.720866) 53 | # 54 | # values.each 55 | # 0.940000 0.000000 0.940000 ( 0.942597) 56 | # 0.960000 0.010000 0.970000 ( 0.959248) 57 | # 0.960000 0.000000 0.960000 ( 0.959099) 58 | -------------------------------------------------------------------------------- /benchmarks/find_original_method_early.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift "./lib" 2 | require 'rspec/mocks' 3 | require "rspec/mocks/standalone" 4 | 5 | =begin 6 | This benchmark script is for troubleshooting the performance of 7 | #264. To use it, you have to edit the code in #264 a bit: 8 | wrap the call in `MethodDouble#initialize` to `find_original_method` 9 | in a conditional like `if $find_original`. 10 | 11 | That allows the code below to compare the perf of stubbing a method 12 | with the original method being found vs. not. 13 | =end 14 | 15 | require 'benchmark' 16 | 17 | n = 10_000 18 | 19 | Foo = Class.new(Object) do 20 | n.times do |i| 21 | define_method "meth_#{i}" do 22 | end 23 | end 24 | end 25 | 26 | Benchmark.bmbm do |bm| 27 | puts "#{n} times - ruby #{RUBY_VERSION}" 28 | 29 | perform_report = lambda do |label, find_original, &create_object| 30 | dbl = create_object.call 31 | $find_original = find_original 32 | 33 | bm.report(label) do 34 | n.times do |i| 35 | dbl.stub("meth_#{i}") 36 | end 37 | end 38 | 39 | RSpec::Mocks.space.reset_all 40 | end 41 | 42 | perform_report.call("Find original - partial mock", true) { Foo.new } 43 | perform_report.call("Don't find original - partial mock", false) { Foo.new } 44 | perform_report.call("Find original - test double", true) { double } 45 | perform_report.call("Don't find original - test double", false) { double } 46 | end 47 | 48 | =begin 49 | 50 | 10000 times - ruby 1.9.3 51 | Rehearsal ---------------------------------------------------------------------- 52 | Don't find original - partial mock 1.050000 0.020000 1.070000 ( 1.068561) 53 | Don't find original - test double 1.190000 0.010000 1.200000 ( 1.199815) 54 | Find original - partial mock 1.270000 0.010000 1.280000 ( 1.282944) 55 | Find original - test double 1.320000 0.020000 1.340000 ( 1.336136) 56 | ------------------------------------------------------------- total: 4.890000sec 57 | 58 | user system total real 59 | Don't find original - partial mock 0.990000 0.000000 0.990000 ( 1.000959) 60 | Don't find original - test double 0.930000 0.010000 0.940000 ( 0.931871) 61 | Find original - partial mock 1.040000 0.000000 1.040000 ( 1.046354) 62 | Find original - test double 0.980000 0.010000 0.990000 ( 0.983577) 63 | 64 | =end 65 | -------------------------------------------------------------------------------- /benchmarks/method_defined_at_any_visibility.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark' 2 | 3 | n = 1_000_000 4 | 5 | Foo = Class.new do 6 | 1.upto(n) do |i| 7 | define_method(:"public_method_#{i}") {} 8 | define_method(:"protected_method_#{i}") {} 9 | protected :"protected_method_#{i}" 10 | define_method(:"private_method_#{i}") {} 11 | private :"protected_method_#{i}" 12 | end 13 | end 14 | 15 | Benchmark.benchmark do |bm| 16 | puts "#{n} times - ruby #{RUBY_VERSION}" 17 | 18 | puts 19 | puts "using method_defined? and private_method_defined?" 20 | puts 21 | 22 | [:public, :protected, :private, :undefined].each do |vis| 23 | puts " - #{vis} methods" 24 | 25 | 3.times do 26 | GC.start 27 | 28 | bm.report do 29 | n.times do |i| 30 | name = :"#{vis}_method_#{i}" 31 | Foo.method_defined?(name) || Foo.private_method_defined?(name) 32 | end 33 | end 34 | end 35 | end 36 | 37 | puts 38 | puts "using public_method_defined?, protected_method_defined? and private_method_defined?" 39 | puts 40 | 41 | [:public, :protected, :private, :undefined].each do |vis| 42 | puts " - #{vis} methods" 43 | 44 | 3.times do 45 | GC.start 46 | 47 | bm.report do 48 | n.times do |i| 49 | name = :"#{vis}_method_#{i}" 50 | Foo.public_method_defined?(name) || 51 | Foo.protected_method_defined?(name) 52 | Foo.private_method_defined?(name) 53 | end 54 | end 55 | end 56 | end 57 | end 58 | 59 | =begin 60 | 61 | 1000000 times - ruby 2.0.0 62 | 63 | using method_defined? and private_method_defined? 64 | 65 | - public methods 66 | 1.410000 0.040000 1.450000 ( 1.462588) 67 | 1.380000 0.000000 1.380000 ( 1.372015) 68 | 1.370000 0.000000 1.370000 ( 1.372362) 69 | - protected methods 70 | 1.410000 0.000000 1.410000 ( 1.402750) 71 | 1.440000 0.000000 1.440000 ( 1.442719) 72 | 1.460000 0.010000 1.470000 ( 1.464763) 73 | - private methods 74 | 1.390000 0.000000 1.390000 ( 1.393956) 75 | 1.340000 0.000000 1.340000 ( 1.349340) 76 | 1.360000 0.000000 1.360000 ( 1.361910) 77 | - undefined methods 78 | 3.260000 0.050000 3.310000 ( 3.316372) 79 | 1.260000 0.010000 1.270000 ( 1.266557) 80 | 1.250000 0.000000 1.250000 ( 1.248734) 81 | 82 | using public_method_defined?, protected_method_defined? and private_method_defined? 83 | 84 | - public methods 85 | 1.550000 0.000000 1.550000 ( 1.550655) 86 | 1.540000 0.010000 1.550000 ( 1.543906) 87 | 1.540000 0.000000 1.540000 ( 1.538267) 88 | - protected methods 89 | 1.590000 0.000000 1.590000 ( 1.598310) 90 | 1.600000 0.000000 1.600000 ( 1.595205) 91 | 1.600000 0.000000 1.600000 ( 1.604186) 92 | - private methods 93 | 1.530000 0.000000 1.530000 ( 1.530080) 94 | 1.560000 0.000000 1.560000 ( 1.562656) 95 | 1.560000 0.000000 1.560000 ( 1.569161) 96 | - undefined methods 97 | 1.300000 0.000000 1.300000 ( 1.298066) 98 | 1.310000 0.000000 1.310000 ( 1.310737) 99 | 1.290000 0.000000 1.290000 ( 1.288307) 100 | 101 | =end 102 | -------------------------------------------------------------------------------- /benchmarks/thread_safety.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__)) 2 | 3 | require 'benchmark' 4 | require 'rspec/mocks' 5 | 6 | Benchmark.bm do |bm| 7 | bm.report("fetching a proxy") do 8 | RSpec::Mocks.with_temporary_scope do 9 | o = Object.new 10 | 100_000.times { 11 | RSpec::Mocks.space.proxy_for(o) 12 | } 13 | end 14 | end 15 | end 16 | 17 | # Without synchronize (not thread-safe): 18 | # 19 | # user system total real 20 | # fetching a proxy 0.120000 0.000000 0.120000 ( 0.141333) 21 | # 22 | # With synchronize (thread-safe): 23 | # user system total real 24 | # fetching a proxy 0.180000 0.000000 0.180000 ( 0.189553) 25 | -------------------------------------------------------------------------------- /benchmarks/transfer_nested_constants.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__)) 2 | 3 | require 'benchmark' 4 | require 'rspec/mocks' 5 | 6 | N = ENV.fetch('N', 10_000).to_i 7 | M = ENV.fetch('M', 5).to_i 8 | 9 | puts "#{N} times, #{M} constants - ruby #{RUBY_VERSION}" 10 | puts 11 | 12 | class A 13 | M.times do |x| 14 | const_set("C#{x}", Object.new) 15 | end 16 | end 17 | 18 | Benchmark.bm(20) do |bm| 19 | RSpec::Mocks.setup(self) 20 | 21 | bm.report("with constants") do 22 | N.times do 23 | class_double('A').as_stubbed_const(:transfer_nested_constants => true) 24 | end 25 | end 26 | 27 | bm.report("without constants") do 28 | N.times do 29 | class_double('A').as_stubbed_const(:transfer_nested_constants => false) 30 | end 31 | end 32 | end 33 | 34 | # > for n in 1 10000; do for m in 0 5 100; do echo; \ 35 | # env N=$n M=$m ruby benchmarks/transfer_nested_constants.rb; \ 36 | # echo; done; done 37 | # 38 | # 1 times, 0 constants - ruby 2.0.0 39 | # 40 | # user system total real 41 | # with constants 0.000000 0.000000 0.000000 ( 0.000180) 42 | # without constants 0.000000 0.000000 0.000000 ( 0.000071) 43 | # 44 | # 45 | # 1 times, 5 constants - ruby 2.0.0 46 | # 47 | # user system total real 48 | # with constants 0.000000 0.000000 0.000000 ( 0.000197) 49 | # without constants 0.000000 0.000000 0.000000 ( 0.000123) 50 | # 51 | # 52 | # 1 times, 100 constants - ruby 2.0.0 53 | # 54 | # user system total real 55 | # with constants 0.000000 0.000000 0.000000 ( 0.000433) 56 | # without constants 0.000000 0.000000 0.000000 ( 0.000115) 57 | # 58 | # 59 | # 10000 times, 0 constants - ruby 2.0.0 60 | # 61 | # user system total real 62 | # with constants 0.900000 0.020000 0.920000 ( 0.935583) 63 | # without constants 0.660000 0.010000 0.670000 ( 0.680178) 64 | # 65 | # 66 | # 10000 times, 5 constants - ruby 2.0.0 67 | # 68 | # user system total real 69 | # with constants 1.080000 0.020000 1.100000 ( 1.114722) 70 | # without constants 0.720000 0.020000 0.740000 ( 0.741976) 71 | # 72 | # 73 | # 10000 times, 100 constants - ruby 2.0.0 74 | # 75 | # user system total real 76 | # with constants 3.870000 0.110000 3.980000 ( 4.000176) 77 | # without constants 0.930000 0.010000 0.940000 ( 0.947197) 78 | -------------------------------------------------------------------------------- /cucumber.yml: -------------------------------------------------------------------------------- 1 | <% 2 | 3 | USE_TILDE_TAGS = !defined?(::RUBY_ENGINE_VERSION) || (::RUBY_ENGINE_VERSION < '2.0.0') 4 | NOT_WIP_TAG = USE_TILDE_TAGS ? '~@wip' : '"not @wip"' 5 | 6 | %> 7 | 8 | default: --require features --tags <%= NOT_WIP_TAG %> features --format progress 9 | wip: --require features --tags @wip:3 --wip features 10 | -------------------------------------------------------------------------------- /features/.nav: -------------------------------------------------------------------------------- 1 | - basics: 2 | - test_doubles.feature 3 | - allowing_messages.feature 4 | - expecting_messages.feature 5 | - partial_test_doubles.feature 6 | - null_object_doubles.feature 7 | - spies.feature 8 | - scope.feature 9 | - verifying_doubles: 10 | - instance_doubles.feature 11 | - class_doubles.feature 12 | - object_doubles.feature 13 | - dynamic_classes.feature 14 | - partial_doubles.feature 15 | - configuring_responses: 16 | - returning_a_value.feature 17 | - raising_an_error.feature 18 | - throwing.feature 19 | - yielding.feature 20 | - calling_the_original_implementation.feature 21 | - wrapping_the_original_implementation.feature 22 | - block_implementation.feature 23 | - setting_constraints: 24 | - matching_arguments.feature 25 | - receive_counts.feature 26 | - message_order.feature 27 | - mutating_constants: 28 | - stub_defined_constant.feature 29 | - stub_undefined_constant.feature 30 | - hide_defined_constant.feature 31 | - hide_undefined_constant.feature 32 | - working_with_legacy_code: 33 | - any_instance.feature 34 | - message_chains.feature 35 | - old_syntax: 36 | - stub.feature 37 | - should_receive.feature 38 | - any_instance.feature 39 | - stub_chain.feature 40 | - unstub.feature 41 | - outside_rspec: 42 | - minitest.feature 43 | - any_test_framework.feature 44 | - standalone.feature 45 | - Changelog 46 | -------------------------------------------------------------------------------- /features/README.md: -------------------------------------------------------------------------------- 1 | # RSpec Mocks 2 | 3 | rspec-mocks helps to control the context in a code example by letting you set known return 4 | values, fake implementations of methods, and even set expectations that specific messages 5 | are received by an object. 6 | 7 | You can do these three things on test doubles that rspec-mocks creates for you on the fly, or 8 | you can do them on objects that are part of your system. 9 | 10 | ## Messages and Methods 11 | 12 | _Message_ and _method_ are metaphors that we use somewhat interchangeably, but they are 13 | subtly different. In Object Oriented Programming, objects communicate by sending 14 | _messages_ to one another. When an object receives a message, it invokes a _method_ with the 15 | same name as the message. 16 | 17 | ## Test Doubles 18 | 19 | A test double is an object that stands in for another object in your system during a code 20 | example. Use the `double` method, passing in an optional identifier, to create one: 21 | 22 | ```ruby 23 | book = double("book") 24 | ``` 25 | 26 | Most of the time you will want some confidence that your doubles resemble an existing 27 | object in your system. Verifying doubles are provided for this purpose. If the existing object 28 | is available, they will prevent you from adding stubs and expectations for methods that do 29 | not exist or that have invalid arguments. 30 | 31 | ```ruby 32 | book = instance_double("Book", :pages => 250) 33 | ``` 34 | 35 | [Verifying doubles](./rspec-mocks/verifying-doubles) have some clever tricks to enable you to both test in isolation without your 36 | dependencies loaded while still being able to validate them against real objects. 37 | 38 | ## Method Stubs 39 | 40 | A method stub is an instruction to an object (real or test double) to return a 41 | known value in response to a message: 42 | 43 | ```ruby 44 | allow(die).to receive(:roll) { 3 } 45 | ``` 46 | 47 | This tells the `die` object to return the value `3` when it receives the `roll` message. 48 | 49 | ## Message Expectations 50 | 51 | A message expectation is an expectation that an object should receive a specific message 52 | during the course of a code example: 53 | 54 | ```ruby 55 | describe Account do 56 | context "when closed" do 57 | it "logs an 'account closed' message" do 58 | logger = double() 59 | account = Account.new 60 | account.logger = logger 61 | 62 | expect(logger).to receive(:account_closed).with(account) 63 | 64 | account.close 65 | end 66 | end 67 | end 68 | ``` 69 | 70 | This example specifies that the `account` object sends the `logger` the `account_closed` 71 | message (with itself as an argument) when it receives the `close` message. 72 | 73 | ## Issues 74 | 75 | The documentation for rspec-mocks is a work in progress. We'll be adding Cucumber 76 | features over time, and clarifying existing ones. If you have specific features you'd like to see 77 | added, find the existing documentation incomplete or confusing, or, better yet, wish to write 78 | a missing Cucumber feature yourself, please [submit an issue](http://github.com/rspec/rspec-mocks/issues) or a [pull request](http://github.com/rspec/rspec-mocks). 79 | -------------------------------------------------------------------------------- /features/basics/allowing_messages.feature: -------------------------------------------------------------------------------- 1 | Feature: Allowing messages 2 | 3 | [Test doubles](./test-doubles) are "strict" by default -- messages that have not been specifically 4 | allowed or expected will trigger an error. Use `allow(...).to receive(...)` to configure 5 | which messages the double is allowed to receive. You can also use `allow(...).to 6 | receive_messages(...)` to configure allowed messages (and return values) in bulk. 7 | 8 | Scenario: Allowed messages return nil by default 9 | Given a file named "allow_message_spec.rb" with: 10 | """ruby 11 | RSpec.describe "allow" do 12 | it "returns nil from allowed messages" do 13 | dbl = double("Some Collaborator") 14 | allow(dbl).to receive(:foo) 15 | expect(dbl.foo).to be_nil 16 | end 17 | end 18 | """ 19 | When I run `rspec allow_message_spec.rb` 20 | Then the examples should all pass 21 | 22 | Scenario: Messages can be allowed in bulk using `receive_messages` 23 | Given a file named "receive_messages_spec.rb" with: 24 | """ruby 25 | RSpec.describe "receive_messages" do 26 | it "configures return values for the provided messages" do 27 | dbl = double("Some Collaborator") 28 | allow(dbl).to receive_messages(:foo => 2, :bar => 3) 29 | expect(dbl.foo).to eq(2) 30 | expect(dbl.bar).to eq(3) 31 | end 32 | end 33 | """ 34 | When I run `rspec receive_messages_spec.rb` 35 | Then the examples should all pass 36 | -------------------------------------------------------------------------------- /features/basics/null_object_doubles.feature: -------------------------------------------------------------------------------- 1 | Feature: Null object doubles 2 | 3 | [Test doubles](./test-doubles) are strict by default, raising errors when they receive messages that have not 4 | been allowed or expected. You can chain `as_null_object` off of `double` in order to make 5 | the double "loose". For any message that has not explicitly allowed or expected, the double 6 | will return itself. It acts as a black hole null object, allowing arbitrarily deep method chains. 7 | 8 | Scenario: `as_null_object` allows arbitrarily deep message chains and returns itself 9 | Given a file named "as_null_object_spec.rb" with: 10 | """ruby 11 | RSpec.describe "as_null_object" do 12 | it "returns itself" do 13 | dbl = double("Some Collaborator").as_null_object 14 | expect(dbl.foo.bar.bazz).to be(dbl) 15 | end 16 | end 17 | """ 18 | When I run `rspec as_null_object_spec.rb` 19 | Then the examples should all pass 20 | 21 | Scenario: Individual methods can still be allowed or expected 22 | Given a file named "as_null_object_spec.rb" with: 23 | """ruby 24 | RSpec.describe "as_null_object" do 25 | it "can allow individual methods" do 26 | dbl = double("Some Collaborator", :foo => 3).as_null_object 27 | allow(dbl).to receive(:bar).and_return(4) 28 | 29 | expect(dbl.foo).to eq(3) 30 | expect(dbl.bar).to eq(4) 31 | end 32 | end 33 | """ 34 | When I run `rspec as_null_object_spec.rb` 35 | Then the examples should all pass 36 | -------------------------------------------------------------------------------- /features/basics/partial_test_doubles.feature: -------------------------------------------------------------------------------- 1 | Feature: Partial test doubles 2 | 3 | A _partial test double_ is an extension of a real object in a system that is instrumented with 4 | test-double like behaviour in the context of a test. This technique is very common in Ruby 5 | because we often see class objects acting as global namespaces for methods. For example, 6 | in Rails: 7 | 8 | ```ruby 9 | person = double("person") 10 | allow(Person).to receive(:find) { person } 11 | ``` 12 | 13 | In this case we're instrumenting Person to return the person object we've defined whenever 14 | it receives the `find` message. We can also set a message expectation so that the example 15 | fails if `find` is not called: 16 | 17 | ```ruby 18 | person = double("person") 19 | expect(Person).to receive(:find) { person } 20 | ``` 21 | 22 | RSpec replaces the method we're stubbing or mocking with its own test-double like method. 23 | At the end of the example, RSpec verifies any message expectations, and then restores the 24 | original methods. 25 | 26 | Note: we recommend enabling the [`verify_partial_doubles`](../verifying-doubles/partial-doubles) config option. 27 | 28 | Scenario: Only the specified methods are redefined 29 | Given a file named "partial_double_spec.rb" with: 30 | """ruby 31 | RSpec.describe "A partial double" do 32 | # Note: stubbing a string like this is a terrible idea. 33 | # This is just for demonstration purposes. 34 | let(:string) { "a string" } 35 | before { allow(string).to receive(:length).and_return(500) } 36 | 37 | it "redefines the specified methods" do 38 | expect(string.length).to eq(500) 39 | end 40 | 41 | it "does not effect other methods" do 42 | expect(string.reverse).to eq("gnirts a") 43 | end 44 | end 45 | """ 46 | When I run `rspec partial_double_spec.rb` 47 | Then the examples should all pass 48 | 49 | Scenario: The original method is restored when the example completes 50 | Given a file named "partial_double_spec.rb" with: 51 | """ruby 52 | class User 53 | def self.find(id) 54 | :original_return_value 55 | end 56 | end 57 | 58 | RSpec.describe "A partial double" do 59 | it "redefines a method" do 60 | allow(User).to receive(:find).and_return(:redefined) 61 | expect(User.find(3)).to eq(:redefined) 62 | end 63 | 64 | it "restores the redefined method after the example completes" do 65 | expect(User.find(3)).to eq(:original_return_value) 66 | end 67 | end 68 | """ 69 | When I run `rspec partial_double_spec.rb --order defined` 70 | Then the examples should all pass 71 | -------------------------------------------------------------------------------- /features/basics/test_doubles.feature: -------------------------------------------------------------------------------- 1 | Feature: Test Doubles 2 | 3 | _Test double_ is a generic term for any object that stands in for a real object during a test 4 | (think "stunt double"). You create one using the `double` method. Doubles are "strict" by 5 | default -- any message you have not allowed or expected will trigger an error -- but you can 6 | [switch a double to being "loose"](./null-object-doubles). When creating a double, you can allow messages (and set 7 | their return values) by passing a hash. 8 | 9 | Once you have a test double, you can [allow](./allowing-messages) or [expect](./expecting-messages) messages on it. 10 | 11 | We recommend you use [verifying doubles](../verifying-doubles) whenever possible. 12 | 13 | Scenario: Doubles are strict by default 14 | Given a file named "double_spec.rb" with: 15 | """ruby 16 | RSpec.describe "A test double" do 17 | it "raises errors when messages not allowed or expected are received" do 18 | dbl = double("Some Collaborator") 19 | dbl.foo 20 | end 21 | end 22 | """ 23 | When I run `rspec double_spec.rb` 24 | Then it should fail with: 25 | """ 26 | # received unexpected message :foo with (no args) 27 | """ 28 | 29 | Scenario: A hash can be used to define allowed messages and return values 30 | Given a file named "double_spec.rb" with: 31 | """ruby 32 | RSpec.describe "A test double" do 33 | it "returns canned responses from the methods named in the provided hash" do 34 | dbl = double("Some Collaborator", :foo => 3, :bar => 4) 35 | expect(dbl.foo).to eq(3) 36 | expect(dbl.bar).to eq(4) 37 | end 38 | end 39 | """ 40 | When I run `rspec double_spec.rb` 41 | Then the examples should all pass 42 | -------------------------------------------------------------------------------- /features/configuring_responses/README.md: -------------------------------------------------------------------------------- 1 | # Configuring responses 2 | 3 | When [allowing](./basics/allowing-messages) or [expecting](./basics/expecting-messages) messages, the default response is to return `nil`. Several 4 | methods are provided to configure how the test double responds to the message. 5 | 6 | * `and_return` 7 | * `and_raise` 8 | * `and_invoke` 9 | * `and_throw` 10 | * `and_yield` 11 | * `and_call_original` 12 | * `and_wrap_original` 13 | 14 | In addition, you can provide a [block implementation](./configuring-responses/block-implementation) to respond in any manner you wish. 15 | 16 | Note: for simplicity, the examples here use `allow` rather than `expect`, but these APIs apply equally to both cases. 17 | -------------------------------------------------------------------------------- /features/configuring_responses/calling_the_original_implementation.feature: -------------------------------------------------------------------------------- 1 | Feature: Calling the original implementation 2 | 3 | Use `and_call_original` to make a partial double response as it normally would. This can 4 | be useful when you want to expect a message without interfering with how it responds. You 5 | can also use it to configure the default response for most arguments, and then override 6 | that for specific arguments using `with`. 7 | 8 | Note: `and_call_original` is only supported on partial doubles, as normal test doubles do 9 | not have an original implementation. 10 | 11 | Background: 12 | Given a file named "lib/calculator.rb" with: 13 | """ruby 14 | class Calculator 15 | def self.add(x, y) 16 | x + y 17 | end 18 | end 19 | """ 20 | 21 | Scenario: `and_call_original` makes the partial double respond as it normally would 22 | Given a file named "spec/and_call_original_spec.rb" with: 23 | """ruby 24 | require 'calculator' 25 | 26 | RSpec.describe "and_call_original" do 27 | it "responds as it normally would" do 28 | expect(Calculator).to receive(:add).and_call_original 29 | expect(Calculator.add(2, 3)).to eq(5) 30 | end 31 | end 32 | """ 33 | When I run `rspec spec/and_call_original_spec.rb` 34 | Then the examples should all pass 35 | 36 | Scenario: `and_call_original` can configure a default response that can be overridden for specific args 37 | Given a file named "spec/and_call_original_spec.rb" with: 38 | """ruby 39 | require 'calculator' 40 | 41 | RSpec.describe "and_call_original" do 42 | it "can be overridden for specific arguments using #with" do 43 | allow(Calculator).to receive(:add).and_call_original 44 | allow(Calculator).to receive(:add).with(2, 3).and_return(-5) 45 | 46 | expect(Calculator.add(2, 2)).to eq(4) 47 | expect(Calculator.add(2, 3)).to eq(-5) 48 | end 49 | end 50 | """ 51 | When I run `rspec spec/and_call_original_spec.rb` 52 | Then the examples should all pass 53 | -------------------------------------------------------------------------------- /features/configuring_responses/mixed_responses.feature: -------------------------------------------------------------------------------- 1 | Feature: Mixed responses 2 | 3 | Use `and_invoke` to invoke a callable when a message is received. Pass `and_invoke` multiple 4 | callables to have different behavior for consecutive calls. The final callable will continue to be 5 | called if the message is received additional times. 6 | 7 | Note: The invoked callable will be given the same arguments as the original call, which includes any blocks 8 | (meaning for example `yield` will be supported). It is recommended to use a `lambda` or similar with the same 9 | arity as your method but you can use a `proc` if you do not care about arity (e.g. when raising). 10 | 11 | Scenario: Mixed responses 12 | Given a file named "raises_and_then_returns.rb" with: 13 | """ruby 14 | RSpec.describe "when the method is called multiple times" do 15 | it "raises and then later returns a value" do 16 | dbl = double 17 | allow(dbl).to receive(:foo).and_invoke(lambda { raise "failure" }, lambda { true }) 18 | 19 | expect { dbl.foo }.to raise_error("failure") 20 | expect(dbl.foo).to eq(true) 21 | end 22 | end 23 | """ 24 | When I run `rspec raises_and_then_returns.rb` 25 | Then the examples should all pass 26 | 27 | Scenario: Block arguments 28 | Given a file named "yields_and_raises.rb" with: 29 | """ruby 30 | RSpec.describe "when the method is called multiple times" do 31 | it "yields and then later raises" do 32 | dbl = double 33 | allow(dbl).to receive(:foo).and_invoke( 34 | proc { |&block| block.call("foo") }, 35 | proc { raise "failure" } 36 | ) 37 | 38 | dbl.foo { |yielded| expect(yielded).to eq("foo") } 39 | expect { dbl.foo }.to raise_error("failure") 40 | end 41 | end 42 | """ 43 | When I run `rspec yields_and_raises.rb` 44 | Then the examples should all pass 45 | -------------------------------------------------------------------------------- /features/configuring_responses/raising_an_error.feature: -------------------------------------------------------------------------------- 1 | Feature: Raising an error 2 | 3 | Use `and_raise` to make the test double raise an error when it receives the message. Any of the following forms are supported: 4 | 5 | * `and_raise(ExceptionClass)` 6 | * `and_raise("message")` 7 | * `and_raise(ExceptionClass, "message")` 8 | * `and_raise(instance_of_an_exception_class)` 9 | 10 | Scenario: Raising an error 11 | Given a file named "raises_an_error_spec.rb" with: 12 | """ruby 13 | RSpec.describe "Making it raise an error" do 14 | it "raises the provided exception" do 15 | dbl = double 16 | allow(dbl).to receive(:foo).and_raise("boom") 17 | dbl.foo 18 | end 19 | end 20 | """ 21 | When I run `rspec raises_an_error_spec.rb` 22 | Then it should fail with: 23 | """ 24 | 1) Making it raise an error raises the provided exception 25 | Failure/Error: dbl.foo 26 | 27 | RuntimeError: 28 | boom 29 | """ 30 | -------------------------------------------------------------------------------- /features/configuring_responses/returning_a_value.feature: -------------------------------------------------------------------------------- 1 | Feature: Returning a value 2 | 3 | Use `and_return` to specify a return value. Pass `and_return` multiple values to specify 4 | different return values for consecutive calls. The final value will continue to be returned if 5 | the message is received additional times. 6 | 7 | Note - If you are looking for documentation for configuring responses from `allow_any_instance_of`, please see the [working with legacy code](../working-with-legacy-code/any-instance) documentation. 8 | 9 | Scenario: Nil is returned by default 10 | Given a file named "returns_nil_spec.rb" with: 11 | """ruby 12 | RSpec.describe "The default response" do 13 | it "returns nil when no response has been configured" do 14 | dbl = double 15 | allow(dbl).to receive(:foo) 16 | expect(dbl.foo).to be_nil 17 | end 18 | end 19 | """ 20 | When I run `rspec returns_nil_spec.rb` 21 | Then the examples should all pass 22 | 23 | Scenario: Specify a return value 24 | Given a file named "and_return_spec.rb" with: 25 | """ruby 26 | RSpec.describe "Specifying a return value" do 27 | it "returns the specified return value" do 28 | dbl = double 29 | allow(dbl).to receive(:foo).and_return(14) 30 | expect(dbl.foo).to eq(14) 31 | end 32 | end 33 | """ 34 | When I run `rspec and_return_spec.rb` 35 | Then the examples should all pass 36 | 37 | Scenario: Specify different return values for multiple calls 38 | Given a file named "multiple_calls_spec.rb" with: 39 | """ruby 40 | RSpec.describe "When the method is called multiple times" do 41 | it "returns the specified values in order, then keeps returning the last value" do 42 | dbl = double 43 | allow(dbl).to receive(:foo).and_return(1, 2, 3) 44 | 45 | expect(dbl.foo).to eq(1) 46 | expect(dbl.foo).to eq(2) 47 | expect(dbl.foo).to eq(3) 48 | expect(dbl.foo).to eq(3) # begins to repeat last value 49 | expect(dbl.foo).to eq(3) 50 | end 51 | end 52 | """ 53 | When I run `rspec multiple_calls_spec.rb` 54 | Then the examples should all pass 55 | -------------------------------------------------------------------------------- /features/configuring_responses/throwing.feature: -------------------------------------------------------------------------------- 1 | Feature: Throwing 2 | 3 | Use `and_throw` to make the test double throw the provided symbol, optionally with the provided argument. 4 | 5 | * `and_throw(:symbol)` 6 | * `and_throw(:symbol, argument)` 7 | 8 | Scenario: Throw a symbol 9 | Given a file named "and_throw_spec.rb" with: 10 | """ruby 11 | RSpec.describe "Making it throw a symbol" do 12 | it "throws the provided symbol" do 13 | dbl = double 14 | allow(dbl).to receive(:foo).and_throw(:hello) 15 | 16 | catch :hello do 17 | dbl.foo 18 | fail "should not get here" 19 | end 20 | end 21 | 22 | it "includes the provided argument when throwing" do 23 | dbl = double 24 | allow(dbl).to receive(:foo).and_throw(:hello, "world") 25 | 26 | arg = catch :hello do 27 | dbl.foo 28 | fail "should not get here" 29 | end 30 | 31 | expect(arg).to eq("world") 32 | end 33 | end 34 | """ 35 | When I run `rspec and_throw_spec.rb` 36 | Then the examples should all pass 37 | -------------------------------------------------------------------------------- /features/configuring_responses/wrapping_the_original_implementation.feature: -------------------------------------------------------------------------------- 1 | Feature: Wrapping the original implementation 2 | 3 | Use `and_wrap_original` to modify a partial double's original response. This can be useful 4 | when you want to utilise an external object but mutate its response. For example if an 5 | API returns a large amount of data and for test purposes you'd like to trim it down. You can 6 | also use it to configure the default response for most arguments, and then override that for 7 | specific arguments using `with`. 8 | 9 | Note: `and_wrap_original` is only supported on partial doubles, as normal test doubles do 10 | not have an original implementation. 11 | 12 | Background: 13 | Given a file named "lib/api.rb" with: 14 | """ruby 15 | class API 16 | def self.solve_for(x) 17 | (1..x).to_a 18 | end 19 | end 20 | """ 21 | 22 | Scenario: `and_wrap_original` wraps the original partial double response 23 | Given a file named "spec/and_wrap_original_spec.rb" with: 24 | """ruby 25 | require 'api' 26 | 27 | RSpec.describe "and_wrap_original" do 28 | it "responds as it normally would, modified by the block" do 29 | expect(API).to receive(:solve_for).and_wrap_original { |m, *args| m.call(*args).first(5) } 30 | expect(API.solve_for(100)).to eq [1,2,3,4,5] 31 | end 32 | end 33 | """ 34 | When I run `rspec spec/and_wrap_original_spec.rb` 35 | Then the examples should all pass 36 | 37 | Scenario: `and_wrap_original` can configure a default response that can be overridden for specific args 38 | Given a file named "spec/and_wrap_original_spec.rb" with: 39 | """ruby 40 | require 'api' 41 | 42 | RSpec.describe "and_wrap_original" do 43 | it "can be overridden for specific arguments using #with" do 44 | allow(API).to receive(:solve_for).and_wrap_original { |m, *args| m.call(*args).first(5) } 45 | allow(API).to receive(:solve_for).with(2).and_return([3]) 46 | 47 | expect(API.solve_for(20)).to eq [1,2,3,4,5] 48 | expect(API.solve_for(2)).to eq [3] 49 | end 50 | end 51 | """ 52 | When I run `rspec spec/and_wrap_original_spec.rb` 53 | Then the examples should all pass 54 | 55 | @kw-arguments 56 | Scenario: `and_wrap_original` can configure a default response that can be overridden for specific keyword arguments 57 | Given a file named "lib/kw_api.rb" with: 58 | """ruby 59 | class API 60 | def self.solve_for(x: 1, y: 2) 61 | (x..y).to_a 62 | end 63 | end 64 | """ 65 | Given a file named "spec/and_wrap_original_spec.rb" with: 66 | """ruby 67 | require 'kw_api' 68 | 69 | RSpec.describe "and_wrap_original" do 70 | it "can be overridden for specific arguments using #with" do 71 | allow(API).to receive(:solve_for).and_wrap_original { |m, **kwargs| m.call(**kwargs).first(5) } 72 | allow(API).to receive(:solve_for).with(x: 3, y: 4).and_return([3]) 73 | 74 | expect(API.solve_for(x: 1, y: 20)).to eq [1,2,3,4,5] 75 | expect(API.solve_for(y: 20)).to eq [1,2,3,4,5] 76 | expect(API.solve_for(x: 3, y: 4)).to eq [3] 77 | end 78 | end 79 | """ 80 | When I run `rspec spec/and_wrap_original_spec.rb` 81 | Then the examples should all pass 82 | -------------------------------------------------------------------------------- /features/configuring_responses/yielding.feature: -------------------------------------------------------------------------------- 1 | Feature: Yielding 2 | 3 | Use `and_yield` to make the test double yield the provided arguments when it receives the 4 | message. If the caller does not provide a block, or the caller's block does not accept the 5 | provided arguments, an error will be raised. If you want to yield multiple times, chain 6 | multiple `and_yield` calls together. 7 | 8 | Scenario: Yield an argument 9 | Given a file named "yield_arguments_spec.rb" with: 10 | """ruby 11 | RSpec.describe "Making it yield arguments" do 12 | it "yields the provided args" do 13 | dbl = double 14 | allow(dbl).to receive(:foo).and_yield(2, 3) 15 | 16 | x = y = nil 17 | dbl.foo { |a, b| x, y = a, b } 18 | expect(x).to eq(2) 19 | expect(y).to eq(3) 20 | end 21 | end 22 | """ 23 | When I run `rspec yield_arguments_spec.rb` 24 | Then the examples should all pass 25 | 26 | Scenario: It fails when the caller does not provide a block 27 | Given a file named "no_caller_block_spec.rb" with: 28 | """ruby 29 | RSpec.describe "Making it yield" do 30 | it "fails when the caller does not provide a block" do 31 | dbl = double 32 | allow(dbl).to receive(:foo).and_yield(2, 3) 33 | dbl.foo 34 | end 35 | end 36 | """ 37 | When I run `rspec no_caller_block_spec.rb` 38 | Then it should fail with: 39 | """ 40 | # asked to yield |[2, 3]| but no block was passed 41 | """ 42 | 43 | Scenario: It fails when the caller's block does not accept the provided arguments 44 | Given a file named "arg_mismatch_spec.rb" with: 45 | """ruby 46 | RSpec.describe "Making it yield" do 47 | it "fails when the caller's block does not accept the provided arguments" do 48 | dbl = double 49 | allow(dbl).to receive(:foo).and_yield(2, 3) 50 | dbl.foo { |x| } 51 | end 52 | end 53 | """ 54 | When I run `rspec arg_mismatch_spec.rb` 55 | Then it should fail with: 56 | """ 57 | # yielded |2, 3| to block with arity of 1 58 | """ 59 | 60 | Scenario: Yield multiple times 61 | Given a file named "yield_multiple_times_spec.rb" with: 62 | """ 63 | RSpec.describe "Making it yield multiple times" do 64 | it "yields the specified args in succession" do 65 | yielded = [] 66 | 67 | dbl = double 68 | allow(dbl).to receive(:foo).and_yield(1).and_yield(2).and_yield(3) 69 | dbl.foo { |x| yielded << x } 70 | 71 | expect(yielded).to eq([1, 2, 3]) 72 | end 73 | end 74 | """ 75 | When I run `rspec yield_multiple_times_spec.rb` 76 | Then the examples should all pass 77 | -------------------------------------------------------------------------------- /features/mutating_constants/README.md: -------------------------------------------------------------------------------- 1 | # Mutating Constants 2 | 3 | ### Stubbing 4 | 5 | Support is provided for stubbing constants. Like with method stubs, the stubbed constants 6 | will be restored to their original state when an example completes. 7 | 8 | ``` ruby 9 | stub_const("Foo", fake_foo) 10 | Foo # => fake_foo 11 | ``` 12 | 13 | Stubbed constant names must be fully qualified; the current module nesting is not 14 | considered. 15 | 16 | ``` ruby 17 | module MyGem 18 | class SomeClass; end 19 | end 20 | 21 | module MyGem 22 | describe "Something" do 23 | let(:fake_class) { Class.new } 24 | 25 | it "accidentally stubs the wrong constant" do 26 | # this stubs ::SomeClass (in the top-level namespace), 27 | # not MyGem::SomeClass like you probably mean. 28 | stub_const("SomeClass", fake_class) 29 | end 30 | 31 | it "stubs the right constant" do 32 | stub_const("MyGem::SomeClass", fake_class) 33 | end 34 | end 35 | end 36 | ``` 37 | 38 | When you stub a constant that is a module or a class, nested constants on the original 39 | module or class are not transferred by default, but you can use the 40 | `:transfer_nested_constants` option to tell rspec-mocks to transfer them: 41 | 42 | ``` ruby 43 | class CardDeck 44 | SUITS = [:Spades, :Diamonds, :Clubs, :Hearts] 45 | NUM_CARDS = 52 46 | end 47 | 48 | fake_class = Class.new 49 | stub_const("CardDeck", fake_class) 50 | CardDeck # => fake_class 51 | CardDeck::SUITS # => raises uninitialized constant error 52 | CardDeck::NUM_CARDS # => raises uninitialized constant error 53 | 54 | stub_const("CardDeck", fake_class, :transfer_nested_constants => true) 55 | CardDeck::SUITS # => [:Spades, :Diamonds, :Clubs, :Hearts] 56 | CardDeck::NUM_CARDS # => 52 57 | 58 | stub_const("CardDeck", fake_class, :transfer_nested_constants => [:SUITS]) 59 | CardDeck::SUITS # => [:Spades, :Diamonds, :Clubs, :Hearts] 60 | CardDeck::NUM_CARDS # => raises uninitialized constant error 61 | ``` 62 | 63 | ### Hiding 64 | 65 | Support is also provided for hiding constants. Hiding a constant temporarily removes it; it is 66 | restored to its original value after the test completes. 67 | 68 | ```ruby 69 | FOO = 42 70 | hide_const("FOO") 71 | FOO # => NameError: uninitialized constant FOO 72 | ``` 73 | 74 | Like stubbed constants, names must be fully qualified. 75 | 76 | Hiding constants that are already undefined has no effect. 77 | 78 | ```ruby 79 | hide_const("NO_OP") 80 | ``` 81 | -------------------------------------------------------------------------------- /features/mutating_constants/hide_defined_constant.feature: -------------------------------------------------------------------------------- 1 | Feature: Hide Defined Constant 2 | 3 | Use `hide_const` to remove a constant for the duration of a test. 4 | 5 | Scenario: Hide top-level constant 6 | Given a file named "hide_const_spec.rb" with: 7 | """ruby 8 | FOO = 7 9 | 10 | RSpec.describe "hiding FOO" do 11 | it "can hide FOO" do 12 | hide_const("FOO") 13 | expect { FOO }.to raise_error(NameError) 14 | end 15 | 16 | it "restores the hidden constant when the example completes" do 17 | expect(FOO).to eq(7) 18 | end 19 | end 20 | """ 21 | When I run `rspec hide_const_spec.rb` 22 | Then the examples should all pass 23 | 24 | Scenario: Hide nested constant 25 | Given a file named "hide_const_spec.rb" with: 26 | """ruby 27 | module MyGem 28 | class SomeClass 29 | FOO = 7 30 | end 31 | end 32 | 33 | module MyGem 34 | RSpec.describe SomeClass do 35 | it "hides the nested constant when it is fully qualified" do 36 | hide_const("MyGem::SomeClass::FOO") 37 | expect { SomeClass::FOO }.to raise_error(NameError) 38 | end 39 | 40 | it "restores the hidden constant when the example completes" do 41 | expect(MyGem::SomeClass::FOO).to eq(7) 42 | end 43 | end 44 | end 45 | """ 46 | When I run `rspec hide_const_spec.rb` 47 | Then the examples should all pass 48 | 49 | Scenario: Hiding undefined constant 50 | Given a file named "hide_const_spec.rb" with: 51 | """ruby 52 | RSpec.describe "hiding UNDEFINED_CONSTANT" do 53 | it "has no effect" do 54 | hide_const("UNDEFINED_CONSTANT") 55 | expect { UNDEFINED_CONSTANT }.to raise_error(NameError) 56 | end 57 | 58 | it "is still undefined after the example completes" do 59 | expect { UNDEFINED_CONSTANT }.to raise_error(NameError) 60 | end 61 | end 62 | """ 63 | When I run `rspec hide_const_spec.rb` 64 | Then the examples should all pass 65 | -------------------------------------------------------------------------------- /features/mutating_constants/hide_undefined_constant.feature: -------------------------------------------------------------------------------- 1 | Feature: Hide Undefined Constant 2 | 3 | Hiding a constant that is already undefined is a no-op. This can be useful when a spec file 4 | may run in either an isolated environment (e.g. when running one spec file) or in a full 5 | environment with all parts of your code base loaded (e.g. when running your entire suite). 6 | 7 | Scenario: Hiding undefined constant 8 | Given a file named "hide_const_spec.rb" with: 9 | """ruby 10 | RSpec.describe "hiding UNDEFINED_CONSTANT" do 11 | it "has no effect" do 12 | hide_const("UNDEFINED_CONSTANT") 13 | expect { UNDEFINED_CONSTANT }.to raise_error(NameError) 14 | end 15 | 16 | it "is still undefined after the example completes" do 17 | expect { UNDEFINED_CONSTANT }.to raise_error(NameError) 18 | end 19 | end 20 | """ 21 | When I run `rspec hide_const_spec.rb` 22 | Then the examples should all pass 23 | -------------------------------------------------------------------------------- /features/mutating_constants/stub_defined_constant.feature: -------------------------------------------------------------------------------- 1 | Feature: Stub Defined Constant 2 | 3 | Use `stub_const` to stub constants. When the constant is already defined, the stubbed value 4 | will replace the original value for the duration of the example. 5 | 6 | Scenario: Stub top-level constant 7 | Given a file named "stub_const_spec.rb" with: 8 | """ruby 9 | FOO = 7 10 | 11 | RSpec.describe "stubbing FOO" do 12 | it "can stub FOO with a different value" do 13 | stub_const("FOO", 5) 14 | expect(FOO).to eq(5) 15 | end 16 | 17 | it "restores the stubbed constant when the example completes" do 18 | expect(FOO).to eq(7) 19 | end 20 | end 21 | """ 22 | When I run `rspec stub_const_spec.rb` 23 | Then the examples should all pass 24 | 25 | Scenario: Stub nested constant 26 | Given a file named "stub_const_spec.rb" with: 27 | """ruby 28 | module MyGem 29 | class SomeClass 30 | FOO = 7 31 | end 32 | end 33 | 34 | module MyGem 35 | RSpec.describe SomeClass do 36 | it "stubs the nested constant when it is fully qualified" do 37 | stub_const("MyGem::SomeClass::FOO", 5) 38 | expect(SomeClass::FOO).to eq(5) 39 | end 40 | end 41 | end 42 | """ 43 | When I run `rspec stub_const_spec.rb` 44 | Then the examples should all pass 45 | 46 | Scenario: Transfer nested constants 47 | Given a file named "stub_const_spec.rb" with: 48 | """ruby 49 | module MyGem 50 | class SomeClass 51 | FOO = 7 52 | end 53 | end 54 | 55 | module MyGem 56 | RSpec.describe SomeClass do 57 | let(:fake_class) { Class.new } 58 | 59 | it "does not transfer nested constants by default" do 60 | stub_const("MyGem::SomeClass", fake_class) 61 | expect { SomeClass::FOO }.to raise_error(NameError) 62 | end 63 | 64 | it "transfers nested constants when using :transfer_nested_constants => true" do 65 | stub_const("MyGem::SomeClass", fake_class, :transfer_nested_constants => true) 66 | expect(SomeClass::FOO).to eq(7) 67 | end 68 | 69 | it "can specify a list of nested constants to transfer" do 70 | stub_const("MyGem::SomeClass", fake_class, :transfer_nested_constants => [:FOO]) 71 | expect(SomeClass::FOO).to eq(7) 72 | end 73 | end 74 | end 75 | """ 76 | When I run `rspec stub_const_spec.rb` 77 | Then the examples should all pass 78 | -------------------------------------------------------------------------------- /features/mutating_constants/stub_undefined_constant.feature: -------------------------------------------------------------------------------- 1 | Feature: Stub Undefined Constant 2 | 3 | Use `stub_const` to stub constants. When the constant is not already defined, all the 4 | necessary intermediary modules will be dynamically created. When the example completes, 5 | the intermediary module constants will be removed to return the constant state to how it 6 | started. 7 | 8 | Scenario: Stub top-level constant 9 | Given a file named "stub_const_spec.rb" with: 10 | """ruby 11 | RSpec.describe "stubbing FOO" do 12 | it "can stub undefined constant FOO" do 13 | stub_const("FOO", 5) 14 | expect(FOO).to eq(5) 15 | end 16 | 17 | it "undefines the constant when the example completes" do 18 | expect { FOO }.to raise_error(NameError) 19 | end 20 | end 21 | """ 22 | When I run `rspec stub_const_spec.rb` 23 | Then the examples should all pass 24 | 25 | Scenario: Stub nested constant 26 | Given a file named "stub_const_spec.rb" with: 27 | """ruby 28 | module MyGem 29 | class SomeClass 30 | end 31 | end 32 | 33 | module MyGem 34 | RSpec.describe SomeClass do 35 | it "can stub an arbitrarily deep constant that is undefined" do 36 | expect(defined?(SomeClass::A)).to be_falsey 37 | stub_const("MyGem::SomeClass::A::B::C", 3) 38 | expect(SomeClass::A::B::C).to eq(3) 39 | expect(SomeClass::A).to be_a(Module) 40 | end 41 | 42 | it 'undefines the intermediary constants that were dynamically created' do 43 | expect(defined?(SomeClass)).to be_truthy 44 | expect(defined?(SomeClass::A)).to be_falsey 45 | end 46 | end 47 | end 48 | """ 49 | When I run `rspec stub_const_spec.rb` 50 | Then the examples should all pass 51 | -------------------------------------------------------------------------------- /features/old_syntax/README.md: -------------------------------------------------------------------------------- 1 | # Old Syntax 2 | 3 | Historically, rspec-mocks has used a monkey-patched syntax to allow you to mock or stub any object: 4 | 5 | ```ruby 6 | obj.stub(:foo).and_return(15) 7 | obj.should_receive(:bar) 8 | ``` 9 | 10 | Unfortunately, this is prone to weird, confusing failures when applied to [delegate/proxy 11 | objects](https://rspec.info/blog/2012/06/rspecs-new-expectation-syntax#delegation_issues). For a method like `stub` to work properly, it must be defined on every object in the 12 | system, but RSpec does not own every object in the system and cannot ensure that it always 13 | works consistently. 14 | 15 | For this reason, in RSpec 2.14, we introduced a new syntax that avoids monkey patching 16 | altogether. It's the syntax shown in all examples of this documentation outside of this 17 | directory. As of RSpec 3, we consider this to be the main, recommended syntax of rspec- 18 | mocks. The old monkey-patched syntax continues to work, but you will get a deprecation 19 | warning if you use it without explicitly opting-in to it: 20 | 21 | ```ruby 22 | # If you're using rspec-core: 23 | RSpec.configure do |config| 24 | config.mock_with :rspec do |mocks| 25 | mocks.syntax = :should 26 | end 27 | end 28 | 29 | # Or, if you're using rspec-mocks in another context: 30 | RSpec::Mocks.configuration.syntax = :should 31 | ``` 32 | 33 | We have no plans to ever kill the old syntax, but we may extract it into an external gem in 34 | RSpec 4. 35 | 36 | If you have an old project that uses the old syntax and you want to update it to the current 37 | syntax, checkout [transpec](http://yujinakayama.me/transpec/). 38 | -------------------------------------------------------------------------------- /features/old_syntax/should_receive.feature: -------------------------------------------------------------------------------- 1 | @allow-old-syntax 2 | Feature: `should_receive` 3 | 4 | `should_receive` is the old way to [expect messages](../basics/expecting-messages) but carries the 5 | baggage of a global monkey patch on all objects. It supports the 6 | same fluent interface for [setting constraints](../setting-constraints) and [configuring responses](../configuring-responses). 7 | 8 | Similarly, you can use `should_not_receive` to set a negative message expectation. 9 | 10 | Background: 11 | Given a file named "spec/spec_helper.rb" with: 12 | """ruby 13 | RSpec.configure do |config| 14 | config.mock_with :rspec do |mocks| 15 | mocks.syntax = :should 16 | end 17 | end 18 | """ 19 | And a file named ".rspec" with: 20 | """ 21 | --require spec_helper 22 | """ 23 | 24 | Scenario: Failing positive message expectation 25 | Given a file named "spec/unfulfilled_message_expectation_spec.rb" with: 26 | """ruby 27 | RSpec.describe "An unfulfilled message expectation" do 28 | it "triggers a failure" do 29 | dbl = double("Some Collaborator") 30 | dbl.should_receive(:foo) 31 | end 32 | end 33 | """ 34 | When I run `rspec spec/unfulfilled_message_expectation_spec.rb` 35 | Then it should fail with: 36 | """ 37 | 1) An unfulfilled message expectation triggers a failure 38 | Failure/Error: dbl.should_receive(:foo) 39 | 40 | (Double "Some Collaborator").foo(*(any args)) 41 | expected: 1 time with any arguments 42 | received: 0 times with any arguments 43 | """ 44 | 45 | Scenario: Passing positive message expectation 46 | Given a file named "spec/fulfilled_message_expectation_spec.rb" with: 47 | """ruby 48 | RSpec.describe "A fulfilled message expectation" do 49 | it "passes" do 50 | dbl = double("Some Collaborator") 51 | dbl.should_receive(:foo) 52 | dbl.foo 53 | end 54 | end 55 | """ 56 | When I run `rspec spec/fulfilled_message_expectation_spec.rb` 57 | Then the examples should all pass 58 | 59 | Scenario: Failing negative message expectation 60 | Given a file named "spec/negative_message_expectation_spec.rb" with: 61 | """ruby 62 | RSpec.describe "A negative message expectation" do 63 | it "fails when the message is received" do 64 | dbl = double("Some Collaborator").as_null_object 65 | dbl.should_not_receive(:foo) 66 | dbl.foo 67 | end 68 | end 69 | """ 70 | When I run `rspec spec/negative_message_expectation_spec.rb` 71 | Then it should fail with: 72 | """ 73 | 1) A negative message expectation fails when the message is received 74 | Failure/Error: dbl.foo 75 | 76 | (Double "Some Collaborator").foo(no args) 77 | expected: 0 times with any arguments 78 | received: 1 time 79 | """ 80 | 81 | Scenario: Passing negative message expectation 82 | Given a file named "spec/negative_message_expectation_spec.rb" with: 83 | """ruby 84 | RSpec.describe "A negative message expectation" do 85 | it "passes if the message is never received" do 86 | dbl = double("Some Collaborator").as_null_object 87 | dbl.should_not_receive(:foo) 88 | end 89 | end 90 | """ 91 | When I run `rspec spec/negative_message_expectation_spec.rb` 92 | Then the examples should all pass 93 | -------------------------------------------------------------------------------- /features/old_syntax/stub.feature: -------------------------------------------------------------------------------- 1 | @allow-old-syntax 2 | Feature: `stub` 3 | 4 | `stub` is the old way to [allow messages](../basics/allowing-messages) but carries the baggage of a 5 | global monkey patch on all objects. It supports the same fluent 6 | interface for [setting constraints](../setting-constraints) and [configuring responses](../configuring-responses). You can also pass `stub` a hash 7 | of message/return-value pairs, which acts like `allow(obj).to receive_messages(hash)`, 8 | but does not support further customization through the fluent interface. 9 | 10 | Background: 11 | Given a file named "spec/spec_helper.rb" with: 12 | """ruby 13 | RSpec.configure do |config| 14 | config.mock_with :rspec do |mocks| 15 | mocks.syntax = :should 16 | end 17 | end 18 | """ 19 | And a file named ".rspec" with: 20 | """ 21 | --require spec_helper 22 | """ 23 | 24 | Scenario: Stub a method 25 | Given a file named "spec/stub_spec.rb" with: 26 | """ruby 27 | RSpec.describe "Stubbing a method" do 28 | it "configures how the object responds" do 29 | dbl = double 30 | dbl.stub(:foo).and_return(13) 31 | expect(dbl.foo).to eq(13) 32 | end 33 | end 34 | """ 35 | When I run `rspec spec/stub_spec.rb` 36 | Then the examples should all pass 37 | 38 | Scenario: Stub multiple methods by passing a hash 39 | Given a file named "spec/stub_multiple_methods_spec.rb" with: 40 | """ruby 41 | RSpec.describe "Stubbing multiple methods" do 42 | it "stubs each named method with the given return value" do 43 | dbl = double 44 | dbl.stub(:foo => 13, :bar => 10) 45 | expect(dbl.foo).to eq(13) 46 | expect(dbl.bar).to eq(10) 47 | end 48 | end 49 | """ 50 | When I run `rspec spec/stub_multiple_methods_spec.rb` 51 | Then the examples should all pass 52 | -------------------------------------------------------------------------------- /features/old_syntax/stub_chain.feature: -------------------------------------------------------------------------------- 1 | @allow-old-syntax 2 | Feature: `stub_chain` 3 | 4 | `stub_chain` is the old way to [allow a message chain](../working-with-legacy-code/message-chains) but carries the 5 | baggage of a global monkey patch on all objects. As with 6 | `receive_message_chain`, use with care; we recommend treating usage of `stub_chain` as a 7 | code smell. 8 | 9 | Background: 10 | Given a file named "spec/spec_helper.rb" with: 11 | """ruby 12 | RSpec.configure do |config| 13 | config.mock_with :rspec do |mocks| 14 | mocks.syntax = :should 15 | end 16 | end 17 | """ 18 | And a file named ".rspec" with: 19 | """ 20 | --require spec_helper 21 | """ 22 | 23 | Scenario: Use `stub_chain` on a double 24 | Given a file named "spec/stub_chain_spec.rb" with: 25 | """ruby 26 | RSpec.describe "Using stub_chain on a double" do 27 | let(:dbl) { double } 28 | 29 | example "using a string and a block" do 30 | dbl.stub_chain("foo.bar") { :baz } 31 | expect(dbl.foo.bar).to eq(:baz) 32 | end 33 | 34 | example "using symbols and a hash" do 35 | dbl.stub_chain(:foo, :bar => :baz) 36 | expect(dbl.foo.bar).to eq(:baz) 37 | end 38 | 39 | example "using symbols and a block" do 40 | dbl.stub_chain(:foo, :bar) { :baz } 41 | expect(dbl.foo.bar).to eq(:baz) 42 | end 43 | end 44 | """ 45 | When I run `rspec spec/stub_chain_spec.rb` 46 | Then the examples should all pass 47 | 48 | Scenario: Use `stub_chain` on any instance of a class 49 | Given a file named "spec/stub_chain_spec.rb" with: 50 | """ruby 51 | RSpec.describe "Using any_instance.stub_chain" do 52 | example "using a string and a block" do 53 | Object.any_instance.stub_chain("foo.bar") { :baz } 54 | expect(Object.new.foo.bar).to eq(:baz) 55 | end 56 | 57 | example "using symbols and a hash" do 58 | Object.any_instance.stub_chain(:foo, :bar => :baz) 59 | expect(Object.new.foo.bar).to eq(:baz) 60 | end 61 | 62 | example "using symbols and a block" do 63 | Object.any_instance.stub_chain(:foo, :bar) { :baz } 64 | expect(Object.new.foo.bar).to eq(:baz) 65 | end 66 | end 67 | """ 68 | When I run `rspec spec/stub_chain_spec.rb` 69 | Then the examples should all pass 70 | -------------------------------------------------------------------------------- /features/old_syntax/unstub.feature: -------------------------------------------------------------------------------- 1 | @allow-old-syntax 2 | Feature: Using `unstub` 3 | 4 | `unstub` removes a method stub, essentially cleaning up the method 5 | stub early, rather than waiting for the cleanup that runs at the end 6 | of the example. The newer non-monkey-patching syntax does not have a direct 7 | equivalent but in most situations you can achieve the same behavior using 8 | [`and_call_original`](../configuring-responses/calling-the-original-implementation). The difference is that `obj.unstub(:foo)` completely cleans up the `foo` 9 | method stub, whereas `allow(obj).to receive(:foo).and_call_original` continues to 10 | observe calls to the method (important when you are using [spies](../basics/spies)), which could affect the 11 | method's behavior if it does anything with `caller` as it will include additional rspec stack 12 | frames. 13 | 14 | Background: 15 | Given a file named "spec/spec_helper.rb" with: 16 | """ruby 17 | RSpec.configure do |config| 18 | config.mock_with :rspec do |mocks| 19 | mocks.syntax = :should 20 | end 21 | end 22 | """ 23 | And a file named ".rspec" with: 24 | """ 25 | --require spec_helper 26 | """ 27 | 28 | Scenario: Unstub a method 29 | Given a file named "spec/unstub_spec.rb" with: 30 | """ruby 31 | RSpec.describe "Unstubbing a method" do 32 | it "restores the original behavior" do 33 | string = "hello world" 34 | string.stub(:reverse) { "hello dlrow" } 35 | 36 | expect { 37 | string.unstub(:reverse) 38 | }.to change { string.reverse }.from("hello dlrow").to("dlrow olleh") 39 | end 40 | end 41 | """ 42 | When I run `rspec spec/unstub_spec.rb` 43 | Then the examples should all pass 44 | -------------------------------------------------------------------------------- /features/outside_rspec/standalone.feature: -------------------------------------------------------------------------------- 1 | Feature: Using `rspec-mocks` on its own outside of RSpec (standalone mode) 2 | 3 | `require "rspec/mocks/standalone"` to expose the API at the top level (e.g. `main`) outside 4 | the RSpec environment in a REPL like IRB or in a one-off script. 5 | 6 | Scenario: Allow a message outside RSpec 7 | Given a file named "example.rb" with: 8 | """ruby 9 | require "rspec/mocks/standalone" 10 | 11 | greeter = double("greeter") 12 | allow(greeter).to receive(:say_hi) { "Hello!" } 13 | puts greeter.say_hi 14 | """ 15 | When I run `ruby example.rb` 16 | Then the output should contain "Hello!" 17 | 18 | Scenario: Expect a message outside RSpec 19 | Given a file named "example.rb" with: 20 | """ruby 21 | require "rspec/mocks/standalone" 22 | 23 | greeter = double("greeter") 24 | expect(greeter).to receive(:say_hi) 25 | 26 | RSpec::Mocks.verify 27 | """ 28 | When I run `ruby example.rb` 29 | Then it should fail with the following output: 30 | | (Double "greeter").say_hi(*(any args)) | 31 | | RSpec::Mocks::MockExpectationError | 32 | | expected: 1 time with any arguments | 33 | | received: 0 times with any arguments | 34 | -------------------------------------------------------------------------------- /features/setting_constraints/README.md: -------------------------------------------------------------------------------- 1 | # Setting Constraints 2 | 3 | RSpec provides a fluent interface off of `expect(...).to receive(...)` that allows you to 4 | further constrain what you expect: the arguments, the number of times, and the ordering of 5 | multiple messages. 6 | 7 | Although not shown here, this fluent interface is also supported by [spies](./basics/spies), off of 8 | `have_received(...)`. 9 | -------------------------------------------------------------------------------- /features/setting_constraints/message_order.feature: -------------------------------------------------------------------------------- 1 | Feature: Message Order 2 | 3 | You can use `ordered` to constrain the order of multiple message expectations. This is not 4 | generally recommended because in most situations the order doesn't matter and using 5 | `ordered` would make your spec brittle, but it's occasionally useful. When you use `ordered`, 6 | the example will only pass if the messages are received in the declared order. 7 | 8 | Scenario: Passing example 9 | Given a file named "passing_example_spec.rb" with: 10 | """ruby 11 | RSpec.describe "Constraining order" do 12 | it "passes when the messages are received in declared order" do 13 | collaborator_1 = double("Collaborator 1") 14 | collaborator_2 = double("Collaborator 2") 15 | 16 | expect(collaborator_1).to receive(:step_1).ordered 17 | expect(collaborator_2).to receive(:step_2).ordered 18 | expect(collaborator_1).to receive(:step_3).ordered 19 | 20 | collaborator_1.step_1 21 | collaborator_2.step_2 22 | collaborator_1.step_3 23 | end 24 | end 25 | """ 26 | When I run `rspec passing_example_spec.rb` 27 | Then the examples should all pass 28 | 29 | Scenario: Failing examples 30 | Given a file named "failing_examples_spec.rb" with: 31 | """ruby 32 | RSpec.describe "Constraining order" do 33 | it "fails when messages are received out of order on one collaborator" do 34 | collaborator_1 = double("Collaborator 1") 35 | 36 | expect(collaborator_1).to receive(:step_1).ordered 37 | expect(collaborator_1).to receive(:step_2).ordered 38 | 39 | collaborator_1.step_2 40 | collaborator_1.step_1 41 | end 42 | 43 | it "fails when messages are received out of order between collaborators" do 44 | collaborator_1 = double("Collaborator 1") 45 | collaborator_2 = double("Collaborator 2") 46 | 47 | expect(collaborator_1).to receive(:step_1).ordered 48 | expect(collaborator_2).to receive(:step_2).ordered 49 | 50 | collaborator_2.step_2 51 | collaborator_1.step_1 52 | end 53 | end 54 | """ 55 | When I run `rspec failing_examples_spec.rb --order defined` 56 | Then the examples should all fail, producing the following output: 57 | | 1) Constraining order fails when messages are received out of order on one collaborator | 58 | | Failure/Error: collaborator_1.step_2 | 59 | | # received :step_2 out of order | 60 | | | 61 | | 2) Constraining order fails when messages are received out of order between collaborators | 62 | | Failure/Error: collaborator_2.step_2 | 63 | | # received :step_2 out of order | 64 | -------------------------------------------------------------------------------- /features/step_definitions/additional_cli_steps.rb: -------------------------------------------------------------------------------- 1 | Then /^the example(?:s)? should(?: all)? pass$/ do 2 | step %q(the output should contain "0 failures") 3 | step %q(the exit status should be 0) 4 | end 5 | 6 | Then /^the examples should all fail, producing the following output:$/ do |table| 7 | step %q(the exit status should be 1) 8 | examples, failures = all_output.match(/(\d+) examples?, (\d+) failures?/).captures.map(&:to_i) 9 | 10 | expect(examples).to be > 0 11 | expect(examples).to eq(failures) 12 | 13 | lines = table.raw.flatten.reject(&:empty?) 14 | expect(all_output).to include(*lines) 15 | end 16 | 17 | RSpec::Matchers.define :match_table do |lines| 18 | match do |all_output| 19 | lines.all? { |line| all_output.include?(line) } 20 | end 21 | 22 | diffable 23 | end 24 | 25 | Then /^it should fail with the following output(, ignoring hash syntax)?:$/ do |ignore_hash_syntax, table| 26 | step %q(the exit status should be 1) 27 | lines = table.raw.flatten.reject(&:empty?) 28 | 29 | if ignore_hash_syntax && RUBY_VERSION.to_f > 3.3 30 | lines = lines.map { |line| line.gsub(/([^\s])=>/, '\1 => ') } 31 | end 32 | 33 | expect(all_output).to match_table(lines) 34 | end 35 | -------------------------------------------------------------------------------- /features/support/disallow_certain_apis.rb: -------------------------------------------------------------------------------- 1 | # This file is designed to prevent the use of certain APIs that 2 | # we don't want used from our cukes, since they function as documentation. 3 | 4 | if defined?(Cucumber) 5 | require 'shellwords' 6 | tag = !defined?(::RUBY_ENGINE_VERSION) || (::RUBY_ENGINE_VERSION < '2.0.0') ? '~@allow-old-syntax' : 'not @allow-old-syntax' 7 | Before(tag) do 8 | set_environment_variable('SPEC_OPTS', "-r#{Shellwords.escape(__FILE__)}") 9 | end 10 | else 11 | module DisallowOneLinerShould 12 | def should(*) 13 | raise "one-liner should is not allowed" 14 | end 15 | 16 | def should_not(*) 17 | raise "one-liner should_not is not allowed" 18 | end 19 | end 20 | 21 | RSpec.configure do |rspec| 22 | rspec.disable_monkey_patching! 23 | rspec.include DisallowOneLinerShould 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'aruba/cucumber' 2 | require 'rspec/expectations' 3 | 4 | Aruba.configure do |config| 5 | if RUBY_PLATFORM =~ /java/ || defined?(Rubinius) || (defined?(RUBY_ENGINE) && RUBY_ENGINE == 'truffleruby') 6 | config.exit_timeout = 60 7 | else 8 | config.exit_timeout = 5 9 | end 10 | end 11 | 12 | Before do 13 | if RUBY_PLATFORM == 'java' 14 | # disable JIT since these processes are so short lived 15 | set_environment_variable('JRUBY_OPTS', "-X-C #{ENV['JRUBY_OPTS']}") 16 | end 17 | 18 | if defined?(Rubinius) 19 | # disable JIT since these processes are so short lived 20 | set_environment_variable('RBXOPT', "-Xint=true #{ENV['RBXOPT']}") 21 | end 22 | end 23 | 24 | Before('@ripper') do |scenario| 25 | unless RSpec::Support::RubyFeatures.ripper_supported? 26 | warn "Skipping scenario due to lack of Ripper support" 27 | if Cucumber::VERSION.to_f >= 3.0 28 | skip_this_scenario 29 | else 30 | scenario.skip_invoke! 31 | end 32 | end 33 | end 34 | 35 | Before('@kw-arguments') do |scenario| 36 | unless RSpec::Support::RubyFeatures.kw_args_supported? 37 | warn "Skipping scenario due to lack of keyword argument support" 38 | if Cucumber::VERSION.to_f >= 3.0 39 | skip_this_scenario 40 | else 41 | scenario.skip_invoke! 42 | end 43 | end 44 | end 45 | 46 | Before('@distincts_kw_args_from_positional_hash') do |scenario| 47 | unless RSpec::Support::RubyFeatures. distincts_kw_args_from_positional_hash? 48 | warn "Skipping scenario due to not applicable to this ruby" 49 | if Cucumber::VERSION.to_f >= 3.0 50 | skip_this_scenario 51 | else 52 | scenario.skip_invoke! 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /features/support/rubinius.rb: -------------------------------------------------------------------------------- 1 | # Required until https://github.com/rubinius/rubinius/issues/2430 is resolved 2 | ENV['RBXOPT'] = "#{ENV["RBXOPT"]} -Xcompiler.no_rbc" 3 | 4 | Around "@unsupported-on-rbx" do |_scenario, block| 5 | block.call unless defined?(Rubinius) 6 | end 7 | -------------------------------------------------------------------------------- /features/verifying_doubles/README.md: -------------------------------------------------------------------------------- 1 | # Verifying doubles 2 | 3 | Verifying doubles are a stricter alternative to [normal doubles](./basics/test-doubles) that provide guarantees about 4 | what is being verified. When using verifying doubles, RSpec will check that the methods 5 | being stubbed are actually present on the underlying object if it is available. Prefer using 6 | verifying doubles over normal doubles. 7 | 8 | No checking will happen if the underlying object or class is not defined, but when run with 9 | it present (either as a full spec run or by explicitly preloading collaborators) a failure will be 10 | triggered if an invalid method is being stubbed or a method is called with an invalid 11 | number of arguments. 12 | 13 | This dual approach allows you to move very quickly and test components in isolation, while 14 | giving you confidence that your doubles are not a complete fiction. Testing in isolation is 15 | optional but recommended for classes that do not depend on third-party components. 16 | -------------------------------------------------------------------------------- /features/verifying_doubles/class_doubles.feature: -------------------------------------------------------------------------------- 1 | Feature: Using a class double 2 | 3 | `class_double` is provided as a complement to [`instance_double`](./instance-doubles) with the difference that it 4 | verifies _class_ methods on the given class rather than instance methods. 5 | 6 | In addition, it also provides a convenience method `as_stubbed_const` to replace concrete 7 | classes with the defined double. See [mutating constants](../mutating-constants) for more details. 8 | 9 | Note: `class_double` can be used for modules as well. We chose to stick with the 10 | `class_double` terminology because the methods a `class_double` verifies against are 11 | commonly called "class methods", not "module methods", even when working with a module. 12 | 13 | Background: 14 | Given a file named "lib/user.rb" with: 15 | """ruby 16 | class User 17 | def suspend! 18 | ConsoleNotifier.notify("suspended as") 19 | end 20 | end 21 | """ 22 | 23 | Given a file named "lib/console_notifier.rb" with: 24 | """ruby 25 | class ConsoleNotifier 26 | MAX_WIDTH = 80 27 | 28 | def self.notify(message) 29 | puts message 30 | end 31 | end 32 | """ 33 | 34 | Given a file named "spec/user_spec.rb" with: 35 | """ruby 36 | require 'user' 37 | require 'console_notifier' 38 | 39 | RSpec.describe User, '#suspend!' do 40 | it 'notifies the console' do 41 | notifier = class_double("ConsoleNotifier"). 42 | as_stubbed_const(:transfer_nested_constants => true) 43 | 44 | expect(notifier).to receive(:notify).with("suspended as") 45 | expect(ConsoleNotifier::MAX_WIDTH).to eq(80) 46 | 47 | user = User.new 48 | user.suspend! 49 | end 50 | end 51 | """ 52 | 53 | Scenario: Replacing existing constants 54 | When I run `rspec spec/user_spec.rb` 55 | Then the examples should all pass 56 | 57 | Scenario: Renaming `ConsoleNotifier.notify` to `send_notification` 58 | Given a file named "lib/console_notifier.rb" with: 59 | """ruby 60 | class ConsoleNotifier 61 | MAX_WIDTH = 80 62 | 63 | def self.send_notification(message) 64 | puts message 65 | end 66 | end 67 | """ 68 | When I run `rspec spec/user_spec.rb` 69 | Then the output should contain "1 example, 1 failure" 70 | And the output should contain "the ConsoleNotifier class does not implement the class method:" 71 | -------------------------------------------------------------------------------- /features/verifying_doubles/object_doubles.feature: -------------------------------------------------------------------------------- 1 | Feature: Using an object double 2 | 3 | `object_double` can be used to create a double from an existing "template" object, from 4 | which it verifies that any stubbed methods on the double also exist on the template. This is 5 | useful for objects that are readily constructable, but may have far-reaching side-effects 6 | such as talking to a database or external API. In this case, using a double rather than the 7 | real thing allows you to focus on the communication patterns of the object's interface 8 | without having to worry about accidentally causing side-effects. Object doubles can also be 9 | used to verify methods defined on an object using `method_missing`, which is not possible 10 | with [`instance_double`](./instance-doubles). 11 | 12 | In addition, `object_double` can be used with specific constant values, as shown below. This 13 | is for niche situations, such as when dealing with singleton objects. 14 | 15 | Scenario: Doubling an existing object 16 | Given a file named "spec/user_spec.rb" with: 17 | """ruby 18 | class User 19 | # Don't want to accidentally trigger this! 20 | def save; sleep 100; end 21 | end 22 | 23 | def save_user(user) 24 | "saved!" if user.save 25 | end 26 | 27 | RSpec.describe '#save_user' do 28 | it 'renders message on success' do 29 | user = object_double(User.new, :save => true) 30 | expect(save_user(user)).to eq("saved!") 31 | end 32 | end 33 | """ 34 | When I run `rspec spec/user_spec.rb` 35 | Then the examples should all pass 36 | 37 | Scenario: Doubling a constant object 38 | Given a file named "spec/email_spec.rb" with: 39 | """ruby 40 | require 'logger' 41 | 42 | module MyApp 43 | LOGGER = Logger.new("myapp") 44 | end 45 | 46 | class Email 47 | def self.send_to(recipient) 48 | MyApp::LOGGER.info("Sent to #{recipient}") 49 | # other emailing logic 50 | end 51 | end 52 | 53 | RSpec.describe Email do 54 | it 'logs a message when sending' do 55 | logger = object_double("MyApp::LOGGER", :info => nil).as_stubbed_const 56 | Email.send_to('hello@foo.com') 57 | expect(logger).to have_received(:info).with("Sent to hello@foo.com") 58 | end 59 | end 60 | """ 61 | When I run `rspec spec/email_spec.rb` 62 | Then the examples should all pass 63 | -------------------------------------------------------------------------------- /features/verifying_doubles/partial_doubles.feature: -------------------------------------------------------------------------------- 1 | Feature: Partial doubles 2 | 3 | When the `verify_partial_doubles` configuration option is set, the same argument and 4 | method existence checks that are performed for [`object_double`](./object-doubles) are also performed on 5 | [partial doubles](../basics/partial-test-doubles). You should set this unless you have a good reason not to. It defaults to off 6 | only for backwards compatibility. 7 | 8 | Scenario: Doubling an existing object 9 | Given a file named "spec/user_spec.rb" with: 10 | """ruby 11 | class User 12 | def save; false; end 13 | end 14 | 15 | def save_user(user) 16 | "saved!" if user.save 17 | end 18 | 19 | RSpec.configure do |config| 20 | config.mock_with :rspec do |mocks| 21 | mocks.verify_partial_doubles = true 22 | end 23 | end 24 | 25 | RSpec.describe '#save_user' do 26 | it 'renders message on success' do 27 | user = User.new 28 | expect(user).to receive(:wave).and_return(true) # Typo in name 29 | expect(save_user(user)).to eq("saved!") 30 | end 31 | end 32 | """ 33 | When I run `rspec spec/user_spec.rb` 34 | Then the output should contain "1 example, 1 failure" 35 | -------------------------------------------------------------------------------- /features/working_with_legacy_code/README.md: -------------------------------------------------------------------------------- 1 | # Working with legacy code 2 | 3 | RSpec provides a few features that, while not generally recommended, can be useful when 4 | you are getting legacy code under test (or in similar situations). Usage of these features 5 | should be considered a code smell. 6 | -------------------------------------------------------------------------------- /lib/rspec/mocks/any_instance.rb: -------------------------------------------------------------------------------- 1 | %w[ 2 | any_instance/chain 3 | any_instance/error_generator 4 | any_instance/stub_chain 5 | any_instance/stub_chain_chain 6 | any_instance/expect_chain_chain 7 | any_instance/expectation_chain 8 | any_instance/message_chains 9 | any_instance/recorder 10 | any_instance/proxy 11 | ].each { |f| RSpec::Support.require_rspec_mocks(f) } 12 | -------------------------------------------------------------------------------- /lib/rspec/mocks/any_instance/chain.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | # @private 4 | module AnyInstance 5 | # @private 6 | class Chain 7 | def initialize(recorder, *args, &block) 8 | @recorder = recorder 9 | @expectation_args = args 10 | @expectation_block = block 11 | @argument_list_matcher = ArgumentListMatcher::MATCH_ALL 12 | end 13 | 14 | # @private 15 | # 16 | # Provides convenience methods for recording customizations on message 17 | # expectations. 18 | module Customizations 19 | # @macro [attach] record 20 | # @method $1(*args, &block) 21 | # Records the `$1` message for playback against an instance that 22 | # invokes a method stubbed or mocked using `any_instance`. 23 | # 24 | # @see RSpec::Mocks::MessageExpectation#$1 25 | # 26 | def self.record(method_name) 27 | define_method(method_name) do |*args, &block| 28 | record(method_name, *args, &block) 29 | end 30 | end 31 | 32 | record :and_return 33 | record :and_raise 34 | record :and_throw 35 | record :and_yield 36 | record :and_call_original 37 | record :and_wrap_original 38 | record :with 39 | record :once 40 | record :twice 41 | record :thrice 42 | record :exactly 43 | record :times 44 | record :time 45 | record :never 46 | record :at_least 47 | record :at_most 48 | end 49 | 50 | include Customizations 51 | 52 | # @private 53 | def playback!(instance) 54 | message_expectation = create_message_expectation_on(instance) 55 | messages.inject(message_expectation) do |object, message| 56 | object.__send__(*message.first, &message.last) 57 | end 58 | end 59 | 60 | # @private 61 | def constrained_to_any_of?(*constraints) 62 | constraints.any? do |constraint| 63 | messages.any? do |message| 64 | message.first.first == constraint 65 | end 66 | end 67 | end 68 | 69 | # @private 70 | def matches_args?(*args) 71 | @argument_list_matcher.args_match?(*args) 72 | end 73 | 74 | # @private 75 | def expectation_fulfilled! 76 | @expectation_fulfilled = true 77 | end 78 | 79 | def never 80 | AnyInstance.error_generator.raise_double_negation_error("expect_any_instance_of(MyClass)") if negated? 81 | super 82 | end 83 | 84 | def with(*args, &block) 85 | @argument_list_matcher = ArgumentListMatcher.new(*args) 86 | super 87 | end 88 | 89 | private 90 | 91 | def negated? 92 | messages.any? { |(message, *_), _| message == :never } 93 | end 94 | 95 | def messages 96 | @messages ||= [] 97 | end 98 | 99 | def last_message 100 | messages.last.first.first unless messages.empty? 101 | end 102 | 103 | def record(rspec_method_name, *args, &block) 104 | verify_invocation_order(rspec_method_name, *args, &block) 105 | messages << [args.unshift(rspec_method_name), block] 106 | self 107 | end 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/rspec/mocks/any_instance/error_generator.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | module AnyInstance 4 | # @private 5 | class ErrorGenerator < ::RSpec::Mocks::ErrorGenerator 6 | def raise_second_instance_received_message_error(unfulfilled_expectations) 7 | __raise "Exactly one instance should have received the following " \ 8 | "message(s) but didn't: #{unfulfilled_expectations.sort.join(', ')}" 9 | end 10 | 11 | def raise_does_not_implement_error(klass, method_name) 12 | __raise "#{klass} does not implement ##{method_name}" 13 | end 14 | 15 | def raise_message_already_received_by_other_instance_error(method_name, object_inspect, invoked_instance) 16 | __raise "The message '#{method_name}' was received by #{object_inspect} " \ 17 | "but has already been received by #{invoked_instance}" 18 | end 19 | 20 | def raise_not_supported_with_prepend_error(method_name, problem_mod) 21 | __raise "Using `any_instance` to stub a method (#{method_name}) that has been " \ 22 | "defined on a prepended module (#{problem_mod}) is not supported." 23 | end 24 | end 25 | 26 | def self.error_generator 27 | @error_generator ||= ErrorGenerator.new 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/rspec/mocks/any_instance/expect_chain_chain.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | module AnyInstance 4 | # @private 5 | class ExpectChainChain < StubChain 6 | def initialize(*args) 7 | super 8 | @expectation_fulfilled = false 9 | end 10 | 11 | def expectation_fulfilled? 12 | @expectation_fulfilled 13 | end 14 | 15 | def playback!(instance) 16 | super.tap { @expectation_fulfilled = true } 17 | end 18 | 19 | private 20 | 21 | def create_message_expectation_on(instance) 22 | ::RSpec::Mocks::ExpectChain.expect_chain_on(instance, *@expectation_args, &@expectation_block) 23 | end 24 | 25 | def invocation_order 26 | EmptyInvocationOrder 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/rspec/mocks/any_instance/expectation_chain.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | module AnyInstance 4 | # @private 5 | class ExpectationChain < Chain 6 | def expectation_fulfilled? 7 | @expectation_fulfilled || constrained_to_any_of?(:never) 8 | end 9 | 10 | def initialize(*args, &block) 11 | @expectation_fulfilled = false 12 | super 13 | end 14 | 15 | private 16 | 17 | def verify_invocation_order(_rspec_method_name, *_args, &_block) 18 | end 19 | end 20 | 21 | # @private 22 | class PositiveExpectationChain < ExpectationChain 23 | private 24 | 25 | def create_message_expectation_on(instance) 26 | proxy = ::RSpec::Mocks.space.proxy_for(instance) 27 | method_name, opts = @expectation_args 28 | opts = (opts || {}).merge(:expected_form => IGNORED_BACKTRACE_LINE) 29 | 30 | me = proxy.add_message_expectation(method_name, opts, &@expectation_block) 31 | if RSpec::Mocks.configuration.yield_receiver_to_any_instance_implementation_blocks? 32 | me.and_yield_receiver_to_implementation 33 | end 34 | 35 | me 36 | end 37 | 38 | ExpectationInvocationOrder = 39 | { 40 | :and_return => [:with, nil], 41 | :and_raise => [:with, nil], 42 | }.freeze 43 | 44 | def invocation_order 45 | ExpectationInvocationOrder 46 | end 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/rspec/mocks/any_instance/message_chains.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | module AnyInstance 4 | # @private 5 | class MessageChains 6 | def initialize 7 | @chains_by_method_name = Hash.new { |h, k| h[k] = [] } 8 | end 9 | 10 | # @private 11 | def [](method_name) 12 | @chains_by_method_name[method_name] 13 | end 14 | 15 | # @private 16 | def add(method_name, chain) 17 | @chains_by_method_name[method_name] << chain 18 | chain 19 | end 20 | 21 | # @private 22 | def remove_stub_chains_for!(method_name) 23 | @chains_by_method_name[method_name].reject! do |chain| 24 | StubChain === chain 25 | end 26 | end 27 | 28 | # @private 29 | def has_expectation?(method_name) 30 | @chains_by_method_name[method_name].find do |chain| 31 | ExpectationChain === chain 32 | end 33 | end 34 | 35 | # @private 36 | def each_unfulfilled_expectation_matching(method_name, *args) 37 | @chains_by_method_name[method_name].each do |chain| 38 | yield chain if !chain.expectation_fulfilled? && chain.matches_args?(*args) 39 | end 40 | end 41 | 42 | # @private 43 | def all_expectations_fulfilled? 44 | @chains_by_method_name.all? do |_method_name, chains| 45 | chains.all? { |chain| chain.expectation_fulfilled? } 46 | end 47 | end 48 | 49 | # @private 50 | def unfulfilled_expectations 51 | @chains_by_method_name.map do |method_name, chains| 52 | method_name.to_s if ExpectationChain === chains.last && !chains.last.expectation_fulfilled? 53 | end.compact 54 | end 55 | 56 | # @private 57 | def received_expected_message!(method_name) 58 | @chains_by_method_name[method_name].each do |chain| 59 | chain.expectation_fulfilled! 60 | end 61 | end 62 | 63 | # @private 64 | def playback!(instance, method_name) 65 | raise_if_second_instance_to_receive_message(instance) 66 | @chains_by_method_name[method_name].each do |chain| 67 | chain.playback!(instance) 68 | end 69 | end 70 | 71 | private 72 | 73 | def raise_if_second_instance_to_receive_message(instance) 74 | @instance_with_expectation ||= instance if ExpectationChain === instance 75 | return unless ExpectationChain === instance 76 | return if @instance_with_expectation.equal?(instance) 77 | 78 | AnyInstance.error_generator.raise_second_instance_received_message_error(unfulfilled_expectations) 79 | end 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/rspec/mocks/any_instance/stub_chain.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | module AnyInstance 4 | # @private 5 | class StubChain < Chain 6 | # @private 7 | def expectation_fulfilled? 8 | true 9 | end 10 | 11 | private 12 | 13 | def create_message_expectation_on(instance) 14 | proxy = ::RSpec::Mocks.space.proxy_for(instance) 15 | method_name, opts = @expectation_args 16 | opts = (opts || {}).merge(:expected_form => IGNORED_BACKTRACE_LINE) 17 | 18 | stub = proxy.add_stub(method_name, opts, &@expectation_block) 19 | @recorder.stubs[stub.message] << stub 20 | 21 | if RSpec::Mocks.configuration.yield_receiver_to_any_instance_implementation_blocks? 22 | stub.and_yield_receiver_to_implementation 23 | end 24 | 25 | stub 26 | end 27 | 28 | InvocationOrder = 29 | { 30 | :and_return => [:with, nil], 31 | :and_raise => [:with, nil], 32 | :and_yield => [:with, :and_yield, nil], 33 | :and_throw => [:with, nil], 34 | :and_call_original => [:with, nil], 35 | :and_wrap_original => [:with, nil] 36 | }.freeze 37 | 38 | EmptyInvocationOrder = {}.freeze 39 | 40 | def invocation_order 41 | InvocationOrder 42 | end 43 | 44 | def verify_invocation_order(rspec_method_name, *_args, &_block) 45 | return if invocation_order.fetch(rspec_method_name, [nil]).include?(last_message) 46 | raise NoMethodError, "Undefined method #{rspec_method_name}" 47 | end 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/rspec/mocks/any_instance/stub_chain_chain.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | module AnyInstance 4 | # @private 5 | class StubChainChain < StubChain 6 | def initialize(*args) 7 | super 8 | @expectation_fulfilled = false 9 | end 10 | 11 | private 12 | 13 | def create_message_expectation_on(instance) 14 | ::RSpec::Mocks::StubChain.stub_chain_on(instance, *@expectation_args, &@expectation_block) 15 | end 16 | 17 | def invocation_order 18 | EmptyInvocationOrder 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/rspec/mocks/marshal_extension.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | # Support for `patch_marshal_to_support_partial_doubles` configuration. 4 | # 5 | # @private 6 | class MarshalExtension 7 | def self.patch! 8 | return if Marshal.respond_to?(:dump_with_rspec_mocks) 9 | 10 | Marshal.instance_eval do 11 | class << self 12 | def dump_with_rspec_mocks(object, *rest) 13 | if !::RSpec::Mocks.space.registered?(object) || NilClass === object 14 | dump_without_rspec_mocks(object, *rest) 15 | else 16 | dump_without_rspec_mocks(object.dup, *rest) 17 | end 18 | end 19 | 20 | alias_method :dump_without_rspec_mocks, :dump 21 | undef_method :dump 22 | alias_method :dump, :dump_with_rspec_mocks 23 | end 24 | end 25 | end 26 | 27 | def self.unpatch! 28 | return unless Marshal.respond_to?(:dump_with_rspec_mocks) 29 | 30 | Marshal.instance_eval do 31 | class << self 32 | undef_method :dump_with_rspec_mocks 33 | undef_method :dump 34 | alias_method :dump, :dump_without_rspec_mocks 35 | undef_method :dump_without_rspec_mocks 36 | end 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/rspec/mocks/matchers/expectation_customization.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | module Matchers 4 | # @private 5 | class ExpectationCustomization 6 | attr_accessor :block 7 | 8 | def initialize(method_name, args, block) 9 | @method_name = method_name 10 | @args = args 11 | @block = block 12 | end 13 | 14 | def playback_onto(expectation) 15 | expectation.__send__(@method_name, *@args, &@block) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/rspec/mocks/matchers/receive_message_chain.rb: -------------------------------------------------------------------------------- 1 | RSpec::Support.require_rspec_mocks 'matchers/expectation_customization' 2 | 3 | module RSpec 4 | module Mocks 5 | module Matchers 6 | # @private 7 | class ReceiveMessageChain 8 | include Matcher 9 | 10 | def initialize(chain, &block) 11 | @chain = chain 12 | @block = block 13 | @recorded_customizations = [] 14 | end 15 | 16 | [:with, :and_return, :and_invoke, :and_throw, :and_raise, :and_yield, :and_call_original].each do |msg| 17 | define_method(msg) do |*args, &block| 18 | @recorded_customizations << ExpectationCustomization.new(msg, args, block) 19 | self 20 | end 21 | end 22 | 23 | def matcher_name 24 | "receive_message_chain" 25 | end 26 | 27 | def description 28 | "receive message chain #{formatted_chain}" 29 | end 30 | 31 | def setup_allowance(subject, &block) 32 | chain = StubChain.stub_chain_on(subject, *@chain, &(@block || block)) 33 | replay_customizations(chain) 34 | end 35 | 36 | def setup_any_instance_allowance(subject, &block) 37 | proxy = ::RSpec::Mocks.space.any_instance_proxy_for(subject) 38 | chain = proxy.stub_chain(*@chain, &(@block || block)) 39 | replay_customizations(chain) 40 | end 41 | 42 | def setup_any_instance_expectation(subject, &block) 43 | proxy = ::RSpec::Mocks.space.any_instance_proxy_for(subject) 44 | chain = proxy.expect_chain(*@chain, &(@block || block)) 45 | replay_customizations(chain) 46 | end 47 | 48 | def setup_expectation(subject, &block) 49 | chain = ExpectChain.expect_chain_on(subject, *@chain, &(@block || block)) 50 | replay_customizations(chain) 51 | end 52 | 53 | def setup_negative_expectation(*_args) 54 | raise NegationUnsupportedError, 55 | "`expect(...).not_to receive_message_chain` is not supported " \ 56 | "since it doesn't really make sense. What would it even mean?" 57 | end 58 | 59 | alias matches? setup_expectation 60 | alias does_not_match? setup_negative_expectation 61 | 62 | private 63 | 64 | def replay_customizations(chain) 65 | @recorded_customizations.each do |customization| 66 | customization.playback_onto(chain) 67 | end 68 | end 69 | 70 | def formatted_chain 71 | @formatted_chain ||= @chain.map do |part| 72 | if Hash === part 73 | part.keys.first.to_s 74 | else 75 | part.to_s 76 | end 77 | end.join(".") 78 | end 79 | end 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/rspec/mocks/matchers/receive_messages.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | module Matchers 4 | # @private 5 | class ReceiveMessages 6 | include Matcher 7 | 8 | def initialize(message_return_value_hash) 9 | @message_return_value_hash = message_return_value_hash 10 | @backtrace_line = CallerFilter.first_non_rspec_line 11 | end 12 | 13 | def matcher_name 14 | "receive_messages" 15 | end 16 | 17 | def description 18 | "receive messages: #{@message_return_value_hash.inspect}" 19 | end 20 | 21 | def setup_expectation(subject) 22 | warn_about_block if block_given? 23 | each_message_on(proxy_on(subject)) do |host, message, return_value| 24 | host.add_simple_expectation(message, return_value, @backtrace_line) 25 | end 26 | end 27 | alias matches? setup_expectation 28 | 29 | def setup_negative_expectation(_subject) 30 | raise NegationUnsupportedError, 31 | "`expect(...).to_not receive_messages` is not supported since it " \ 32 | "doesn't really make sense. What would it even mean?" 33 | end 34 | alias does_not_match? setup_negative_expectation 35 | 36 | def setup_allowance(subject) 37 | warn_about_block if block_given? 38 | each_message_on(proxy_on(subject)) do |host, message, return_value| 39 | host.add_simple_stub(message, return_value) 40 | end 41 | end 42 | 43 | def setup_any_instance_expectation(subject) 44 | warn_about_block if block_given? 45 | each_message_on(any_instance_of(subject)) do |host, message, return_value| 46 | host.should_receive(message).and_return(return_value) 47 | end 48 | end 49 | 50 | def setup_any_instance_allowance(subject) 51 | warn_about_block if block_given? 52 | any_instance_of(subject).stub(@message_return_value_hash) 53 | end 54 | 55 | def warn_about_block 56 | raise "Implementation blocks aren't supported with `receive_messages`" 57 | end 58 | 59 | private 60 | 61 | def proxy_on(subject) 62 | ::RSpec::Mocks.space.proxy_for(subject) 63 | end 64 | 65 | def any_instance_of(subject) 66 | ::RSpec::Mocks.space.any_instance_proxy_for(subject) 67 | end 68 | 69 | def each_message_on(host) 70 | @message_return_value_hash.each do |message, value| 71 | yield host, message, value 72 | end 73 | end 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/rspec/mocks/message_chain.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | # @private 4 | class MessageChain 5 | attr_reader :object, :chain, :block 6 | 7 | def initialize(object, *chain, &blk) 8 | @object = object 9 | @chain, @block = format_chain(*chain, &blk) 10 | end 11 | 12 | # @api private 13 | def setup_chain 14 | if chain.length > 1 15 | if (matching_stub = find_matching_stub) 16 | chain.shift 17 | chain_on(matching_stub.invoke(nil), *chain, &@block) 18 | elsif (matching_expectation = find_matching_expectation) 19 | chain.shift 20 | chain_on(matching_expectation.invoke_without_incrementing_received_count(nil), *chain, &@block) 21 | else 22 | next_in_chain = Double.new 23 | expectation(object, chain.shift) { next_in_chain } 24 | chain_on(next_in_chain, *chain, &@block) 25 | end 26 | else 27 | expectation(object, chain.shift, &@block) 28 | end 29 | end 30 | 31 | private 32 | 33 | def chain_on(object, *chain, &block) 34 | initialize(object, *chain, &block) 35 | setup_chain 36 | end 37 | 38 | def format_chain(*chain, &blk) 39 | if Hash === chain.last 40 | hash = chain.pop 41 | hash.each do |k, v| 42 | chain << k 43 | blk = Proc.new { v } 44 | end 45 | end 46 | return chain.join('.').split('.'), blk 47 | end 48 | 49 | def find_matching_stub 50 | ::RSpec::Mocks.space.proxy_for(object). 51 | __send__(:find_matching_method_stub, chain.first.to_sym) 52 | end 53 | 54 | def find_matching_expectation 55 | ::RSpec::Mocks.space.proxy_for(object). 56 | __send__(:find_matching_expectation, chain.first.to_sym) 57 | end 58 | end 59 | 60 | # @private 61 | class ExpectChain < MessageChain 62 | # @api private 63 | def self.expect_chain_on(object, *chain, &blk) 64 | new(object, *chain, &blk).setup_chain 65 | end 66 | 67 | private 68 | 69 | def expectation(object, message, &return_block) 70 | ::RSpec::Mocks.expect_message(object, message, {}, &return_block) 71 | end 72 | end 73 | 74 | # @private 75 | class StubChain < MessageChain 76 | def self.stub_chain_on(object, *chain, &blk) 77 | new(object, *chain, &blk).setup_chain 78 | end 79 | 80 | private 81 | 82 | def expectation(object, message, &return_block) 83 | ::RSpec::Mocks.allow_message(object, message, {}, &return_block) 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/rspec/mocks/minitest_integration.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/mocks' 2 | 3 | module RSpec 4 | module Mocks 5 | # @private 6 | module MinitestIntegration 7 | include ::RSpec::Mocks::ExampleMethods 8 | 9 | def before_setup 10 | ::RSpec::Mocks.setup 11 | super 12 | end 13 | 14 | def after_teardown 15 | super 16 | 17 | # Only verify if there's not already an error. Otherwise 18 | # we risk getting the same failure twice, since negative 19 | # expectation violations raise both when the message is 20 | # unexpectedly received, and also during `verify` (in case 21 | # the first failure was caught by user code via a 22 | # `rescue Exception`). 23 | ::RSpec::Mocks.verify unless failures.any? 24 | ensure 25 | ::RSpec::Mocks.teardown 26 | end 27 | end 28 | end 29 | end 30 | 31 | Minitest::Test.send(:include, RSpec::Mocks::MinitestIntegration) 32 | 33 | if defined?(::Minitest::Expectation) 34 | if defined?(::RSpec::Expectations) && ::Minitest::Expectation.method_defined?(:to) 35 | # rspec/expectations/minitest_integration has already been loaded and 36 | # has defined `to`/`not_to`/`to_not` on `Minitest::Expectation` so we do 37 | # not want to here (or else we would interfere with rspec-expectations' definition). 38 | else 39 | # ...otherwise, define those methods now. If `rspec/expectations/minitest_integration` 40 | # is loaded after this file, it'll override the definition here. 41 | Minitest::Expectation.class_eval do 42 | include RSpec::Mocks::ExpectationTargetMethods 43 | 44 | def to(*args) 45 | ctx.assertions += 1 46 | super 47 | end 48 | 49 | def not_to(*args) 50 | ctx.assertions += 1 51 | super 52 | end 53 | 54 | def to_not(*args) 55 | ctx.assertions += 1 56 | super 57 | end 58 | end 59 | end 60 | end 61 | 62 | module RSpec 63 | module Mocks 64 | remove_const :MockExpectationError 65 | # Raised when a message expectation is not satisfied. 66 | MockExpectationError = ::Minitest::Assertion 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/rspec/mocks/order_group.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | # @private 4 | class OrderGroup 5 | def initialize 6 | @expectations = [] 7 | @invocation_order = [] 8 | @index = 0 9 | end 10 | 11 | # @private 12 | def register(expectation) 13 | @expectations << expectation 14 | end 15 | 16 | def invoked(message) 17 | @invocation_order << message 18 | end 19 | 20 | # @private 21 | def ready_for?(expectation) 22 | remaining_expectations.find(&:ordered?) == expectation 23 | end 24 | 25 | # @private 26 | def consume 27 | remaining_expectations.each_with_index do |expectation, index| 28 | next unless expectation.ordered? 29 | 30 | @index += index + 1 31 | return expectation 32 | end 33 | nil 34 | end 35 | 36 | # @private 37 | def handle_order_constraint(expectation) 38 | return unless expectation.ordered? && remaining_expectations.include?(expectation) 39 | return consume if ready_for?(expectation) 40 | expectation.raise_out_of_order_error 41 | end 42 | 43 | def verify_invocation_order(expectation) 44 | expectation.raise_out_of_order_error unless expectations_invoked_in_order? 45 | true 46 | end 47 | 48 | def clear 49 | @index = 0 50 | @invocation_order.clear 51 | @expectations.clear 52 | end 53 | 54 | def empty? 55 | @expectations.empty? 56 | end 57 | 58 | private 59 | 60 | def remaining_expectations 61 | @expectations[@index..-1] || [] 62 | end 63 | 64 | def expectations_invoked_in_order? 65 | invoked_expectations == expected_invocations 66 | end 67 | 68 | def invoked_expectations 69 | @expectations.select { |e| e.ordered? && @invocation_order.include?(e) } 70 | end 71 | 72 | def expected_invocations 73 | @invocation_order.map { |invocation| expectation_for(invocation) }.compact 74 | end 75 | 76 | def expectation_for(message) 77 | @expectations.find { |e| message == e } 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/rspec/mocks/standalone.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/mocks' 2 | extend RSpec::Mocks::ExampleMethods 3 | RSpec::Mocks.setup 4 | -------------------------------------------------------------------------------- /lib/rspec/mocks/verifying_message_expectation.rb: -------------------------------------------------------------------------------- 1 | RSpec::Support.require_rspec_support 'method_signature_verifier' 2 | 3 | module RSpec 4 | module Mocks 5 | # A message expectation that knows about the real implementation of the 6 | # message being expected, so that it can verify that any expectations 7 | # have the valid arguments. 8 | # @api private 9 | class VerifyingMessageExpectation < MessageExpectation 10 | # A level of indirection is used here rather than just passing in the 11 | # method itself, since method look up is expensive and we only want to 12 | # do it if actually needed. 13 | # 14 | # Conceptually the method reference makes more sense as a constructor 15 | # argument since it should be immutable, but it is significantly more 16 | # straight forward to build the object in pieces so for now it stays as 17 | # an accessor. 18 | attr_accessor :method_reference 19 | 20 | def initialize(*args) 21 | super 22 | end 23 | 24 | # @private 25 | def with(*args, &block) 26 | super(*args, &block).tap do 27 | validate_expected_arguments! do |signature| 28 | example_call_site_args = [:an_arg] * signature.min_non_kw_args 29 | example_call_site_args << :kw_args_hash if signature.required_kw_args.any? 30 | @argument_list_matcher.resolve_expected_args_based_on(example_call_site_args) 31 | end 32 | end 33 | end 34 | ruby2_keywords(:with) if respond_to?(:ruby2_keywords, true) 35 | 36 | private 37 | 38 | def validate_expected_arguments! 39 | return if method_reference.nil? 40 | 41 | method_reference.with_signature do |signature| 42 | args = yield signature 43 | verifier = Support::LooseSignatureVerifier.new(signature, args) 44 | 45 | unless verifier.valid? 46 | # Fail fast is required, otherwise the message expectation will fail 47 | # as well ("expected method not called") and clobber this one. 48 | @failed_fast = true 49 | @error_generator.raise_invalid_arguments_error(verifier) 50 | end 51 | end 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/rspec/mocks/version.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | # Version information for RSpec mocks. 4 | module Version 5 | # Version of RSpec mocks currently in use in SemVer format. 6 | STRING = '3.14.0.pre' 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /maintenance-branch: -------------------------------------------------------------------------------- 1 | main 2 | -------------------------------------------------------------------------------- /rspec-mocks.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $LOAD_PATH.unshift File.expand_path("../lib", __FILE__) 3 | require "rspec/mocks/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "rspec-mocks" 7 | s.version = RSpec::Mocks::Version::STRING 8 | s.platform = Gem::Platform::RUBY 9 | s.license = "MIT" 10 | s.authors = ["Steven Baker", "David Chelimsky", "Myron Marston"] 11 | s.email = "rspec@googlegroups.com" 12 | s.homepage = "https://github.com/rspec/rspec-mocks" 13 | s.summary = "rspec-mocks-#{RSpec::Mocks::Version::STRING}" 14 | s.description = "RSpec's 'test double' framework, with support for stubbing and mocking" 15 | 16 | s.metadata = { 17 | 'bug_tracker_uri' => 'https://github.com/rspec/rspec-mocks/issues', 18 | 'changelog_uri' => "https://github.com/rspec/rspec-mocks/blob/v#{s.version}/Changelog.md", 19 | 'documentation_uri' => 'https://rspec.info/documentation/', 20 | 'mailing_list_uri' => 'https://groups.google.com/forum/#!forum/rspec', 21 | 'source_code_uri' => 'https://github.com/rspec/rspec-mocks', 22 | } 23 | 24 | s.files = `git ls-files -- lib/*`.split("\n") 25 | s.files += %w[README.md LICENSE.md Changelog.md .yardopts .document] 26 | s.test_files = [] 27 | s.rdoc_options = ["--charset=UTF-8"] 28 | s.require_path = "lib" 29 | 30 | s.required_ruby_version = '>= 1.8.7' 31 | 32 | private_key = File.expand_path('~/.gem/rspec-gem-private_key.pem') 33 | if File.exist?(private_key) 34 | s.signing_key = private_key 35 | s.cert_chain = [File.expand_path('~/.gem/rspec-gem-public_cert.pem')] 36 | end 37 | 38 | if RSpec::Mocks::Version::STRING =~ /[a-zA-Z]+/ 39 | # pin to exact version for rc's and betas 40 | s.add_runtime_dependency "rspec-support", "= #{RSpec::Mocks::Version::STRING}" 41 | else 42 | # pin to major/minor ignoring patch 43 | s.add_runtime_dependency "rspec-support", "~> #{RSpec::Mocks::Version::STRING.split('.')[0..1].concat(['0']).join('.')}" 44 | end 45 | 46 | s.add_runtime_dependency "diff-lcs", ">= 1.2.0", "< 2.0" 47 | 48 | s.add_development_dependency 'rake', '> 10.0.0' 49 | s.add_development_dependency 'cucumber', '>= 1.3' 50 | if RUBY_VERSION.to_f >= 2.4 51 | s.add_development_dependency 'aruba', '>= 1.1.0', '< 3.0.0' 52 | else 53 | s.add_development_dependency 'aruba', '~> 0.14.10' 54 | end 55 | s.add_development_dependency 'minitest', '~> 5.2' 56 | end 57 | -------------------------------------------------------------------------------- /script/ci_functions.sh: -------------------------------------------------------------------------------- 1 | # This file was generated on 2024-09-03T15:05:50+01:00 from the rspec-dev repo. 2 | # DO NOT modify it by hand as your changes will get lost the next time it is generated. 3 | 4 | # Taken from: 5 | # https://github.com/travis-ci/travis-build/blob/e9314616e182a23e6a280199cd9070bfc7cae548/lib/travis/build/script/templates/header.sh#L34-L53 6 | ci_retry() { 7 | local result=0 8 | local count=1 9 | while [ $count -le 3 ]; do 10 | [ $result -ne 0 ] && { 11 | echo -e "\n\033[33;1mThe command \"$@\" failed. Retrying, $count of 3.\033[0m\n" >&2 12 | } 13 | "$@" 14 | result=$? 15 | [ $result -eq 0 ] && break 16 | count=$(($count + 1)) 17 | sleep 1 18 | done 19 | 20 | [ $count -eq 3 ] && { 21 | echo "\n\033[33;1mThe command \"$@\" failed 3 times.\033[0m\n" >&2 22 | } 23 | 24 | return $result 25 | } 26 | 27 | # Taken from https://github.com/vcr/vcr/commit/fa96819c92b783ec0c794f788183e170e4f684b2 28 | # and https://github.com/vcr/vcr/commit/040aaac5370c68cd13c847c076749cd547a6f9b1 29 | nano_cmd="$(type -p gdate date | head -1)" 30 | nano_format="+%s%N" 31 | [ "$(uname -s)" != "Darwin" ] || nano_format="${nano_format/%N/000000000}" 32 | 33 | fold() { 34 | local name="$1" 35 | local status=0 36 | shift 1 37 | echo "============= Starting $name ===============" 38 | 39 | "$@" 40 | status=$? 41 | 42 | if [ "$status" -eq 0 ]; then 43 | echo "============= Ending $name ===============" 44 | else 45 | STATUS="$status" 46 | fi 47 | 48 | return $status 49 | } 50 | -------------------------------------------------------------------------------- /script/clone_all_rspec_repos: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file was generated on 2024-09-03T15:05:50+01:00 from the rspec-dev repo. 3 | # DO NOT modify it by hand as your changes will get lost the next time it is generated. 4 | 5 | set -e 6 | source script/functions.sh 7 | 8 | if is_mri; then 9 | pushd .. 10 | 11 | clone_repo "rspec-metagem" "rspec" 12 | clone_repo "rspec-core" 13 | clone_repo "rspec-expectations" 14 | clone_repo "rspec-mocks" 15 | clone_repo "rspec-rails" 16 | 17 | if rspec_support_compatible; then 18 | clone_repo "rspec-support" 19 | fi 20 | 21 | popd 22 | else 23 | echo "Not cloning all repos since we are not on MRI and they are only needed for the MRI build" 24 | fi 25 | -------------------------------------------------------------------------------- /script/cucumber.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file was generated on 2024-09-03T15:05:50+01:00 from the rspec-dev repo. 3 | # DO NOT modify it by hand as your changes will get lost the next time it is generated. 4 | 5 | set -e 6 | source script/functions.sh 7 | 8 | run_cukes 9 | -------------------------------------------------------------------------------- /script/legacy_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file was generated on 2024-09-03T15:05:50+01:00 from the rspec-dev repo. 3 | # DO NOT modify it by hand as your changes will get lost the next time it is generated. 4 | 5 | set -e 6 | source script/functions.sh 7 | 8 | bundle install --standalone --binstubs --without coverage documentation 9 | 10 | if [ -x ./bin/rspec ]; then 11 | echo "RSpec bin detected" 12 | else 13 | if [ -x ./exe/rspec ]; then 14 | cp ./exe/rspec ./bin/rspec 15 | echo "RSpec restored from exe" 16 | else 17 | echo "No RSpec bin available" 18 | exit 1 19 | fi 20 | fi 21 | -------------------------------------------------------------------------------- /script/list_method_cache_busters.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # set -x 3 | 4 | # This list is from https://charlie.bz/blog/things-that-clear-rubys-method-cache 5 | 6 | IGNORE_FILE=/tmp/cache_busters_ignore 7 | COMMENT_LINE_RE="^(\w|\/)+\.rb: +#" 8 | 9 | cat script/ignores | grep -v "^$" | ruby -ne 'puts $_.split(/\s+###/)[0]' > $IGNORE_FILE 10 | 11 | egrep 'def [a-z]*\..*' -R lib | grep -v "def self" | grep -v -f $IGNORE_FILE | egrep -v "$COMMENT_LINE_RE" 12 | egrep 'undef\\b' -R lib | grep -v -f $IGNORE_FILE | egrep -v "$COMMENT_LINE_RE" 13 | grep alias_method -R lib | grep -v -f $IGNORE_FILE | egrep -v "$COMMENT_LINE_RE" 14 | grep remove_method -R lib | grep -v -f $IGNORE_FILE | egrep -v "$COMMENT_LINE_RE" 15 | grep const_set -R lib | grep -v -f $IGNORE_FILE | egrep -v "$COMMENT_LINE_RE" 16 | grep remove_const -R lib | grep -v -f $IGNORE_FILE | egrep -v "$COMMENT_LINE_RE" 17 | egrep '\bextend\b' -R lib | grep -v -f $IGNORE_FILE | egrep -v "$COMMENT_LINE_RE" 18 | grep 'Class.new' -R lib | grep -v -f $IGNORE_FILE | egrep -v "$COMMENT_LINE_RE" 19 | grep private_constant -R lib | grep -v -f $IGNORE_FILE | egrep -v "$COMMENT_LINE_RE" 20 | grep public_constant -R lib | grep -v -f $IGNORE_FILE | egrep -v "$COMMENT_LINE_RE" 21 | grep "Marshal.load" -R lib | grep -v -f $IGNORE_FILE | egrep -v "$COMMENT_LINE_RE" 22 | grep "OpenStruct.new" -R lib | grep -v -f $IGNORE_FILE | egrep -v "$COMMENT_LINE_RE" 23 | -------------------------------------------------------------------------------- /script/predicate_functions.sh: -------------------------------------------------------------------------------- 1 | # This file was generated on 2024-09-03T15:05:50+01:00 from the rspec-dev repo. 2 | # DO NOT modify it by hand as your changes will get lost the next time it is generated. 3 | 4 | function is_mri { 5 | if ruby -e "exit(!defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby')"; then 6 | # RUBY_ENGINE only returns 'ruby' on MRI. 7 | # MRI 1.8.7 lacks the constant but all other rubies have it (including JRuby in 1.8 mode) 8 | return 0 9 | else 10 | return 1 11 | fi; 12 | } 13 | 14 | function is_ruby_head { 15 | # This checks for the presence of our CI's ruby-head env variable 16 | if [ -z ${RUBY_HEAD+x} ]; then 17 | return 1 18 | else 19 | return 0 20 | fi; 21 | } 22 | 23 | function supports_cross_build_checks { 24 | if is_mri; then 25 | # We don't run cross build checks on ruby-head 26 | if is_ruby_head; then 27 | return 1 28 | else 29 | return 0 30 | fi 31 | else 32 | return 1 33 | fi 34 | } 35 | 36 | function is_jruby { 37 | if ruby -e "exit(defined?(RUBY_PLATFORM) && RUBY_PLATFORM == 'java')"; then 38 | # RUBY_ENGINE only returns 'ruby' on MRI. 39 | # MRI 1.8.7 lacks the constant but all other rubies have it (including JRuby in 1.8 mode) 40 | return 0 41 | else 42 | return 1 43 | fi; 44 | } 45 | 46 | function is_mri_192 { 47 | if is_mri; then 48 | if ruby -e "exit(RUBY_VERSION == '1.9.2')"; then 49 | return 0 50 | else 51 | return 1 52 | fi 53 | else 54 | return 1 55 | fi 56 | } 57 | 58 | function is_mri_192_plus { 59 | if is_mri; then 60 | if ruby -e "exit(RUBY_VERSION.to_f > 1.8)"; then 61 | return 0 62 | else 63 | return 1 64 | fi 65 | else 66 | return 1 67 | fi 68 | } 69 | 70 | function is_mri_2plus { 71 | if is_mri; then 72 | if ruby -e "exit(RUBY_VERSION.to_f > 2.0)"; then 73 | return 0 74 | else 75 | return 1 76 | fi 77 | else 78 | return 1 79 | fi 80 | } 81 | 82 | function is_ruby_23_plus { 83 | if ruby -e "exit(RUBY_VERSION.to_f >= 2.3)"; then 84 | return 0 85 | else 86 | return 1 87 | fi 88 | } 89 | 90 | function is_ruby_25_plus { 91 | if ruby -e "exit(RUBY_VERSION.to_f >= 2.5)"; then 92 | return 0 93 | else 94 | return 1 95 | fi 96 | } 97 | 98 | function is_ruby_27_plus { 99 | if ruby -e "exit(RUBY_VERSION.to_f >= 2.7)"; then 100 | return 0 101 | else 102 | return 1 103 | fi 104 | } 105 | 106 | function is_ruby_31_plus { 107 | if ruby -e "exit(RUBY_VERSION.to_f >= 3.1)"; then 108 | return 0 109 | else 110 | return 1 111 | fi 112 | } 113 | 114 | function rspec_rails_compatible { 115 | if is_ruby_27_plus; then 116 | return 0 117 | else 118 | return 1 119 | fi 120 | } 121 | 122 | function rspec_support_compatible { 123 | if [ "$MAINTENANCE_BRANCH" != "2-99-maintenance" ] && [ "$MAINTENANCE_BRANCH" != "2-14-maintenance" ]; then 124 | return 0 125 | else 126 | return 1 127 | fi 128 | } 129 | 130 | function additional_specs_available { 131 | type run_additional_specs > /dev/null 2>&1 132 | return $? 133 | } 134 | 135 | function documentation_enforced { 136 | if [ -x ./bin/yard ]; then 137 | if is_mri_2plus; then 138 | return 0 139 | else 140 | return 1 141 | fi 142 | else 143 | return 1 144 | fi 145 | } 146 | 147 | function style_and_lint_enforced { 148 | if is_ruby_head; then 149 | return 1 150 | else 151 | if [ -x ./bin/rubocop ]; then 152 | return 0 153 | else 154 | return 1 155 | fi 156 | fi 157 | } 158 | -------------------------------------------------------------------------------- /script/run_build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file was generated on 2024-09-03T15:05:50+01:00 from the rspec-dev repo. 3 | # DO NOT modify it by hand as your changes will get lost the next time it is generated. 4 | 5 | set -e 6 | source script/functions.sh 7 | 8 | # Allow repos to override the default functions and add their own 9 | if [ -f script/custom_build_functions.sh ]; then 10 | source script/custom_build_functions.sh 11 | fi 12 | 13 | fold "binstub check" check_binstubs 14 | 15 | fold "specs" run_specs_and_record_done 16 | 17 | if additional_specs_available; then 18 | fold "additional specs" run_additional_specs 19 | fi 20 | 21 | fold "cukes" run_cukes 22 | 23 | if documentation_enforced; then 24 | fold "doc check" check_documentation_coverage 25 | fi 26 | 27 | if supports_cross_build_checks; then 28 | fold "one-by-one specs" run_specs_one_by_one 29 | export NO_COVERAGE=true 30 | run_all_spec_suites 31 | else 32 | echo "Skipping the rest of the build on non-MRI rubies" 33 | fi 34 | -------------------------------------------------------------------------------- /script/run_rubocop: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file was generated on 2024-09-03T15:05:50+01:00 from the rspec-dev repo. 3 | # DO NOT modify it by hand as your changes will get lost the next time it is generated. 4 | 5 | set -e 6 | source script/functions.sh 7 | 8 | # Allow repos to override the default functions and add their own 9 | if [ -f script/custom_build_functions.sh ]; then 10 | source script/custom_build_functions.sh 11 | fi 12 | 13 | 14 | fold "rubocop" check_style_and_lint 15 | -------------------------------------------------------------------------------- /script/update_rubygems_and_install_bundler: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file was generated on 2024-09-03T15:05:50+01:00 from the rspec-dev repo. 3 | # DO NOT modify it by hand as your changes will get lost the next time it is generated. 4 | 5 | set -e 6 | source script/functions.sh 7 | 8 | if is_ruby_31_plus; then 9 | echo "Installing most recent rubygems / bundler" 10 | yes | gem update --no-document --system 11 | yes | gem install --no-document bundler 12 | elif is_ruby_23_plus; then 13 | echo "Installing rubygems 3.2.22 / bundler 2.2.22" 14 | yes | gem update --system '3.2.22' 15 | yes | gem install bundler -v '2.2.22' 16 | else 17 | echo "Warning installing older versions of Rubygems / Bundler" 18 | gem update --system '2.7.8' 19 | gem install bundler -v '1.17.3' 20 | fi 21 | -------------------------------------------------------------------------------- /spec/integration/rails_support_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../support/aruba', __FILE__) 2 | 3 | RSpec.describe "Supporting Rails monkey patches", :type => :aruba do 4 | before do 5 | if RSpec::Support::OS.windows? && RUBY_VERSION.to_f < 2.4 6 | skip "Aruba on windows is broken on Ruby 2.3 and below" 7 | end 8 | end 9 | 10 | it "works when Rails has monkey patched #with" do 11 | write_file( 12 | "spec/with_monkey_patch_spec.rb", 13 | """ 14 | class Object 15 | # Rails monkey patches in **kwargs but this is a good analogy 16 | def with 17 | end 18 | end 19 | 20 | RSpec.describe do 21 | specify do 22 | mock = instance_double(\"Hash\") 23 | allow(mock).to receive(:key?).with(:x) { 1 } 24 | 25 | expect(mock.key?(:x)).to eq 1 26 | end 27 | end 28 | """ 29 | ) 30 | 31 | run_command("bundle exec rspec spec/with_monkey_patch_spec.rb") 32 | 33 | expect(last_command_started).to have_output(/0 failures/) 34 | expect(last_command_started).to have_exit_status(0) 35 | end 36 | 37 | it "works mocking any instance when Rails has monkey patched #with" do 38 | write_file( 39 | "spec/with_monkey_patch_spec.rb", 40 | """ 41 | class Object 42 | # Rails monkey patches in **kwargs but this is a good analogy 43 | def with 44 | end 45 | end 46 | 47 | RSpec.describe do 48 | specify do 49 | klass = Class.new 50 | allow_any_instance_of(klass).to receive(:bar).with(:y) { 2 } 51 | 52 | expect(klass.new.bar(:y)).to eq 2 53 | end 54 | end 55 | """ 56 | ) 57 | 58 | run_command("bundle exec rspec spec/with_monkey_patch_spec.rb") 59 | 60 | expect(last_command_started).to have_output(/0 failures/) 61 | expect(last_command_started).to have_exit_status(0) 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/rspec/mocks/and_invoke_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe 'and_invoke' do 4 | let(:obj) { double('obj') } 5 | 6 | context 'when a block is passed' do 7 | it 'raises ArgumentError' do 8 | expect { 9 | allow(obj).to receive(:foo).and_invoke('bar') { 'baz' } 10 | }.to raise_error(ArgumentError, /implementation block/i) 11 | end 12 | end 13 | 14 | context 'when no argument is passed' do 15 | it 'raises ArgumentError' do 16 | expect { allow(obj).to receive(:foo).and_invoke }.to raise_error(ArgumentError) 17 | end 18 | end 19 | 20 | context 'when a non-callable are passed in any position' do 21 | let(:non_callable) { nil } 22 | let(:callable) { lambda { nil } } 23 | 24 | it 'raises ArgumentError' do 25 | error = [ArgumentError, "Arguments to `and_invoke` must be callable."] 26 | 27 | expect { allow(obj).to receive(:foo).and_invoke(non_callable) }.to raise_error(*error) 28 | expect { allow(obj).to receive(:foo).and_invoke(callable, non_callable) }.to raise_error(*error) 29 | end 30 | end 31 | 32 | context 'when calling passed callables' do 33 | let(:dbl) { double } 34 | 35 | it 'passes the arguments into the callable' do 36 | expect(dbl).to receive(:square_then_cube).and_invoke(lambda { |i| i ** 2 }, 37 | lambda { |i| i ** 3 }) 38 | 39 | expect(dbl.square_then_cube(2)).to eq 4 40 | expect(dbl.square_then_cube(2)).to eq 8 41 | end 42 | 43 | if RSpec::Support::RubyFeatures.kw_args_supported? 44 | binding.eval(<<-RUBY, __FILE__, __LINE__) 45 | it 'passes keyword arguments into the callable' do 46 | expect(dbl).to receive(:square_then_cube).and_invoke(lambda { |i: 1| i ** 2 }, 47 | lambda { |i: 1| i ** 3 }) 48 | 49 | expect(dbl.square_then_cube(i: 2)).to eq 4 50 | expect(dbl.square_then_cube(i: 2)).to eq 8 51 | end 52 | RUBY 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/rspec/mocks/and_return_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe 'and_return' do 4 | let(:obj) { double('obj') } 5 | 6 | context 'when a block is passed' do 7 | it 'raises ArgumentError' do 8 | expect { 9 | allow(obj).to receive(:foo).and_return('bar') { 'baz' } 10 | }.to raise_error(ArgumentError, /implementation block/i) 11 | end 12 | end 13 | 14 | context 'when no argument is passed' do 15 | it 'raises ArgumentError' do 16 | expect { allow(obj).to receive(:foo).and_return }.to raise_error(ArgumentError) 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/rspec/mocks/any_instance/message_chains_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe RSpec::Mocks::AnyInstance::MessageChains do 2 | let(:recorder) { double } 3 | let(:chains) { RSpec::Mocks::AnyInstance::MessageChains.new } 4 | let(:stub_chain) { RSpec::Mocks::AnyInstance::StubChain.new recorder } 5 | let(:expectation_chain) { RSpec::Mocks::AnyInstance::PositiveExpectationChain.new recorder } 6 | 7 | it "knows if a method does not have an expectation set on it" do 8 | chains.add(:method_name, stub_chain) 9 | expect(chains.has_expectation?(:method_name)).to be_falsey 10 | end 11 | 12 | it "knows if a method has an expectation set on it" do 13 | chains.add(:method_name, stub_chain) 14 | chains.add(:method_name, expectation_chain) 15 | expect(chains.has_expectation?(:method_name)).to be_truthy 16 | end 17 | 18 | it "can remove all stub chains" do 19 | chains.add(:method_name, stub_chain) 20 | chains.add(:method_name, expectation_chain) 21 | chains.add(:method_name, RSpec::Mocks::AnyInstance::StubChain.new(recorder)) 22 | 23 | chains.remove_stub_chains_for!(:method_name) 24 | expect(chains[:method_name]).to eq([expectation_chain]) 25 | end 26 | 27 | context "creating stub chains" do 28 | it "understands how to add a stub chain for a method" do 29 | chains.add(:method_name, stub_chain) 30 | expect(chains[:method_name]).to eq([stub_chain]) 31 | end 32 | 33 | it "allows multiple stub chains for a method" do 34 | chains.add(:method_name, stub_chain) 35 | chains.add(:method_name, another_stub_chain = RSpec::Mocks::AnyInstance::StubChain.new(recorder)) 36 | expect(chains[:method_name]).to eq([stub_chain, another_stub_chain]) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/rspec/mocks/array_including_matcher_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | module ArgumentMatchers 4 | RSpec.describe ArrayIncludingMatcher do 5 | it "describes itself properly" do 6 | expect(ArrayIncludingMatcher.new([1, 2, 3]).description).to eq "array_including(1, 2, 3)" 7 | end 8 | 9 | it "describes passed matchers" do 10 | description = array_including(fake_matcher(Object.new)).description 11 | 12 | expect(description).to include(MatcherHelpers.fake_matcher_description) 13 | end 14 | 15 | context "passing" do 16 | it "matches the same array" do 17 | expect(array_including([1, 2, 3])).to be === [1, 2, 3] 18 | end 19 | 20 | it "matches the same array, specified without square brackets" do 21 | expect(array_including(1, 2, 3)).to be === [1, 2, 3] 22 | end 23 | 24 | it "matches the same array, specified without square brackets" do 25 | expect(array_including(1, 2, 3)).to be === [1, 2, 3] 26 | end 27 | 28 | it "matches the same array, which includes nested arrays" do 29 | expect(array_including([1, 2], 3, 4)).to be === [[1, 2], 3, 4] 30 | end 31 | 32 | it "works with duplicates in expected" do 33 | expect(array_including(1, 1, 2, 3)).to be === [1, 2, 3] 34 | end 35 | 36 | it "works with duplicates in actual" do 37 | expect(array_including(1, 2, 3)).to be === [1, 1, 2, 3] 38 | end 39 | 40 | it "is composable with other matchers" do 41 | klass = Class.new 42 | dbl = double 43 | expect(dbl).to receive(:a_message).with(3, array_including(instance_of(klass))) 44 | dbl.a_message(3, [1, klass.new, 4]) 45 | end 46 | 47 | # regression check 48 | it "is composable when nested" do 49 | expect(array_including(1, array_including(2, 3), 4)).to be === [1, [2, 3], 4] 50 | expect([[1, 2], 3, 4]).to match array_including(array_including(1, 2), 3, 4) 51 | expect([1,[1,2]]).to match array_including(1, array_including(1,2)) 52 | end 53 | end 54 | 55 | context "failing" do 56 | it "fails when not all the entries in the expected are present" do 57 | expect(array_including(1, 2, 3, 4, 5)).not_to be === [1, 2] 58 | end 59 | 60 | it "fails when passed a composed matcher is passed and not satisfied" do 61 | with_unfulfilled_double do |dbl| 62 | expect { 63 | klass = Class.new 64 | expect(dbl).to receive(:a_message).with(3, array_including(instance_of(klass))) 65 | dbl.a_message(3, [1, 4]) 66 | }.to fail_with(/expected: \(3, array_including\(an_instance_of\(\)\)\)/) 67 | end 68 | end 69 | end 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/rspec/mocks/before_all_spec.rb: -------------------------------------------------------------------------------- 1 | require 'support/before_all_shared_example_group' 2 | 3 | RSpec.describe "Using rspec-mocks features in before(:all) blocks" do 4 | describe "#stub_const" do 5 | include_examples "fails in a before(:all) block" do 6 | def use_rspec_mocks 7 | stub_const("SomeNewConst", Class.new) 8 | end 9 | 10 | it 'does not stub the const' do 11 | expect(defined?(SomeNewConst)).to be_falsey 12 | end 13 | end 14 | end 15 | 16 | describe "#hide_const(for an undefined const)" do 17 | include_examples "fails in a before(:all) block" do 18 | def use_rspec_mocks 19 | hide_const("Foo") 20 | end 21 | end 22 | end 23 | 24 | describe "#hide_const(for a defined const)" do 25 | include_examples "fails in a before(:all) block" do 26 | def use_rspec_mocks 27 | hide_const("Float") 28 | end 29 | 30 | it 'does not hide the const' do 31 | expect(defined?(Float)).to be_truthy 32 | end 33 | end 34 | end 35 | 36 | describe "allow(...).to receive_message_chain" do 37 | include_examples "fails in a before(:all) block" do 38 | def use_rspec_mocks 39 | allow(Object).to receive_message_chain(:foo, :bar) 40 | end 41 | end 42 | end 43 | 44 | describe "#expect(...).to receive" do 45 | include_examples "fails in a before(:all) block" do 46 | def use_rspec_mocks 47 | expect(Object).to receive(:foo) 48 | end 49 | end 50 | end 51 | 52 | describe "#allow(...).to receive" do 53 | include_examples "fails in a before(:all) block" do 54 | def use_rspec_mocks 55 | allow(Object).to receive(:foo) 56 | end 57 | end 58 | end 59 | 60 | describe "#expect_any_instance_of(...).to receive" do 61 | include_examples "fails in a before(:all) block" do 62 | def use_rspec_mocks 63 | expect_any_instance_of(Object).to receive(:foo) 64 | end 65 | end 66 | end 67 | 68 | describe "#allow_any_instance_of(...).to receive" do 69 | include_examples "fails in a before(:all) block" do 70 | def use_rspec_mocks 71 | allow_any_instance_of(Object).to receive(:foo) 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/rspec/mocks/block_return_value_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "a double declaration with a block handed to:" do 2 | describe "expect(...).to receive" do 3 | it "returns the value of executing the block" do 4 | obj = Object.new 5 | expect(obj).to receive(:foo) { 'bar' } 6 | expect(obj.foo).to eq('bar') 7 | end 8 | 9 | it "works when a multi-return stub has already been set" do 10 | obj = Object.new 11 | return_value = Object.new 12 | allow(obj).to receive(:foo).and_return(return_value, nil) 13 | expect(obj).to receive(:foo) { return_value } 14 | expect(obj.foo).to be(return_value) 15 | end 16 | end 17 | 18 | describe "allow(...).to receive" do 19 | it "returns the value of executing the block" do 20 | obj = Object.new 21 | allow(obj).to receive(:foo) { 'bar' } 22 | expect(obj.foo).to eq('bar') 23 | end 24 | 25 | # The "receives a block" part is important: 1.8.7 has a bug that reports the 26 | # wrong arity when a block receives a block. 27 | it 'forwards all given args to the block, even when it receives a block' do 28 | obj = Object.new 29 | yielded_args = [] 30 | allow(obj).to receive(:foo) { |*args, &_| yielded_args << args } 31 | obj.foo(1, 2, 3) 32 | 33 | expect(yielded_args).to eq([[1, 2, 3]]) 34 | end 35 | end 36 | 37 | describe "with" do 38 | it "returns the value of executing the block" do 39 | obj = Object.new 40 | allow(obj).to receive(:foo).with('baz') { 'bar' } 41 | expect(obj.foo('baz')).to eq('bar') 42 | end 43 | 44 | it "returns the value of executing the block with given argument" do 45 | obj = Object.new 46 | allow(obj).to receive(:foo).with('baz') { |x| 'bar' + x } 47 | expect(obj.foo('baz')).to eq('barbaz') 48 | end 49 | end 50 | 51 | %w[once twice].each do |method| 52 | describe method do 53 | it "returns the value of executing the block" do 54 | obj = Object.new 55 | allow(obj).to receive(:foo).send(method) { 'bar' } 56 | expect(obj.foo).to eq('bar') 57 | end 58 | end 59 | end 60 | 61 | describe 'ordered' do 62 | it "returns the value of executing the block" do 63 | obj = Object.new 64 | expect_warning_with_call_site(__FILE__, __LINE__ + 1) 65 | allow(obj).to receive(:foo).ordered { 'bar' } 66 | expect(obj.foo).to eq('bar') 67 | end 68 | end 69 | 70 | describe "times" do 71 | it "returns the value of executing the block" do 72 | obj = Object.new 73 | allow(obj).to receive(:foo).at_least(1).time { 'bar' } 74 | expect(obj.foo('baz')).to eq('bar') 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /spec/rspec/mocks/error_generator_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module RSpec 4 | module Mocks 5 | RSpec.describe ErrorGenerator do 6 | context "when inserting a backtrace line" do 7 | def has_java_frames? 8 | yield 9 | rescue RSpec::Mocks::MockExpectationError => e 10 | e.backtrace.grep(/\.java:/).any? 11 | else 12 | raise "got no exception" 13 | end 14 | 15 | it "produces stacktraces that match how `raise` produces stacktraces (on JRuby `caller` and `raise` can differ about the presence of java frames)" do 16 | raise_has_java_frames = has_java_frames? { raise RSpec::Mocks::MockExpectationError } 17 | 18 | eg_has_java_frames = has_java_frames? do 19 | ErrorGenerator.new.send(:__raise, "message", "foo.rb:1") 20 | end 21 | 22 | expect(raise_has_java_frames).to eq eg_has_java_frames 23 | end 24 | end 25 | 26 | def unexpected_failure_message_for(object_description) 27 | /received unexpected message :bees with \(#{object_description}\)/ 28 | end 29 | 30 | describe "formatting arguments" do 31 | it 'formats time objects with increased precision' do 32 | time = Time.utc(1969, 12, 31, 19, 01, 40, 101) 33 | expected_output = "1969-12-31 19:01:40.000101" 34 | 35 | o = double(:double) 36 | expect { 37 | o.bees(time) 38 | }.to fail_including(expected_output) 39 | end 40 | 41 | context "on non-matcher objects that define #description" do 42 | it "does not use the object's description" do 43 | o = double(:double, :description => "Friends") 44 | expect { 45 | o.bees(o) 46 | }.to fail_with(unexpected_failure_message_for(o.inspect)) 47 | end 48 | end 49 | 50 | context "on matcher objects" do 51 | context "that define description" do 52 | it "uses the object's description" do 53 | d = double(:double) 54 | o = fake_matcher(Object.new) 55 | expect { 56 | d.bees(o) 57 | }.to raise_error(unexpected_failure_message_for(o.description)) 58 | end 59 | end 60 | 61 | context "that do not define description" do 62 | it "does not use the object's description" do 63 | d = double(:double) 64 | o = Class.new do 65 | def self.name 66 | "RSpec::Mocks::ArgumentMatchers::" 67 | end 68 | end.new 69 | 70 | expect(RSpec::Support.is_a_matcher?(o)).to be true 71 | 72 | expect { 73 | d.bees(o) 74 | }.to fail_with(unexpected_failure_message_for(o.inspect)) 75 | end 76 | end 77 | 78 | context "on default method stub" do 79 | it "error message display starts in new line" do 80 | d = double(:double) 81 | allow(d).to receive(:foo).with({}) 82 | expect { d.foo([]) }.to fail_with(/\nDiff/) 83 | end 84 | end 85 | end 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/rspec/mocks/example_methods_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe ExampleMethods do 4 | it 'does not define private helper methods since it gets included into a ' \ 5 | 'namespace where users define methods and could inadvertently overwrite ' \ 6 | 'them' do 7 | expect(ExampleMethods.private_instance_methods).to eq([]) 8 | end 9 | 10 | def test_extend_on_new_object(*to_extend, &block) 11 | host = Object.new 12 | to_extend.each { |mod| host.extend mod } 13 | host.instance_eval do 14 | dbl = double 15 | expect(dbl).to receive(:foo).at_least(:once).and_return(1) 16 | dbl.foo 17 | instance_exec(dbl, &block) if block 18 | end 19 | end 20 | 21 | it 'works properly when extended onto an object' do 22 | test_extend_on_new_object ExampleMethods 23 | end 24 | 25 | it 'works properly when extended onto an object that has previous extended `RSpec::Matchers`' do 26 | test_extend_on_new_object RSpec::Matchers, ExampleMethods do |dbl| 27 | expect(dbl.foo).to eq(1) 28 | end 29 | end 30 | 31 | it 'works properly when extended onto an object that later extends `RSpec::Matchers`' do 32 | test_extend_on_new_object ExampleMethods, RSpec::Matchers do |dbl| 33 | expect(dbl.foo).to eq(1) 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/rspec/mocks/expiration_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe "After a test double has been torn down" do 4 | RSpec.shared_examples_for "expiration" do 5 | before do 6 | expect(dbl).to receive(:foo).at_least(:once) 7 | allow(dbl).to receive(:bar) 8 | dbl.foo 9 | 10 | RSpec::Mocks.verify 11 | RSpec::Mocks.teardown 12 | RSpec::Mocks.setup 13 | end 14 | 15 | it 'disallows previously mocked methods' do 16 | expect { dbl.foo }.to raise_error(ExpiredTestDoubleError) 17 | end 18 | 19 | it 'disallows previously stubbed methods' do 20 | expect { dbl.bar }.to raise_error(ExpiredTestDoubleError) 21 | end 22 | 23 | it 'disallows stubbing new methods (with receive)' do 24 | expect { 25 | allow(dbl).to receive(:bazz) 26 | }.to raise_error(ExpiredTestDoubleError) 27 | end 28 | 29 | it 'disallows stubbing new methods (with receive_messages)' do 30 | expect { 31 | allow(dbl).to receive_messages(:bazz => 3) 32 | }.to raise_error(ExpiredTestDoubleError) 33 | end 34 | 35 | it 'disallows stubbing new message chains' do 36 | expect { 37 | allow(dbl).to receive_message_chain(:bazz, :bam, :goo) 38 | }.to raise_error(ExpiredTestDoubleError) 39 | end 40 | 41 | it 'disallows mocking new methods' do 42 | expect { 43 | expect(dbl).to receive(:bazz) 44 | }.to raise_error(ExpiredTestDoubleError) 45 | end 46 | 47 | it 'disallows being turned into a null object' do 48 | expect { dbl.as_null_object }.to raise_error(ExpiredTestDoubleError) 49 | end 50 | 51 | it 'disallows being checked for nullness' do 52 | expect { dbl.null_object? }.to raise_error(ExpiredTestDoubleError) 53 | end 54 | 55 | end 56 | 57 | context "for a plain double" do 58 | let(:dbl) { double } 59 | include_examples "expiration" 60 | end 61 | 62 | class ExpiredInstanceInterface 63 | def foo; end 64 | def bar; end 65 | def bazz; end 66 | end 67 | 68 | class ExpiredClassInterface 69 | def self.foo; end 70 | def self.bar; end 71 | def self.bazz; end 72 | end 73 | 74 | context "for an instance_double" do 75 | let(:dbl) { instance_double(ExpiredInstanceInterface) } 76 | include_examples "expiration" 77 | end 78 | 79 | context "for a class_double" do 80 | let(:dbl) { class_double(ExpiredClassInterface) } 81 | include_examples "expiration" 82 | end 83 | 84 | context "for an object_double" do 85 | let(:dbl) { object_double(ExpiredInstanceInterface.new) } 86 | include_examples "expiration" 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/rspec/mocks/failure_notification_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Failure notification" do 2 | def capture_errors(&block) 3 | errors = [] 4 | RSpec::Support.with_failure_notifier(Proc.new { |e, _opts| errors << e }, &block) 5 | errors 6 | end 7 | 8 | it "uses the rspec-support notifier to support `aggregate_failures`" do 9 | dbl = double("Foo") 10 | 11 | expect(capture_errors { dbl.some_unallowed_method }).to match [an_object_having_attributes( 12 | :message => a_string_including(dbl.inspect, "some_unallowed_method") 13 | )] 14 | end 15 | 16 | it "includes the line of future expectation in the notification for an unreceived message" do 17 | dbl = double("Foo") 18 | expect(dbl).to receive(:wont_happen); expected_from_line = __LINE__ 19 | 20 | error = capture_errors { verify dbl }.first 21 | expect(error.backtrace.first).to match(/#{File.basename(__FILE__)}:#{expected_from_line}/) 22 | end 23 | 24 | it "does not allow a double to miscount the number of times a message was received when a failure is notified in an alternate way" do 25 | dbl = double("Foo") 26 | expect(dbl).not_to receive(:bar) 27 | 28 | capture_errors { dbl.bar } 29 | 30 | expect { verify dbl }.to fail_including("expected: 0 times", "received: 1 time") 31 | end 32 | 33 | context "when using `aggregate_failures`" do 34 | specify 'spy failures for unreceived messages are reported correctly' do 35 | expect { 36 | aggregate_failures do 37 | expect(spy).to have_received(:foo) 38 | end 39 | }.to raise_error(RSpec::Expectations::ExpectationNotMetError) do |e| 40 | expect(e).not_to be_a(RSpec::Expectations::MultipleExpectationsNotMetError) 41 | expect(e.message).to include("expected: 1 time", "received: 0 times") 42 | end 43 | end 44 | 45 | specify 'spy failures for messages received with unexpected args are reported correctly' do 46 | expect { 47 | aggregate_failures do 48 | the_spy = spy 49 | the_spy.foo(1) 50 | expect(the_spy).to have_received(:foo).with(2) 51 | end 52 | }.to raise_error(RSpec::Expectations::ExpectationNotMetError) do |e| 53 | expect(e).not_to be_a(RSpec::Expectations::MultipleExpectationsNotMetError) 54 | expect(e.message).to include("expected: (2)", "got: (1)") 55 | end 56 | end 57 | 58 | specify "failing negative expectations are only notified once" do 59 | expect { 60 | aggregate_failures do 61 | dbl = double 62 | 63 | expect(dbl).not_to receive(:foo) 64 | expect(dbl).not_to receive(:bar) 65 | 66 | dbl.foo 67 | dbl.bar 68 | 69 | verify_all 70 | end 71 | }.to raise_error(RSpec::Expectations::MultipleExpectationsNotMetError) do |e| 72 | expect(e.failures.count).to eq(2) 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/rspec/mocks/hash_excluding_matcher_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | module ArgumentMatchers 4 | RSpec.describe HashExcludingMatcher do 5 | 6 | it "describes itself properly" do 7 | message = 8 | if RUBY_VERSION.to_f > 3.3 9 | "hash_not_including(a: 5)" 10 | else 11 | "hash_not_including(:a=>5)" 12 | end 13 | 14 | expect(HashExcludingMatcher.new(:a => 5).description).to eq message 15 | end 16 | 17 | describe "passing" do 18 | it "matches a hash without the specified key" do 19 | expect(hash_not_including(:c)).to be === {:a => 1, :b => 2} 20 | end 21 | 22 | it "matches a hash with the specified key, but different value" do 23 | expect(hash_not_including(:b => 3)).to be === {:a => 1, :b => 2} 24 | end 25 | 26 | it "matches a hash without the specified key, given as anything()" do 27 | expect(hash_not_including(:c => anything)).to be === {:a => 1, :b => 2} 28 | end 29 | 30 | it "matches an empty hash" do 31 | expect(hash_not_including(:a)).to be === {} 32 | end 33 | 34 | it "matches a hash without any of the specified keys" do 35 | expect(hash_not_including(:a, :b, :c)).to be === { :d => 7 } 36 | end 37 | 38 | it "matches against classes inheriting from Hash" do 39 | expect(hash_not_including(Class.new(Hash)[:c, 1])).not_to be === {:c => 1} 40 | end 41 | end 42 | 43 | describe "failing" do 44 | it "does not match a non-hash" do 45 | expect(hash_not_including(:a => 1)).not_to be === 1 46 | end 47 | 48 | it "does not match a hash with a specified key" do 49 | expect(hash_not_including(:b)).not_to be === { :b => 2 } 50 | end 51 | 52 | it "does not match a hash with the specified key/value pair" do 53 | expect(hash_not_including(:b => 2)).not_to be === { :a => 1, :b => 2 } 54 | end 55 | 56 | it "does not match a hash with the specified key" do 57 | expect(hash_not_including(:a, :b => 3)).not_to be === { :a => 1, :b => 2 } 58 | end 59 | 60 | it "does not match a hash with one of the specified keys" do 61 | expect(hash_not_including(:a, :b)).not_to be === { :b => 2 } 62 | end 63 | 64 | it "does not match a hash with some of the specified keys" do 65 | expect(hash_not_including(:a, :b, :c)).not_to be === { :a => 1, :b => 2 } 66 | end 67 | 68 | it "does not match a hash with one key/value pair included" do 69 | expect(hash_not_including(:a, :b, :c, :d => 7)).not_to be === { :d => 7 } 70 | end 71 | end 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/rspec/mocks/instance_method_stasher_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe InstanceMethodStasher do 4 | class ExampleClass 5 | def hello 6 | :hello_defined_on_class 7 | end 8 | end 9 | 10 | def singleton_class_for(obj) 11 | class << obj; self; end 12 | end 13 | 14 | def stasher_for(obj, method_name) 15 | InstanceMethodStasher.new(obj, method_name) 16 | end 17 | 18 | it "stashes the current implementation of an instance method so it can be temporarily replaced" do 19 | obj = Object.new 20 | def obj.hello; :hello_defined_on_singleton_class; end; 21 | 22 | stashed_method = stasher_for(obj, :hello) 23 | stashed_method.stash 24 | 25 | with_isolated_stderr { def obj.hello; :overridden_hello; end } 26 | expect(obj.hello).to eql :overridden_hello 27 | 28 | stashed_method.restore 29 | expect(obj.hello).to eql :hello_defined_on_singleton_class 30 | end 31 | 32 | it "stashes private instance methods" do 33 | obj = Object.new 34 | def obj.hello; :hello_defined_on_singleton_class; end; 35 | singleton_class_for(obj).__send__(:private, :hello) 36 | 37 | stashed_method = stasher_for(obj, :hello) 38 | stashed_method.stash 39 | 40 | with_isolated_stderr { def obj.hello; :overridden_hello; end } 41 | stashed_method.restore 42 | expect(obj.send(:hello)).to eql :hello_defined_on_singleton_class 43 | end 44 | 45 | it "only stashes methods directly defined on the given class, not its ancestors" do 46 | obj = ExampleClass.new 47 | 48 | stashed_method = stasher_for(obj, :hello) 49 | stashed_method.stash 50 | 51 | def obj.hello; :overridden_hello; end; 52 | expect(obj.hello).to eql :overridden_hello 53 | 54 | stashed_method.restore 55 | expect(obj.hello).to eql :overridden_hello 56 | end 57 | 58 | it "does not unnecessarily create obfuscated aliased methods", :if => (RUBY_VERSION.to_f > 1.8) do 59 | obj = Object.new 60 | def obj.hello; :hello_defined_on_singleton_class; end; 61 | 62 | stashed_method = stasher_for(obj, :hello) 63 | stashed_method.stash 64 | expect(obj.methods.grep(/rspec/)).to eq([]) 65 | end 66 | 67 | it "undefines the original method", :if => (RUBY_VERSION.to_f > 1.8) do 68 | obj = Object.new 69 | def obj.hello; :hello_defined_on_singleton_class; end; 70 | 71 | stashed_method = stasher_for(obj, :hello) 72 | stashed_method.stash 73 | 74 | expect(obj.methods).not_to include(:hello) 75 | expect(obj).not_to respond_to(:hello) 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /spec/rspec/mocks/marshal_extension_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Marshal, 'extensions' do 2 | # An object that raises when code attempts to dup it. 3 | # 4 | # Because we manipulate the internals of RSpec::Mocks.space below, we need 5 | # an object that simply blows up when #dup is called without using any 6 | # partial mocking or stubbing from rspec-mocks itself. 7 | class UndupableObject 8 | def dup 9 | raise NotImplementedError 10 | end 11 | end 12 | 13 | describe '#dump' do 14 | context 'when rspec-mocks has been fully initialized' do 15 | include_context "with monkey-patched marshal" 16 | 17 | it 'duplicates objects with stubbed or mocked implementations before serialization' do 18 | obj = double(:foo => "bar") 19 | 20 | serialized = Marshal.dump(obj) 21 | expect(Marshal.load(serialized)).to be_an(obj.class) 22 | end 23 | 24 | it 'does not duplicate other objects before serialization' do 25 | obj = UndupableObject.new 26 | 27 | serialized = Marshal.dump(obj) 28 | expect(Marshal.load(serialized)).to be_an(UndupableObject) 29 | end 30 | 31 | it 'does not duplicate nil before serialization' do 32 | serialized = Marshal.dump(nil) 33 | expect(Marshal.load(serialized)).to be_nil 34 | end 35 | 36 | specify 'applying and unapplying patch is idempotent' do 37 | obj = double(:foo => "bar") 38 | config = RSpec::Mocks.configuration 39 | 40 | config.patch_marshal_to_support_partial_doubles = true 41 | config.patch_marshal_to_support_partial_doubles = true 42 | serialized = Marshal.dump(obj) 43 | expect(Marshal.load(serialized)).to be_an(obj.class) 44 | config.patch_marshal_to_support_partial_doubles = false 45 | config.patch_marshal_to_support_partial_doubles = false 46 | expect { Marshal.dump(obj) }.to raise_error(TypeError) 47 | end 48 | end 49 | 50 | context 'outside the per-test lifecycle' do 51 | def outside_per_test_lifecycle 52 | RSpec::Mocks.teardown 53 | yield 54 | ensure 55 | RSpec::Mocks.setup 56 | end 57 | 58 | it 'does not duplicate the object before serialization' do 59 | obj = UndupableObject.new 60 | outside_per_test_lifecycle do 61 | serialized = Marshal.dump(obj) 62 | expect(Marshal.load(serialized)).to be_an(UndupableObject) 63 | end 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/rspec/mocks/message_expectation_string_representation_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe MessageExpectation, "has a nice string representation" do 4 | let(:test_double) { double } 5 | 6 | example "for a raw message expectation on a test double" do 7 | expect(allow(test_double).to receive(:foo)).to have_string_representation( 8 | "#.foo(any arguments)>" 9 | ) 10 | end 11 | 12 | example "for a raw message expectation on a partial double" do 13 | expect(allow("partial double".dup).to receive(:foo)).to have_string_representation( 14 | '#' 15 | ) 16 | end 17 | 18 | example "for a message expectation constrained by `with`" do 19 | expect(allow(test_double).to receive(:foo).with(1, a_kind_of(String), any_args)).to have_string_representation( 20 | "#.foo(1, a kind of String, *(any args))>" 21 | ) 22 | end 23 | 24 | RSpec::Matchers.define :have_string_representation do |expected_representation| 25 | match do |object| 26 | values_match?(expected_representation, object.to_s) && object.to_s == object.inspect 27 | end 28 | 29 | failure_message do |object| 30 | "expected string representation: #{expected_representation}\n" \ 31 | " but got string representation: #{object.to_s}" 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/rspec/mocks/methods_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe "Methods added to every object" do 4 | include_context "with syntax", :expect 5 | 6 | def added_methods 7 | host = Class.new 8 | orig_instance_methods = host.instance_methods 9 | Syntax.enable_should(host) 10 | (host.instance_methods - orig_instance_methods).map(&:to_sym) 11 | end 12 | 13 | it 'limits the number of methods that get added to all objects' do 14 | # If really necessary, you can add to this list, but long term, 15 | # we are hoping to cut down on the number of methods added to all objects 16 | expect(added_methods).to match_array([ 17 | :as_null_object, :null_object?, 18 | :received_message?, :should_not_receive, :should_receive, 19 | :stub, :stub_chain, :unstub 20 | ]) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/rspec/mocks/mock_expectation_error_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe 'MockExpectationError' do 4 | 5 | class Foo 6 | def self.foo 7 | bar 8 | rescue StandardError 9 | end 10 | end 11 | 12 | it 'is not caught by StandardError rescue blocks' do 13 | expect(Foo).not_to receive(:bar) 14 | 15 | expect_fast_failure_from(Foo) do 16 | Foo.foo 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/rspec/mocks/modifying_invoked_expectations_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Modifying invoked expectations" do 4 | shared_examples_for "a customization on an invoked expectation" do |customization_method, *args| 5 | it "raises when the #{customization_method} method is called, indicating the expectation has already been invoked" do 6 | dbl = double 7 | msg_expectation = expect(dbl).to receive(:foo) 8 | expect(dbl.foo).to eq(nil) 9 | 10 | expect { 11 | msg_expectation.__send__(customization_method, *args) 12 | }.to raise_error( 13 | RSpec::Mocks::MockExpectationAlreadyInvokedError, 14 | a_string_including(dbl.inspect, "foo", customization_method.to_s) 15 | ) 16 | end 17 | end 18 | 19 | it_behaves_like "a customization on an invoked expectation", :with, :some_arg 20 | it_behaves_like "a customization on an invoked expectation", :and_return, 1 21 | it_behaves_like "a customization on an invoked expectation", :and_raise, "boom" 22 | it_behaves_like "a customization on an invoked expectation", :and_throw, :symbol 23 | it_behaves_like "a customization on an invoked expectation", :and_yield, 1 24 | it_behaves_like "a customization on an invoked expectation", :exactly, :once 25 | it_behaves_like "a customization on an invoked expectation", :at_least, :once 26 | it_behaves_like "a customization on an invoked expectation", :at_most, :once 27 | end 28 | -------------------------------------------------------------------------------- /spec/rspec/mocks/mutex_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe "Mocking Mutex" do 4 | let(:mocked_mutex) { instance_double(Mutex) } 5 | before do 6 | allow(Mutex).to receive(:new).and_return(mocked_mutex) 7 | allow(mocked_mutex).to receive(:synchronize).and_yield 8 | end 9 | 10 | it "successfully yields" do 11 | called = false 12 | mutex = Mutex.new 13 | mutex.synchronize { called = true } 14 | expect(called).to be_truthy 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/rspec/mocks/nil_expectation_warning_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe "an expectation set on nil" do 4 | it "issues a warning with file and line number information" do 5 | expect { 6 | expect(nil).to receive(:foo) 7 | }.to output(a_string_including( 8 | "An expectation of `:foo` was set on `nil`", 9 | "#{__FILE__}:#{__LINE__ - 3}" 10 | )).to_stderr 11 | 12 | nil.foo 13 | end 14 | 15 | it "issues a warning when the expectation is negative" do 16 | expect { 17 | expect(nil).not_to receive(:foo) 18 | }.to output(a_string_including( 19 | "An expectation of `:foo` was set on `nil`", 20 | "#{__FILE__}:#{__LINE__ - 3}" 21 | )).to_stderr 22 | end 23 | 24 | it 'does not issue a warning when expectations are set to be allowed' do 25 | allow_message_expectations_on_nil 26 | 27 | expect { 28 | expect(nil).to receive(:foo) 29 | expect(nil).to_not receive(:bar) 30 | }.not_to output.to_stderr 31 | 32 | nil.foo 33 | end 34 | 35 | context 'configured to allow expectation on nil' do 36 | include_context 'with isolated configuration' 37 | 38 | it 'does not issue a warning when expectations are set to be allowed' do 39 | RSpec::Mocks.configuration.allow_message_expectations_on_nil = true 40 | 41 | expect { 42 | expect(nil).to receive(:foo) 43 | expect(nil).not_to receive(:bar) 44 | }.not_to output.to_stderr 45 | 46 | nil.foo 47 | end 48 | end 49 | 50 | context 'configured to disallow expectations on nil' do 51 | include_context 'with isolated configuration' 52 | 53 | it "raises an error when expectations on nil are disallowed" do 54 | RSpec::Mocks.configuration.allow_message_expectations_on_nil = false 55 | expect { expect(nil).to receive(:foo) }.to raise_error(RSpec::Mocks::MockExpectationError) 56 | expect { expect(nil).not_to receive(:bar) }.to raise_error(RSpec::Mocks::MockExpectationError) 57 | end 58 | end 59 | 60 | it 'does not call #nil? on a double extra times' do 61 | dbl = double 62 | expect(dbl).to receive(:nil?).once.and_return(false) 63 | dbl.nil? 64 | end 65 | end 66 | 67 | RSpec.describe "#allow_message_expectations_on_nil" do 68 | include_context "with monkey-patched marshal" 69 | 70 | it "does not affect subsequent examples" do 71 | allow_message_expectations_on_nil 72 | RSpec::Mocks.teardown 73 | RSpec::Mocks.setup 74 | 75 | expect { 76 | expect(nil).to receive(:foo) 77 | }.to output(a_string_including( 78 | "An expectation of `:foo` was set on `nil`", 79 | "#{__FILE__}:#{__LINE__ - 3}" 80 | )).to_stderr 81 | 82 | nil.foo 83 | end 84 | 85 | it 'doesnt error when marshalled' do 86 | allow_message_expectations_on_nil 87 | expect(Marshal.dump(nil)).to eq Marshal.dump_without_rspec_mocks(nil) 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/rspec/mocks/once_counts_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe "#once" do 4 | before(:each) do 5 | @double = double 6 | end 7 | 8 | it "passes when called once" do 9 | expect(@double).to receive(:do_something).once 10 | @double.do_something 11 | verify @double 12 | end 13 | 14 | it "passes when called once with specified args" do 15 | expect(@double).to receive(:do_something).once.with("a", "b", "c") 16 | @double.do_something("a", "b", "c") 17 | verify @double 18 | end 19 | 20 | it "passes when called once with unspecified args" do 21 | expect(@double).to receive(:do_something).once 22 | @double.do_something("a", "b", "c") 23 | verify @double 24 | end 25 | 26 | it "fails when called with wrong args" do 27 | expect(@double).to receive(:do_something).once.with("a", "b", "c") 28 | expect { 29 | @double.do_something("d", "e", "f") 30 | }.to fail 31 | reset @double 32 | end 33 | 34 | it "fails fast when called twice" do 35 | expect(@double).to receive(:do_something).once 36 | @double.do_something 37 | expect_fast_failure_from(@double) do 38 | @double.do_something 39 | end 40 | end 41 | 42 | it "fails when not called" do 43 | expect(@double).to receive(:do_something).once 44 | expect { 45 | verify @double 46 | }.to fail 47 | end 48 | 49 | context "when called with the wrong number of times with the specified args and also called with different args" do 50 | it "mentions the wrong call count in the failure message rather than the different args" do 51 | allow(@double).to receive(:do_something) # allow any args... 52 | expect(@double).to receive(:do_something).with(:args, 1).once 53 | 54 | @double.do_something(:args, 2) 55 | @double.do_something(:args, 1) 56 | 57 | expect { 58 | # we've grouped these lines because it should probably fail fast 59 | # on the first line (since our expectation above only allows one 60 | # call with these args), but currently it fails with a confusing 61 | # message on verification, and ultimately we care more about 62 | # what the message is than when it is raised. Still, it would be 63 | # preferable for the error to be triggered on the first line, 64 | # so it'd be good to update this spec to enforce that once we 65 | # get the failure message right. 66 | @double.do_something(:args, 1) 67 | verify @double 68 | }.to fail_with(a_string_including("expected: 1 time", "received: 2 times")) 69 | end 70 | end 71 | 72 | context "when called with negative expectation" do 73 | it "raises an error" do 74 | expect { 75 | expect(@double).not_to receive(:do_something).once 76 | }.to raise_error(/`count` is not supported with negative message expectations/) 77 | end 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/rspec/mocks/order_group_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'OrderGroup' do 2 | let(:order_group) { ::RSpec::Mocks::OrderGroup.new } 3 | 4 | describe '#consume' do 5 | let(:ordered_1) { double :ordered? => true } 6 | let(:ordered_2) { double :ordered? => true } 7 | let(:unordered) { double :ordered? => false } 8 | 9 | before do 10 | order_group.register unordered 11 | order_group.register ordered_1 12 | order_group.register unordered 13 | order_group.register ordered_2 14 | order_group.register unordered 15 | order_group.register unordered 16 | end 17 | 18 | it 'returns the first ordered? expectation' do 19 | expect(order_group.consume).to eq ordered_1 20 | end 21 | it 'keeps returning ordered? expectation until all are returned' do 22 | expectations = 3.times.map { order_group.consume } 23 | expect(expectations).to eq [ordered_1, ordered_2, nil] 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/rspec/mocks/partial_double_using_mocks_directly_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec::Mocks 2 | RSpec.describe "PartialDoubleUsingMocksDirectly" do 3 | let(:klass) do 4 | Class.new do 5 | module MethodMissing 6 | remove_method :method_missing rescue nil 7 | def method_missing(m, *a, &b) 8 | if m == :captured_by_method_missing 9 | "response generated by method missing" 10 | else 11 | super(m, *a, &b) 12 | end 13 | end 14 | end 15 | 16 | extend MethodMissing 17 | include MethodMissing 18 | 19 | def existing_method 20 | :original_value 21 | end 22 | 23 | end 24 | end 25 | 26 | let(:obj) { klass.new } 27 | 28 | it "fails when expected message is not received" do 29 | expect(obj).to receive(:msg) 30 | expect { 31 | verify obj 32 | }.to fail 33 | end 34 | 35 | it "fails when message is received with incorrect args" do 36 | expect(obj).to receive(:msg).with(:correct_arg) 37 | expect { 38 | obj.msg(:incorrect_arg) 39 | }.to fail 40 | obj.msg(:correct_arg) 41 | end 42 | 43 | it "passes when expected message is received" do 44 | expect(obj).to receive(:msg) 45 | obj.msg 46 | verify obj 47 | end 48 | 49 | it "passes when message is received with correct args" do 50 | expect(obj).to receive(:msg).with(:correct_arg) 51 | obj.msg(:correct_arg) 52 | verify obj 53 | end 54 | 55 | it "restores the original method if it existed" do 56 | expect(obj.existing_method).to equal(:original_value) 57 | expect(obj).to receive(:existing_method).and_return(:mock_value) 58 | expect(obj.existing_method).to equal(:mock_value) 59 | verify obj 60 | expect(obj.existing_method).to equal(:original_value) 61 | end 62 | 63 | context "with an instance method handled by method_missing" do 64 | it "restores the original behavior" do 65 | expect(obj.captured_by_method_missing).to eq("response generated by method missing") 66 | allow(obj).to receive(:captured_by_method_missing) { "foo" } 67 | expect(obj.captured_by_method_missing).to eq("foo") 68 | reset obj 69 | expect(obj.captured_by_method_missing).to eq("response generated by method missing") 70 | end 71 | end 72 | 73 | context "with a class method handled by method_missing" do 74 | it "restores the original behavior" do 75 | expect(klass.captured_by_method_missing).to eq("response generated by method missing") 76 | allow(klass).to receive(:captured_by_method_missing) { "foo" } 77 | expect(klass.captured_by_method_missing).to eq("foo") 78 | reset klass 79 | expect(klass.captured_by_method_missing).to eq("response generated by method missing") 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /spec/rspec/mocks/precise_counts_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe "PreciseCounts" do 4 | before(:each) do 5 | @double = double("test double") 6 | end 7 | 8 | it "fails when exactly n times method is called less than n times" do 9 | expect(@double).to receive(:do_something).exactly(3).times 10 | @double.do_something 11 | @double.do_something 12 | expect { 13 | verify @double 14 | }.to fail 15 | end 16 | 17 | it "fails fast when exactly n times method is called more than n times" do 18 | expect(@double).to receive(:do_something).exactly(3).times 19 | @double.do_something 20 | @double.do_something 21 | @double.do_something 22 | expect_fast_failure_from(@double) do 23 | @double.do_something 24 | end 25 | end 26 | 27 | it "fails when exactly n times method is never called" do 28 | expect(@double).to receive(:do_something).exactly(3).times 29 | expect { 30 | verify @double 31 | }.to fail 32 | end 33 | 34 | it "passes if exactly n times method is called exactly n times" do 35 | expect(@double).to receive(:do_something).exactly(3).times 36 | @double.do_something 37 | @double.do_something 38 | @double.do_something 39 | verify @double 40 | end 41 | 42 | it "returns the value given by a block when the exactly once method is called" do 43 | expect(@double).to receive(:to_s).exactly(:once) { "testing" } 44 | expect(@double.to_s).to eq "testing" 45 | verify @double 46 | end 47 | 48 | it "passes multiple calls with different args" do 49 | expect(@double).to receive(:do_something).once.with(1) 50 | expect(@double).to receive(:do_something).once.with(2) 51 | @double.do_something(1) 52 | @double.do_something(2) 53 | verify @double 54 | end 55 | 56 | it "passes multiple calls with different args and counts" do 57 | expect(@double).to receive(:do_something).twice.with(1) 58 | expect(@double).to receive(:do_something).once.with(2) 59 | @double.do_something(1) 60 | @double.do_something(2) 61 | @double.do_something(1) 62 | verify @double 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /spec/rspec/mocks/serialization_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe "Serialization of mocked objects" do 4 | include_context "with monkey-patched marshal" 5 | 6 | class SerializableObject < Struct.new(:foo, :bar); end 7 | 8 | def self.with_yaml_loaded(&block) 9 | context 'with YAML loaded' do 10 | module_exec(&block) 11 | end 12 | end 13 | 14 | def self.without_yaml_loaded(&block) 15 | context 'without YAML loaded' do 16 | before do 17 | # We can't really unload yaml, but we can fake it here... 18 | hide_const("YAML") 19 | Struct.class_exec do 20 | alias __old_to_yaml to_yaml 21 | undef to_yaml 22 | end 23 | end 24 | 25 | module_exec(&block) 26 | 27 | after do 28 | Struct.class_exec do 29 | alias to_yaml __old_to_yaml 30 | undef __old_to_yaml 31 | end 32 | end 33 | end 34 | end 35 | 36 | let(:serializable_object) { RSpec::Mocks::SerializableObject.new(7, "something") } 37 | 38 | def set_stub 39 | allow(serializable_object).to receive_messages(:bazz => 5) 40 | end 41 | 42 | shared_examples 'normal YAML serialization' do 43 | it 'serializes to yaml the same with and without stubbing, using #to_yaml' do 44 | expect { set_stub }.to_not change { serializable_object.to_yaml } 45 | end 46 | 47 | it 'serializes to yaml the same with and without stubbing, using YAML.dump' do 48 | expect { set_stub }.to_not change { ::YAML.dump(serializable_object) } 49 | end 50 | end 51 | 52 | with_yaml_loaded do 53 | compiled_with_psych = begin 54 | require 'psych' 55 | true 56 | rescue LoadError 57 | false 58 | end 59 | 60 | if compiled_with_psych 61 | context 'using Syck as the YAML engine' do 62 | before(:each) { ::YAML::ENGINE.yamler = 'syck' } 63 | around(:each) { |example| with_isolated_stderr(&example) } 64 | it_behaves_like 'normal YAML serialization' 65 | end if defined?(::YAML::ENGINE) 66 | 67 | context 'using Psych as the YAML engine' do 68 | before(:each) { ::YAML::ENGINE.yamler = 'psych' } if defined?(::YAML::ENGINE) 69 | it_behaves_like 'normal YAML serialization' 70 | end 71 | else 72 | it_behaves_like 'normal YAML serialization' 73 | end 74 | end 75 | 76 | without_yaml_loaded do 77 | it 'does not add #to_yaml to the stubbed object' do 78 | expect(serializable_object).not_to respond_to(:to_yaml) 79 | set_stub 80 | expect(serializable_object).not_to respond_to(:to_yaml) 81 | end 82 | end 83 | 84 | it 'marshals the same with and without stubbing' do 85 | expect { set_stub }.to_not change { Marshal.dump(serializable_object) } 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/rspec/mocks/standalone_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/support/spec/in_sub_process' 2 | main = self 3 | 4 | RSpec.describe "Loading rspec/mocks/standalone" do 5 | include RSpec::Support::InSubProcess 6 | 7 | it "exposes the RSpec::Mocks API on `main`" do 8 | in_sub_process do 9 | require 'rspec/mocks/standalone' 10 | main.instance_eval do 11 | dbl = double 12 | expect(dbl).to receive(:foo) 13 | dbl.foo 14 | RSpec::Mocks.verify 15 | RSpec::Mocks.teardown 16 | end 17 | end 18 | end 19 | 20 | it "does not infect other objects with the RSpec::Mocks API" do 21 | in_sub_process do 22 | require 'rspec/mocks/standalone' 23 | object = Object.new 24 | expect(object).not_to respond_to(:double, :expect) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/rspec/mocks/stash_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe "only stashing the original method" do 4 | let(:klass) do 5 | Class.new do 6 | def self.foo(_) 7 | :original_value 8 | end 9 | end 10 | end 11 | 12 | it "keeps the original method intact after multiple expectations are added on the same method" do 13 | expect(klass).to receive(:foo).with(:fizbaz).and_return(:wowwow) 14 | expect(klass).to receive(:foo).with(:bazbar).and_return(:okay) 15 | 16 | klass.foo(:fizbaz) 17 | klass.foo(:bazbar) 18 | verify klass 19 | 20 | reset klass 21 | expect(klass.foo(:yeah)).to equal(:original_value) 22 | end 23 | end 24 | 25 | RSpec.describe "when a class method is aliased on a subclass and the method is mocked" do 26 | it "restores the original aliased public method" do 27 | klass = Class.new do 28 | class << self 29 | alias alternate_new new 30 | end 31 | end 32 | 33 | expect(klass).to receive(:alternate_new) 34 | expect(klass.alternate_new).to be_nil 35 | 36 | verify klass 37 | 38 | reset klass 39 | expect(klass.alternate_new).to be_an_instance_of(klass) 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/rspec/mocks/syntax_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | RSpec.describe Mocks do 3 | it "does not inadvertently define BasicObject on 1.8", :if => RUBY_VERSION.to_f < 1.9 do 4 | expect(defined?(::BasicObject)).to be nil 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/rspec/mocks/test_double_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe TestDouble do 4 | describe "#freeze" do 5 | subject { double } 6 | 7 | it "gives a warning" do 8 | expect(RSpec).to receive(:warn_with).with(/freeze a test double/) 9 | subject.freeze 10 | end 11 | 12 | it "gives the correct call site for the warning" do 13 | expect_warning_with_call_site(__FILE__, __LINE__ + 1) 14 | subject.freeze 15 | end 16 | 17 | it "doesn't freeze the object" do 18 | allow(RSpec).to receive(:warn_with).with(/freeze a test double/) 19 | double.freeze 20 | allow(subject).to receive(:hi) 21 | 22 | expect { 23 | subject.hi 24 | }.not_to raise_error 25 | end 26 | 27 | it "returns the instance of the test double" do 28 | allow(RSpec).to receive(:warn_with).with(/freeze a test double/) 29 | expect(subject.freeze).to eq subject 30 | end 31 | end 32 | 33 | RSpec.shared_examples_for "a copy method" do |method| 34 | it "copies the `as_null_object` state when #{method}'d" do 35 | dbl = double.as_null_object 36 | copy = dbl.__send__(method) 37 | expect(copy.foo.bar).to be(copy) 38 | end 39 | end 40 | 41 | include_examples "a copy method", :dup 42 | include_examples "a copy method", :clone 43 | 44 | [[:should, :expect], [:expect], [:should]].each do |syntax| 45 | context "with syntax #{syntax.inspect}" do 46 | include_context "with syntax", syntax 47 | 48 | it 'stubs the methods passed in the stubs hash' do 49 | dbl = double("MyDouble", :a => 5, :b => 10) 50 | 51 | expect(dbl.a).to eq(5) 52 | expect(dbl.b).to eq(10) 53 | end 54 | end 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/rspec/mocks/thrice_counts_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Mocks 3 | RSpec.describe "#thrice" do 4 | before(:each) do 5 | @double = double("test double") 6 | end 7 | 8 | it "passes when called thrice" do 9 | expect(@double).to receive(:do_something).thrice 10 | 3.times { @double.do_something } 11 | verify @double 12 | end 13 | 14 | it "passes when called thrice with specified args" do 15 | expect(@double).to receive(:do_something).thrice.with("1", 1) 16 | 3.times { @double.do_something("1", 1) } 17 | verify @double 18 | end 19 | 20 | it "passes when called thrice with unspecified args" do 21 | expect(@double).to receive(:do_something).thrice 22 | @double.do_something("1") 23 | @double.do_something(1) 24 | @double.do_something(nil) 25 | verify @double 26 | end 27 | 28 | it "fails fast when call count is higher than expected" do 29 | expect(@double).to receive(:do_something).thrice 30 | 3.times { @double.do_something } 31 | expect_fast_failure_from(@double) do 32 | @double.do_something 33 | end 34 | end 35 | 36 | it "fails when call count is lower than expected" do 37 | expect(@double).to receive(:do_something).thrice 38 | @double.do_something 39 | expect { 40 | verify @double 41 | }.to fail 42 | end 43 | 44 | it "fails when called with wrong args on the first call" do 45 | expect(@double).to receive(:do_something).thrice.with("1", 1) 46 | expect { 47 | @double.do_something(1, "1") 48 | }.to fail 49 | reset @double 50 | end 51 | 52 | it "fails when called with wrong args on the second call" do 53 | expect(@double).to receive(:do_something).thrice.with("1", 1) 54 | @double.do_something("1", 1) 55 | expect { 56 | @double.do_something(1, "1") 57 | }.to fail 58 | reset @double 59 | end 60 | 61 | it "fails when called with wrong args on the third call" do 62 | expect(@double).to receive(:do_something).thrice.with("1", 1) 63 | @double.do_something("1", 1) 64 | @double.do_something("1", 1) 65 | expect { 66 | @double.do_something(1, "1") 67 | }.to fail 68 | reset @double 69 | end 70 | 71 | context "when called with negative expectation" do 72 | it "raises an error" do 73 | expect { 74 | expect(@double).not_to receive(:do_something).thrice 75 | }.to raise_error(/`count` is not supported with negative message expectations/) 76 | end 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/rspec/mocks/to_ary_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "a double receiving to_ary" do 2 | shared_examples "to_ary" do 3 | it "can be overridden with a stub" do 4 | allow(obj).to receive(:to_ary) { :non_nil_value } 5 | expect(obj.to_ary).to be(:non_nil_value) 6 | end 7 | 8 | it "responds when overridden" do 9 | allow(obj).to receive(:to_ary) { :non_nil_value } 10 | expect(obj).to respond_to(:to_ary) 11 | end 12 | 13 | it "supports Array#flatten" do 14 | dbl = double('foo') 15 | expect([dbl].flatten).to eq([dbl]) 16 | end 17 | end 18 | 19 | context "double as_null_object" do 20 | let(:obj) { double('obj').as_null_object } 21 | include_examples "to_ary" 22 | 23 | it "does respond to to_ary" do 24 | expect(obj).to respond_to(:to_ary) 25 | end 26 | 27 | it "does respond to to_a" do 28 | expect(obj).to respond_to(:to_a) 29 | end 30 | 31 | it "returns nil" do 32 | expect(obj.to_ary).to eq nil 33 | end 34 | end 35 | 36 | context "double without as_null_object" do 37 | let(:obj) { double('obj') } 38 | include_examples "to_ary" 39 | 40 | it "doesn't respond to to_ary" do 41 | expect(obj).not_to respond_to(:to_ary) 42 | end 43 | 44 | it "doesn't respond to to_a", :if => (RUBY_VERSION.to_f > 1.8) do 45 | expect(obj).not_to respond_to(:to_a) 46 | end 47 | 48 | it "raises " do 49 | expect { obj.to_ary }.to raise_error(NoMethodError) 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/rspec/mocks/verifying_doubles/class_double_with_class_not_loaded_spec.rb: -------------------------------------------------------------------------------- 1 | require 'support/doubled_classes' 2 | 3 | module RSpec 4 | module Mocks 5 | RSpec.describe 'A class double with the doubled class not loaded' do 6 | include_context "with isolated configuration" 7 | 8 | before do 9 | RSpec::Mocks.configuration.verify_doubled_constant_names = false 10 | end 11 | 12 | it 'includes the double name in errors for unexpected messages' do 13 | o = class_double("NonLoadedClass") 14 | expect { 15 | o.foo 16 | }.to fail_including('ClassDouble(NonLoadedClass)') 17 | end 18 | 19 | it 'allows any method to be stubbed' do 20 | o = class_double('NonloadedClass') 21 | allow(o).to receive(:undefined_instance_method).with(:arg).and_return(1) 22 | expect(o.undefined_instance_method(:arg)).to eq(1) 23 | end 24 | 25 | specify "trying to raise a class_double raises a TypeError", :unless => RUBY_VERSION == '1.9.2' do 26 | subject = Object.new 27 | class_double("StubbedError").as_stubbed_const 28 | allow(subject).to receive(:some_method).and_raise(StubbedError) 29 | expect { subject.some_method }.to raise_error(TypeError, 'exception class/object expected') 30 | end 31 | 32 | context "when stubbing a private module method" do 33 | before(:all) do 34 | Module.class_exec do 35 | private 36 | def use; end 37 | end 38 | end 39 | 40 | after(:all) do 41 | Module.class_exec do 42 | undef use 43 | end 44 | end 45 | 46 | it 'can mock private module methods' do 47 | double = Module.new 48 | allow(double).to receive(:use) 49 | expect { double.use }.to raise_error(/private method [`']use' called/) 50 | 51 | double = class_double("NonloadedClass") 52 | expect(double).to receive(:use).and_return(:ok) 53 | expect(double.use).to be(:ok) 54 | end 55 | end 56 | 57 | context "when the class const has been previously stubbed" do 58 | before { stub_const("NonLoadedClass", Class.new) } 59 | 60 | it "treats the class as being unloaded for `class_double('NonLoadedClass')`" do 61 | o = class_double("NonLoadedClass") 62 | allow(o).to receive(:undefined_method) 63 | end 64 | 65 | it "treats the class as being unloaded for `instance_double(NonLoadedClass)`" do 66 | o = class_double(NonLoadedClass) 67 | allow(o).to receive(:undefined_method) 68 | end 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /spec/rspec/mocks/verifying_doubles/instance_double_with_class_not_loaded_spec.rb: -------------------------------------------------------------------------------- 1 | require 'support/doubled_classes' 2 | 3 | module RSpec 4 | module Mocks 5 | RSpec.describe 'An instance double with the doubled class not loaded' do 6 | include_context "with isolated configuration" 7 | 8 | before do 9 | RSpec::Mocks.configuration.verify_doubled_constant_names = false 10 | end 11 | 12 | it 'includes the doubled module in errors for unexpected messages' do 13 | o = instance_double("NonLoadedClass") 14 | expect { 15 | o.foo 16 | }.to fail_including('InstanceDouble(NonLoadedClass)') 17 | end 18 | 19 | it 'allows any instance method to be stubbed' do 20 | o = instance_double('NonloadedClass') 21 | allow(o).to receive(:undefined_instance_method).with(:arg).and_return(true) 22 | expect(o.undefined_instance_method(:arg)).to eq(true) 23 | end 24 | 25 | it 'allows any instance method to be expected' do 26 | o = instance_double("NonloadedClass") 27 | 28 | expect(o).to receive(:undefined_instance_method). 29 | with(:arg). 30 | and_return(true) 31 | 32 | expect(o.undefined_instance_method(:arg)).to eq(true) 33 | end 34 | 35 | it 'handles classes that are materialized after mocking' do 36 | stub_const "A::B", Object.new 37 | o = instance_double "A", :undefined_instance_method => true 38 | 39 | expect(o.undefined_instance_method).to eq(true) 40 | end 41 | 42 | context 'for null objects' do 43 | let(:obj) { instance_double('NonLoadedClass').as_null_object } 44 | 45 | it 'returns self from any message' do 46 | expect(obj.a.b.c).to be(obj) 47 | end 48 | 49 | it 'reports it responds to any message' do 50 | expect(obj.respond_to?(:a)).to be true 51 | expect(obj.respond_to?(:a, false)).to be true 52 | expect(obj.respond_to?(:a, true)).to be true 53 | end 54 | end 55 | 56 | context "when the class const has been previously stubbed" do 57 | before { class_double("NonLoadedClass").as_stubbed_const } 58 | 59 | it "treats the class as unloaded for `instance_double('NonLoadedClass')`" do 60 | o = instance_double("NonLoadedClass") 61 | allow(o).to receive(:undefined_method) 62 | end 63 | 64 | it "treats the class as unloaded for `instance_double(NonLoadedClass)`" do 65 | o = instance_double(NonLoadedClass) 66 | allow(o).to receive(:undefined_method) 67 | end 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/rspec/mocks/verifying_doubles/naming_spec.rb: -------------------------------------------------------------------------------- 1 | require 'support/doubled_classes' 2 | 3 | module RSpec 4 | module Mocks 5 | RSpec::Matchers.define :fail_expectations_as do |expected| 6 | description { "include a meaningful name in the exception" } 7 | 8 | def error_message_for(_) 9 | expect(actual).to have_received(:defined_instance_and_class_method) 10 | rescue MockExpectationError, Expectations::ExpectationNotMetError => e 11 | e.message 12 | else 13 | raise("should have failed but did not") 14 | end 15 | 16 | failure_message do |actual| 17 | "expected #{actual.inspect} to fail expectations as:\n" \ 18 | " #{expected.inspect}, but failed with:\n" \ 19 | " #{@error_message.inspect}" 20 | end 21 | 22 | match do |actual| 23 | @error_message = error_message_for(actual) 24 | @error_message.include?(expected) 25 | end 26 | end 27 | 28 | RSpec.describe 'Verified double naming' do 29 | shared_examples "a named verifying double" do |type_desc| 30 | context "when a name is given as a string" do 31 | subject { create_double("LoadedClass", "foo") } 32 | it { is_expected.to fail_expectations_as(%Q{#{type_desc}(LoadedClass) "foo"}) } 33 | end 34 | 35 | context "when a name is given as a symbol" do 36 | subject { create_double("LoadedClass", :foo) } 37 | it { is_expected.to fail_expectations_as(%Q{#{type_desc}(LoadedClass) :foo}) } 38 | end 39 | 40 | context "when no name is given" do 41 | subject { create_double("LoadedClass") } 42 | it { is_expected.to fail_expectations_as(%Q{#{type_desc}(LoadedClass) (anonymous)}) } 43 | end 44 | end 45 | 46 | describe "instance_double" do 47 | it_behaves_like "a named verifying double", "InstanceDouble" do 48 | alias :create_double :instance_double 49 | end 50 | end 51 | 52 | describe "instance_spy" do 53 | it_behaves_like "a named verifying double", "InstanceDouble" do 54 | alias :create_double :instance_spy 55 | end 56 | end 57 | 58 | describe "class_double" do 59 | it_behaves_like "a named verifying double", "ClassDouble" do 60 | alias :create_double :class_double 61 | end 62 | end 63 | 64 | describe "class_spy" do 65 | it_behaves_like "a named verifying double", "ClassDouble" do 66 | alias :create_double :class_spy 67 | end 68 | end 69 | 70 | describe "object_double" do 71 | it_behaves_like "a named verifying double", "ObjectDouble" do 72 | alias :create_double :object_double 73 | end 74 | end 75 | 76 | describe "object_spy" do 77 | it_behaves_like "a named verifying double", "ObjectDouble" do 78 | alias :create_double :object_spy 79 | end 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /spec/support/aruba.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'aruba/rspec' 3 | 4 | Aruba.configure do |config| 5 | if RUBY_PLATFORM =~ /java/ || defined?(Rubinius) || (defined?(RUBY_ENGINE) && RUBY_ENGINE == 'truffleruby') 6 | config.exit_timeout = 60 7 | else 8 | config.exit_timeout = 5 9 | end 10 | end 11 | rescue NameError => e 12 | # This silences a name error on unsupported version of JRuby 13 | raise e unless RSpec::Support::Ruby.jruby? && JRUBY_VERSION =~ /9\.1\.17\.0/ 14 | rescue LoadError => e 15 | # This silences a load error on unsupported version of JRuby 16 | raise e unless RSpec::Support::Ruby.jruby? && JRUBY_VERSION =~ /9\.1\.17\.0/ 17 | end 18 | -------------------------------------------------------------------------------- /spec/support/before_all_shared_example_group.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples "fails in a before(:all) block" do 2 | the_error = nil 3 | before(:all) do 4 | begin 5 | use_rspec_mocks 6 | rescue 7 | the_error = $! 8 | end 9 | end 10 | 11 | it "raises an error with a useful message" do 12 | expect(the_error).to be_a_kind_of(RSpec::Mocks::OutsideOfExampleError) 13 | 14 | expect(the_error.message).to match(/The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported./) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/support/doubled_classes.rb: -------------------------------------------------------------------------------- 1 | class LoadedClass 2 | extend RSpec::Support::RubyFeatures 3 | 4 | M = :m 5 | N = :n 6 | INSTANCE = LoadedClass.new 7 | 8 | def initialize(_a, _b) 9 | end 10 | 11 | class << self 12 | def respond_to?(method_name, include_all=false) 13 | return true if method_name == :dynamic_class_method 14 | super 15 | end 16 | 17 | def defined_class_method 18 | end 19 | 20 | def send 21 | # fake out! 22 | end 23 | 24 | def defined_instance_and_class_method 25 | end 26 | 27 | protected 28 | 29 | def defined_protected_class_method 30 | end 31 | 32 | private 33 | 34 | def defined_private_class_method 35 | end 36 | end 37 | 38 | def defined_instance_method 39 | end 40 | 41 | def instance_method_with_two_args(_a, _b) 42 | end 43 | 44 | def instance_method_with_only_defaults(_a=1, _b=2) 45 | end 46 | 47 | def defined_instance_and_class_method 48 | end 49 | 50 | if required_kw_args_supported? 51 | # Need to eval this since it is invalid syntax on earlier rubies. 52 | eval <<-RUBY 53 | def kw_args_method(foo, optional_arg:'hello', required_arg:) 54 | end 55 | 56 | def mixed_args_method(foo, bar, optional_arg_1:1, optional_arg_2:2) 57 | end 58 | RUBY 59 | end 60 | 61 | def send(*) 62 | end 63 | 64 | def respond_to?(method_name, include_all=false) 65 | return true if method_name == :dynamic_instance_method 66 | super 67 | end 68 | 69 | class Nested; end 70 | 71 | protected 72 | 73 | def defined_protected_method 74 | end 75 | 76 | private 77 | 78 | def defined_private_method 79 | "wink wink ;)" 80 | end 81 | end 82 | 83 | class LoadedClassWithOverriddenName < LoadedClass 84 | def self.name 85 | "Overriding name is not a good idea but we can't count on users not doing this" 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /spec/support/matchers.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | def fail(&block) 4 | raise_error(RSpec::Mocks::MockExpectationError, &block) 5 | end 6 | 7 | def fail_with(*args, &block) 8 | raise_error(RSpec::Mocks::MockExpectationError, *args, &block) 9 | end 10 | 11 | def fail_including(*snippets) 12 | raise_error( 13 | RSpec::Mocks::MockExpectationError, 14 | a_string_including(*snippets) 15 | ) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /tmp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rspec/rspec-mocks/3caa9ac841e146d618421f870077c0ad684e6cad/tmp/.gitkeep --------------------------------------------------------------------------------