├── .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 ├── Should.md ├── benchmarks ├── 2.x_vs_3.x_matcher_dsl_implementation.rb ├── autoload_v_require.rb ├── caller_vs_raise_for_backtrace.rb ├── cloning_matchers.rb ├── count_vs_select_size.rb ├── default_messages_as_methods_v_blocks.rb ├── example_spec.rb ├── gsub_vs_tr_single_character.rb ├── include_matcher.rb ├── include_v_superclass.rb ├── match_array │ ├── failing_with_distinct_items.rb │ ├── failing_with_duplicate_items.rb │ ├── passing_with_distinct_items.rb │ ├── passing_with_duplicate_items.rb │ └── rubyprof │ │ └── passing_with_distinct_items.rb ├── matcher_dsl_vs_classes.rb ├── method_to_proc.rb ├── output_stringio_vs_tempfile.rb └── set_vs_array_include.rb ├── cucumber.yml ├── features ├── .nav ├── README.md ├── aggregating_failures.feature ├── built_in_matchers │ ├── README.md │ ├── all.feature │ ├── be.feature │ ├── be_within.feature │ ├── change.feature │ ├── comparisons.feature │ ├── contain_exactly.feature │ ├── cover.feature │ ├── end_with.feature │ ├── equality.feature │ ├── exist.feature │ ├── have_attributes.feature │ ├── include.feature │ ├── match.feature │ ├── output.feature │ ├── predicates.feature │ ├── raise_error.feature │ ├── respond_to.feature │ ├── satisfy.feature │ ├── start_with.feature │ ├── throw_symbol.feature │ ├── types.feature │ └── yield.feature ├── composing_matchers.feature ├── compound_expectations.feature ├── custom_matchers │ ├── access_running_example.feature │ ├── define_block_matcher.feature │ ├── define_diffable_matcher.feature │ ├── define_matcher.feature │ ├── define_matcher_outside_rspec.feature │ └── define_matcher_with_fluent_interface.feature ├── customized_message.feature ├── define_negated_matcher.feature ├── diffing.feature ├── implicit_docstrings.feature ├── step_definitions │ └── additional_cli_steps.rb ├── support │ ├── diff_lcs_versions.rb │ ├── disallow_certain_apis.rb │ ├── env.rb │ ├── rubinius.rb │ └── ruby_features.rb ├── syntax_configuration.feature └── test_frameworks │ └── minitest.feature ├── lib └── rspec │ ├── expectations.rb │ ├── expectations │ ├── block_snippet_extractor.rb │ ├── configuration.rb │ ├── expectation_target.rb │ ├── fail_with.rb │ ├── failure_aggregator.rb │ ├── handler.rb │ ├── minitest_integration.rb │ ├── syntax.rb │ └── version.rb │ ├── matchers.rb │ └── matchers │ ├── aliased_matcher.rb │ ├── built_in.rb │ ├── built_in │ ├── all.rb │ ├── base_matcher.rb │ ├── be.rb │ ├── be_between.rb │ ├── be_instance_of.rb │ ├── be_kind_of.rb │ ├── be_within.rb │ ├── change.rb │ ├── compound.rb │ ├── contain_exactly.rb │ ├── count_expectation.rb │ ├── cover.rb │ ├── eq.rb │ ├── eql.rb │ ├── equal.rb │ ├── exist.rb │ ├── has.rb │ ├── have_attributes.rb │ ├── include.rb │ ├── match.rb │ ├── operators.rb │ ├── output.rb │ ├── raise_error.rb │ ├── respond_to.rb │ ├── satisfy.rb │ ├── start_or_end_with.rb │ ├── throw_symbol.rb │ └── yield.rb │ ├── composable.rb │ ├── dsl.rb │ ├── english_phrasing.rb │ ├── fail_matchers.rb │ ├── generated_descriptions.rb │ ├── matcher_delegator.rb │ ├── matcher_protocol.rb │ └── multi_matcher_diff.rb ├── maintenance-branch ├── rspec-expectations.gemspec ├── script ├── ci_functions.sh ├── clone_all_rspec_repos ├── cucumber.sh ├── functions.sh ├── legacy_setup.sh ├── predicate_functions.sh ├── run_build ├── run_rubocop └── update_rubygems_and_install_bundler └── spec ├── rspec ├── expectations │ ├── block_snippet_extractor_spec.rb │ ├── configuration_spec.rb │ ├── expectation_target_spec.rb │ ├── extensions │ │ └── kernel_spec.rb │ ├── fail_with_spec.rb │ ├── failure_aggregator_spec.rb │ ├── handler_spec.rb │ ├── minitest_integration_spec.rb │ └── syntax_spec.rb ├── expectations_spec.rb ├── matchers │ ├── aliased_matcher_spec.rb │ ├── aliases_spec.rb │ ├── built_in │ │ ├── all_spec.rb │ │ ├── base_matcher_spec.rb │ │ ├── be_between_spec.rb │ │ ├── be_instance_of_spec.rb │ │ ├── be_kind_of_spec.rb │ │ ├── be_spec.rb │ │ ├── be_within_spec.rb │ │ ├── captures_spec.rb │ │ ├── change_spec.rb │ │ ├── compound_spec.rb │ │ ├── contain_exactly_spec.rb │ │ ├── cover_spec.rb │ │ ├── eq_spec.rb │ │ ├── eql_spec.rb │ │ ├── equal_spec.rb │ │ ├── exist_spec.rb │ │ ├── has_spec.rb │ │ ├── have_attributes_spec.rb │ │ ├── include_spec.rb │ │ ├── match_spec.rb │ │ ├── operators_spec.rb │ │ ├── output_spec.rb │ │ ├── raise_error_spec.rb │ │ ├── respond_to_spec.rb │ │ ├── satisfy_spec.rb │ │ ├── start_and_end_with_spec.rb │ │ ├── throw_symbol_spec.rb │ │ └── yield_spec.rb │ ├── composable_spec.rb │ ├── define_negated_matcher_spec.rb │ ├── description_generation_spec.rb │ ├── dsl_spec.rb │ ├── english_phrasing_spec.rb │ ├── legacy_spec.rb │ └── multi_matcher_diff_spec.rb └── matchers_spec.rb ├── spec_helper.rb └── support ├── matchers.rb └── shared_examples ├── block_matcher.rb ├── matcher.rb └── value_matcher.rb /.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:22+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 | *.rbc 12 | .yardoc 13 | bin 14 | .rbx 15 | Gemfile-custom 16 | bundle 17 | .rspec-local 18 | spec/examples.txt 19 | specs.out 20 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --warnings 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: 2 | - .rubocop_todo.yml 3 | - .rubocop_rspec_base.yml 4 | 5 | AllCops: 6 | TargetRubyVersion: 2.4 7 | DisplayCopNames: true 8 | Exclude: 9 | - bin/* 10 | - tmp/**/* 11 | 12 | # Over time we'd like to get this down, but this is what we're at now. 13 | Layout/LineLength: 14 | Max: 186 15 | 16 | # Offense count: 1 17 | Style/BlockComments: 18 | Enabled: false 19 | 20 | Style/ClassAndModuleChildren: 21 | Exclude: 22 | - spec/**/* 23 | 24 | Style/EvalWithLocation: 25 | Exclude: 26 | - spec/rspec/matchers/built_in/respond_to_spec.rb 27 | 28 | Style/MultilineBlockChain: 29 | Exclude: 30 | - spec/**/* 31 | 32 | Style/RescueModifier: 33 | Exclude: 34 | - spec/**/* 35 | - benchmarks/**/* 36 | 37 | Style/Semicolon: 38 | Enabled: false 39 | 40 | Style/SingleLineMethods: 41 | Exclude: 42 | - spec/**/* 43 | - benchmarks/**/* 44 | 45 | # We have some situations where we need to use `raise ExceptionClass.new(argument)`. 46 | Style/RaiseArgs: 47 | Enabled: false 48 | 49 | Style/FrozenStringLiteralComment: 50 | EnforcedStyle: never 51 | Exclude: 52 | - REPORT_TEMPLATE.md 53 | 54 | Style/PercentLiteralDelimiters: 55 | PreferredDelimiters: 56 | default: [] 57 | '%r': '||' 58 | 59 | Style/WordArray: 60 | Enabled: false 61 | 62 | Security/Eval: 63 | Exclude: 64 | - Gemfile 65 | 66 | Metrics/AbcSize: 67 | Max: 27 68 | 69 | # Offense count: 2 70 | # Configuration parameters: CountComments, ExcludedMethods. 71 | Metrics/BlockLength: 72 | Max: 96 73 | Exclude: 74 | - spec/**/* 75 | 76 | # Offense count: 1 77 | # Configuration parameters: CountComments. 78 | Metrics/ModuleLength: 79 | Max: 239 80 | Exclude: 81 | - spec/**/* 82 | 83 | # Offense count: 4 84 | Metrics/PerceivedComplexity: 85 | Max: 14 86 | 87 | Layout/AccessModifierIndentation: 88 | Exclude: 89 | - 'lib/rspec/expectations/syntax.rb' # Too much diff to fix 90 | 91 | # Offense count: 7 92 | Layout/ParameterAlignment: 93 | Enabled: false 94 | 95 | Layout/SpaceInsideArrayLiteralBrackets: 96 | Exclude: 97 | - spec/rspec/matchers/built_in/contain_exactly_spec.rb 98 | 99 | Layout/SpaceInsideParens: 100 | Exclude: 101 | - spec/rspec/matchers/built_in/* 102 | 103 | Lint/AmbiguousBlockAssociation: 104 | Exclude: 105 | - spec/**/* 106 | 107 | Lint/AmbiguousRegexpLiteral: 108 | Exclude: 109 | - 'features/step_definitions/*' 110 | 111 | Lint/SuppressedException: 112 | Exclude: 113 | - benchmarks/**/* 114 | 115 | # Offense count: 3 116 | Lint/IneffectiveAccessModifier: 117 | Exclude: 118 | - 'lib/rspec/matchers.rb' 119 | - 'lib/rspec/matchers/built_in/compound.rb' 120 | 121 | Lint/InheritException: 122 | Exclude: 123 | - 'lib/rspec/expectations.rb' 124 | 125 | Bundler/DuplicatedGem: 126 | Enabled: false 127 | 128 | Bundler/OrderedGems: 129 | Enabled: false 130 | -------------------------------------------------------------------------------- /.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 | ```shell 4 | git clone https://github.com/rspec/rspec-expectations.git 5 | cd rspec-expectations 6 | gem install bundler 7 | bundle install 8 | ``` 9 | 10 | Now you should be able to run any of: 11 | 12 | ```shell 13 | rake 14 | rake spec 15 | rake cucumber 16 | ``` 17 | 18 | Or, if you prefer to use the rspec and cucumber commands directly, you can either: 19 | 20 | ```shell 21 | bundle exec rspec 22 | ``` 23 | 24 | Or ... 25 | 26 | ```shell 27 | bundle install --binstubs 28 | bin/rspec 29 | ``` 30 | 31 | ## Customize the dev environment 32 | 33 | The Gemfile includes the gems you'll need to be able to run specs. If you want 34 | to customize your dev environment with additional tools like guard or 35 | ruby-debug, add any additional gem declarations to Gemfile-custom (see 36 | Gemfile-custom.sample for some examples). 37 | -------------------------------------------------------------------------------- /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-mocks 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 | elsif RUBY_VERSION < '2.2.0' 24 | gem 'rake', '12.3.2' 25 | else 26 | gem 'rake', '>= 12.3.3' 27 | end 28 | 29 | if ENV['DIFF_LCS_VERSION'] 30 | gem 'diff-lcs', ENV['DIFF_LCS_VERSION'] 31 | else 32 | gem 'diff-lcs', '~> 1.4', '>= 1.4.3' 33 | end 34 | 35 | gem 'coderay' # for syntax highlighting 36 | gem 'yard', '~> 0.9.24', :require => false 37 | 38 | ### deps for rdoc.info 39 | group :documentation do 40 | gem 'redcarpet', :platform => :mri 41 | gem 'github-markup', :platform => :mri 42 | end 43 | 44 | group :coverage do 45 | gem 'simplecov' 46 | end 47 | 48 | if RUBY_VERSION < '2.0.0' || RUBY_ENGINE == 'java' 49 | gem 'json', '< 2.0.0' # is a dependency of simplecov 50 | else 51 | gem 'json', '> 2.3.0' 52 | end 53 | 54 | # allow gems to be installed on older rubies and/or windows 55 | if RUBY_VERSION < '2.2.0' && !!(RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/) 56 | gem 'ffi', '< 1.10' 57 | elsif RUBY_VERSION < '2.4.0' && !!(RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/) 58 | gem 'ffi', '< 1.15' 59 | elsif RUBY_VERSION < '1.9' 60 | gem 'ffi', '< 1.9.19' # ffi dropped Ruby 1.8 support in 1.9.19 61 | elsif RUBY_VERSION < '2.0' 62 | gem 'ffi', '< 1.11.0' # ffi dropped Ruby 1.9 support in 1.11.0 63 | else 64 | gem 'ffi', '> 1.9.24' # prevent Github security vulnerability warning 65 | end 66 | 67 | if RUBY_VERSION <= '2.3.0' && !!(RbConfig::CONFIG['host_os'] =~ /cygwin|mswin|mingw|bccwin|wince|emx/) 68 | gem "childprocess", "< 1.0.0" 69 | elsif RUBY_VERSION < '2.0.0' 70 | gem "childprocess", "< 1.0.0" 71 | else 72 | gem "childprocess", "> 1.0.0" 73 | end 74 | 75 | if RUBY_VERSION < '2.0.0' 76 | gem 'thor', '< 1.0.0' 77 | else 78 | gem 'thor', '> 1.0.0' 79 | end 80 | 81 | if RUBY_VERSION < '1.9.2' 82 | gem 'contracts', '~> 0.15.0' # is a dependency of aruba 83 | end 84 | 85 | # Version 5.12 of minitest requires Ruby 2.4 86 | if RUBY_VERSION < '2.4.0' 87 | gem 'minitest', '< 5.12.0' 88 | end 89 | 90 | platforms :jruby do 91 | if RUBY_VERSION < '1.9.0' 92 | # Pin jruby-openssl on older J Ruby 93 | gem "jruby-openssl", "< 0.10.0" 94 | else 95 | gem "jruby-openssl" 96 | end 97 | end 98 | 99 | if RUBY_VERSION >= '2.4' && RUBY_ENGINE == 'ruby' 100 | gem 'rubocop', "~> 1.0", "< 1.12" 101 | end 102 | 103 | if RUBY_VERSION < '2.0.0' 104 | gem 'cucumber', "<= 1.3.22" 105 | elsif !ENV['DIFF_LCS_VERSION'].to_s.empty? && ENV['DIFF_LCS_VERSION'].scan(/\d\.\d/).first.to_f < 1.5 106 | # Older version of diff-lcs cause a downstream error with cucumber and modern rails 107 | gem "activesupport", "< 7" 108 | end 109 | 110 | eval File.read('Gemfile-custom') if File.exist?('Gemfile-custom') 111 | -------------------------------------------------------------------------------- /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 | guard 'rspec', :version => 2 do 2 | watch(/^spec\/(.*)_spec.rb/) 3 | watch(/^lib\/(.*)\.rb/) { |m| "spec/#{m[1]}_spec.rb" } 4 | watch(/^spec\/spec_helper.rb/) { "spec" } 5 | watch(/^lib\/rspec\/matchers\/built_in/) { "spec" } 6 | end 7 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | ### Subject of the issue 7 | 10 | 11 | ### Your environment 12 | * Ruby version: 13 | * rspec-expectations 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/expectations/version' 8 | 9 | require 'cucumber/rake/task' 10 | Cucumber::Rake::Task.new(:cucumber) 11 | 12 | if RUBY_VERSION >= '2.4' && RUBY_ENGINE == 'ruby' 13 | require 'rubocop/rake_task' 14 | RuboCop::RakeTask.new(:rubocop) 15 | end 16 | 17 | desc "Run all examples" 18 | RSpec::Core::RakeTask.new(:spec) do |t| 19 | t.ruby_opts = %w[-w] 20 | end 21 | 22 | namespace :clobber do 23 | desc "delete generated .rbc files" 24 | task :rbc do 25 | sh 'find . -name "*.rbc" | xargs rm' 26 | end 27 | end 28 | 29 | desc "delete generated files" 30 | task :clobber => ["clobber:rbc"] do 31 | rm_rf 'doc' 32 | rm_rf '.yardoc' 33 | rm_rf 'pkg' 34 | rm_rf 'tmp' 35 | rm_rf 'coverage' 36 | end 37 | 38 | if RUBY_VERSION >= '2.4' && RUBY_ENGINE == 'ruby' 39 | task :default => [:spec, :cucumber, :rubocop] 40 | else 41 | task :default => [:spec, :cucumber] 42 | end 43 | 44 | task :verify_private_key_present do 45 | private_key = File.expand_path('~/.gem/rspec-gem-private_key.pem') 46 | unless File.exist?(private_key) 47 | raise "Your private key is not present. This gem should not be built without it." 48 | end 49 | end 50 | 51 | task :build => :verify_private_key_present 52 | -------------------------------------------------------------------------------- /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/autoload_v_require.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark' 2 | 3 | n = 10 4 | 5 | Benchmark.benchmark do |bm| 6 | 3.times do 7 | bm.report do 8 | n.times do 9 | `bin/rspec benchmarks/example_spec.rb` 10 | end 11 | end 12 | end 13 | end 14 | 15 | # Before autoloading matcher class files 16 | # 0.000000 0.010000 8.800000 ( 8.906383) 17 | # 0.010000 0.010000 8.880000 ( 8.980907) 18 | # 0.000000 0.010000 8.820000 ( 8.918083) 19 | # 20 | # After autoloading matcher class files 21 | # 0.000000 0.010000 8.610000 ( 8.701434) 22 | # 0.010000 0.010000 8.620000 ( 8.741811) 23 | # 0.000000 0.000000 8.580000 ( 8.677235) 24 | # 25 | # Roughly 2.5% improvement in load time (every bit counts!) 26 | -------------------------------------------------------------------------------- /benchmarks/caller_vs_raise_for_backtrace.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark/ips' 2 | 3 | def create_stack_trace(n, &block) 4 | return create_stack_trace(n - 1, &block) if n > 0 5 | yield 6 | end 7 | 8 | [10, 50, 100].each do |frames| 9 | create_stack_trace(frames) do 10 | Benchmark.ips do |x| 11 | x.report("use caller (#{caller.count} frames)") do 12 | exception = RuntimeError.new("boom") 13 | exception.set_backtrace caller 14 | exception.backtrace 15 | end 16 | 17 | x.report("use raise (#{caller.count} frames)") do 18 | exception = begin 19 | raise "boom" 20 | rescue => e 21 | e 22 | end 23 | 24 | exception.backtrace 25 | end 26 | 27 | x.compare! 28 | end 29 | end 30 | end 31 | 32 | __END__ 33 | 34 | Calculating ------------------------------------- 35 | use caller (16 frames) 36 | 4.986k i/100ms 37 | use raise (16 frames) 38 | 4.255k i/100ms 39 | ------------------------------------------------- 40 | use caller (16 frames) 41 | 52.927k (± 9.9%) i/s - 264.258k 42 | use raise (16 frames) 43 | 50.079k (±10.1%) i/s - 251.045k 44 | 45 | Comparison: 46 | use caller (16 frames): 52927.3 i/s 47 | use raise (16 frames): 50078.6 i/s - 1.06x slower 48 | 49 | Calculating ------------------------------------- 50 | use caller (56 frames) 51 | 2.145k i/100ms 52 | use raise (56 frames) 53 | 2.065k i/100ms 54 | ------------------------------------------------- 55 | use caller (56 frames) 56 | 22.282k (± 9.3%) i/s - 111.540k 57 | use raise (56 frames) 58 | 21.428k (± 9.9%) i/s - 107.380k 59 | 60 | Comparison: 61 | use caller (56 frames): 22281.5 i/s 62 | use raise (56 frames): 21428.1 i/s - 1.04x slower 63 | 64 | Calculating ------------------------------------- 65 | use caller (106 frames) 66 | 1.284k i/100ms 67 | use raise (106 frames) 68 | 1.253k i/100ms 69 | ------------------------------------------------- 70 | use caller (106 frames) 71 | 12.437k (±10.6%) i/s - 62.916k 72 | use raise (106 frames) 73 | 10.873k (±12.6%) i/s - 53.879k 74 | 75 | Comparison: 76 | use caller (106 frames): 12437.4 i/s 77 | use raise (106 frames): 10873.2 i/s - 1.14x slower 78 | -------------------------------------------------------------------------------- /benchmarks/cloning_matchers.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark' 2 | require 'rspec/expectations' 3 | include RSpec::Matchers 4 | 5 | n = 1_000_000 6 | matcher = eq(3) 7 | 8 | Benchmark.bm do |x| 9 | x.report do 10 | n.times { matcher.clone } 11 | end 12 | end 13 | 14 | __END__ 15 | 16 | We can do about 1000 clones per ms: 17 | 18 | user system total real 19 | 1.080000 0.030000 1.110000 ( 1.120009) 20 | -------------------------------------------------------------------------------- /benchmarks/count_vs_select_size.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark/ips' 2 | 3 | [10, 100, 1000, 10_000, 100_000].each do |array_size| 4 | array = (1..array_size).to_a 5 | 6 | Benchmark.ips do |ips| 7 | ips.report("#select { true }.size for #{array_size}") do 8 | array.select { true }.size 9 | end 10 | ips.report("#count { true } for #{array_size}") do 11 | array.count { true } 12 | end 13 | end 14 | end 15 | 16 | __END__ 17 | 18 | ruby benchmarks/count_vs_select_size.rb (git)-[main] 19 | Warming up -------------------------------------- 20 | #select { true }.size for 10 21 | 129.033k i/100ms 22 | #count { true } for 10 23 | 168.627k i/100ms 24 | Calculating ------------------------------------- 25 | #select { true }.size for 10 26 | 1.397M (± 6.8%) i/s - 6.968M in 5.011533s 27 | #count { true } for 10 28 | 1.716M (± 7.8%) i/s - 8.600M in 5.048212s 29 | Warming up -------------------------------------- 30 | #select { true }.size for 100 31 | 16.633k i/100ms 32 | #count { true } for 100 33 | 19.215k i/100ms 34 | Calculating ------------------------------------- 35 | #select { true }.size for 100 36 | 170.209k (± 8.1%) i/s - 848.283k in 5.036749s 37 | #count { true } for 100 38 | 212.102k (± 4.1%) i/s - 1.076M in 5.081653s 39 | Warming up -------------------------------------- 40 | #select { true }.size for 1000 41 | 1.650k i/100ms 42 | #count { true } for 1000 43 | 1.803k i/100ms 44 | Calculating ------------------------------------- 45 | #select { true }.size for 1000 46 | 15.651k (±17.0%) i/s - 75.900k in 5.073128s 47 | #count { true } for 1000 48 | 20.613k (± 5.6%) i/s - 104.574k in 5.091257s 49 | Warming up -------------------------------------- 50 | #select { true }.size for 10000 51 | 146.000 i/100ms 52 | #count { true } for 10000 53 | 202.000 i/100ms 54 | Calculating ------------------------------------- 55 | #select { true }.size for 10000 56 | 1.613k (± 8.4%) i/s - 8.030k in 5.014577s 57 | #count { true } for 10000 58 | 2.031k (± 4.8%) i/s - 10.302k in 5.085695s 59 | Warming up -------------------------------------- 60 | #select { true }.size for 100000 61 | 15.000 i/100ms 62 | #count { true } for 100000 63 | 21.000 i/100ms 64 | Calculating ------------------------------------- 65 | #select { true }.size for 100000 66 | 170.963 (± 4.1%) i/s - 855.000 in 5.010050s 67 | #count { true } for 100000 68 | 211.185 (± 4.7%) i/s - 1.071k in 5.083109s 69 | -------------------------------------------------------------------------------- /benchmarks/default_messages_as_methods_v_blocks.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark' 2 | require 'rspec/expectations' 3 | 4 | include RSpec::Expectations 5 | include RSpec::Matchers 6 | 7 | RSpec::Matchers.define :eq_using_dsl do |expected| 8 | match do |actual| 9 | actual == expected 10 | end 11 | end 12 | 13 | n = 10_000 14 | 15 | Benchmark.benchmark do |bm| 16 | 3.times do 17 | bm.report do 18 | n.times do 19 | eq_using_dsl(5).tap do |m| 20 | m.description 21 | m.failure_message_for_should 22 | m.failure_message_for_should_not 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /benchmarks/example_spec.rb: -------------------------------------------------------------------------------- 1 | describe "something" do 2 | it "does something that passes" do 3 | 1.should eq(1) 4 | end 5 | 6 | it "does something that fails" do 7 | 1.should eq(2) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /benchmarks/gsub_vs_tr_single_character.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark/ips' 2 | 3 | Benchmark.ips do |x| 4 | y = '1_2_3_4_5_6_7_8_9_10' 5 | 6 | x.report('gsub') do |_times| 7 | y.tr('_', ' ') 8 | end 9 | 10 | x.report('tr') do |_times| 11 | y.tr('_', ' ') 12 | end 13 | 14 | x.compare! 15 | end 16 | 17 | __END__ 18 | 19 | Calculating ------------------------------------- 20 | gsub 29.483k i/100ms 21 | tr 79.170k i/100ms 22 | ------------------------------------------------- 23 | gsub 10.420B (±23.7%) i/s - 31.106B 24 | tr 78.139B (±20.6%) i/s - 129.289B 25 | 26 | Comparison: 27 | tr: 78139428607.9 i/s 28 | gsub: 10419757735.7 i/s - 7.50x slower 29 | -------------------------------------------------------------------------------- /benchmarks/include_v_superclass.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark' 2 | 3 | n = 10_000 4 | 5 | class Foo; end 6 | module Bar; end 7 | 8 | Benchmark.benchmark do |bm| 9 | puts "Class.new(Foo)" 10 | 11 | 3.times do 12 | bm.report do 13 | n.times do 14 | Class.new(Foo) 15 | end 16 | end 17 | end 18 | 19 | puts "Class.new { include Bar }" 20 | 21 | 3.times do 22 | bm.report do 23 | n.times do 24 | Class.new { include Bar } 25 | end 26 | end 27 | end 28 | end 29 | 30 | # $ ruby benchmarks/include_v_superclass.rb 31 | # Class.new(Foo) 32 | # 0.030000 0.000000 0.030000 ( 0.033536) 33 | # 0.020000 0.000000 0.020000 ( 0.022077) 34 | # 0.040000 0.010000 0.050000 ( 0.035813) 35 | # Class.new { include Bar } 36 | # 0.040000 0.000000 0.040000 ( 0.041427) 37 | # 0.040000 0.000000 0.040000 ( 0.039019) 38 | # 0.030000 0.000000 0.030000 ( 0.037018) 39 | -------------------------------------------------------------------------------- /benchmarks/match_array/rubyprof/passing_with_distinct_items.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift "./lib" 2 | require 'rspec/expectations' 3 | require 'securerandom' 4 | 5 | extend RSpec::Matchers 6 | 7 | actual = Array.new(1000) { SecureRandom.uuid } 8 | expected = actual.shuffle 9 | expect(actual).to match_array(expected) 10 | -------------------------------------------------------------------------------- /benchmarks/method_to_proc.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark' 2 | 3 | n = 10_000_000 4 | 5 | puts "3 runs of #{n} times running #{RUBY_ENGINE}/#{RUBY_VERSION}" 6 | 7 | def foo(x); end 8 | def extract_method_proc(&b); b; end 9 | 10 | Benchmark.benchmark do |bm| 11 | puts "calling foo = method(:foo).to_proc" 12 | foo_proc = method(:foo).to_proc 13 | 14 | 3.times do 15 | bm.report do 16 | n.times { foo_proc.call(1) } 17 | end 18 | end 19 | 20 | puts "calling Proc.new { |x| foo(x) }" 21 | foo_proc = extract_method_proc { |x| foo(x) } 22 | 23 | 3.times do 24 | bm.report do 25 | n.times { foo_proc.call(1) } 26 | end 27 | end 28 | end 29 | 30 | __END__ 31 | 32 | Surprisingly, `Method#to_proc` is slower, except on 1.9.3 where it's a wash. 33 | 34 | 3 runs of 10000000 times running ruby/2.1.1 35 | calling foo = method(:foo).to_proc 36 | 2.190000 0.010000 2.200000 ( 2.206627) 37 | 2.370000 0.010000 2.380000 ( 2.391100) 38 | 2.190000 0.000000 2.190000 ( 2.193119) 39 | calling Proc.new { |x| foo(x) } 40 | 1.640000 0.000000 1.640000 ( 1.648841) 41 | 1.610000 0.000000 1.610000 ( 1.617186) 42 | 1.590000 0.010000 1.600000 ( 1.600570) 43 | 44 | 3 runs of 10000000 times running ruby/2.0.0 45 | calling foo = method(:foo).to_proc 46 | 2.170000 0.010000 2.180000 ( 2.192418) 47 | 2.140000 0.000000 2.140000 ( 2.141015) 48 | 2.150000 0.010000 2.160000 ( 2.172794) 49 | calling Proc.new { |x| foo(x) } 50 | 1.680000 0.000000 1.680000 ( 1.686904) 51 | 1.650000 0.000000 1.650000 ( 1.654465) 52 | 1.640000 0.000000 1.640000 ( 1.648229) 53 | 54 | 3 runs of 10000000 times running ruby/1.9.3 55 | calling foo = method(:foo).to_proc 56 | 2.440000 0.010000 2.450000 ( 2.457211) 57 | 2.430000 0.000000 2.430000 ( 2.450140) 58 | 2.480000 0.010000 2.490000 ( 2.496520) 59 | calling Proc.new { |x| foo(x) } 60 | 2.400000 0.000000 2.400000 ( 2.415641) 61 | 2.480000 0.000000 2.480000 ( 2.489564) 62 | 2.460000 0.000000 2.460000 ( 2.477368) 63 | 64 | 3 runs of 10000000 times running ruby/1.9.2 65 | calling foo = method(:foo).to_proc 66 | 2.490000 0.010000 2.500000 ( 2.502401) 67 | 2.580000 0.000000 2.580000 ( 2.589306) 68 | 2.310000 0.010000 2.320000 ( 2.328342) 69 | calling Proc.new { |x| foo(x) } 70 | 1.860000 0.000000 1.860000 ( 1.866537) 71 | 1.860000 0.000000 1.860000 ( 1.871056) 72 | 1.850000 0.010000 1.860000 ( 1.857426) 73 | -------------------------------------------------------------------------------- /benchmarks/output_stringio_vs_tempfile.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | require 'benchmark' 4 | require 'rspec/expectations' 5 | include RSpec::Matchers 6 | 7 | n = 100_000 8 | 9 | Benchmark.bm(25) do |bm| 10 | bm.report("to_stdout with StringIO") do 11 | n.times { expect {}.not_to output('foo').to_stdout } 12 | end 13 | 14 | bm.report("to_stdout with Tempfile") do 15 | n.times { expect {}.not_to output('foo').to_stdout_from_any_process } 16 | end 17 | 18 | bm.report("to_stderr with StringIO") do 19 | n.times { expect {}.not_to output('foo').to_stderr } 20 | end 21 | 22 | bm.report("to_stderr with Tempfile") do 23 | n.times { expect {}.not_to output('foo').to_stderr_from_any_process } 24 | end 25 | end 26 | 27 | # user system total real 28 | # to_stdout with StringIO 0.470000 0.010000 0.480000 ( 0.467317) 29 | # to_stdout with Tempfile 8.920000 7.420000 16.340000 ( 16.355174) 30 | # to_stderr with StringIO 0.460000 0.000000 0.460000 ( 0.454059) 31 | # to_stderr with Tempfile 8.930000 7.560000 16.490000 ( 16.494696) 32 | -------------------------------------------------------------------------------- /benchmarks/set_vs_array_include.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark' 2 | require 'set' 3 | 4 | n = 10_000_000 5 | 6 | array = [ 7 | :@name, :@declarations, :@diffable, :@messages, 8 | :@match_block, :@match_for_should_not_block, 9 | :@expected_exception 10 | ] 11 | set = array.to_set 12 | 13 | puts "Positive examples: " 14 | Benchmark.bm(25) do |x| 15 | array.each_with_index do |var, i| 16 | x.report("set.include?(item #{i}) ") do 17 | n.times { set.include?(var) } 18 | end 19 | 20 | x.report("array.include?(item #{i})") do 21 | n.times { array.include?(var) } 22 | end 23 | 24 | puts "=" * 80 25 | end 26 | end 27 | 28 | puts "\n\nNegative examples: " 29 | Benchmark.bm(5) do |x| 30 | x.report("set ") do 31 | n.times { set.include?(:@other) } 32 | end 33 | 34 | x.report("array") do 35 | n.times { array.include?(:@other) } 36 | end 37 | end 38 | 39 | # Positive examples: 40 | # user system total real 41 | # set.include?(item 0) 2.000000 0.010000 2.010000 ( 1.999305) 42 | # array.include?(item 0) 1.170000 0.000000 1.170000 ( 1.173168) 43 | # ================================================================================ 44 | # set.include?(item 1) 2.020000 0.000000 2.020000 ( 2.016389) 45 | # array.include?(item 1) 1.580000 0.000000 1.580000 ( 1.585301) 46 | # ================================================================================ 47 | # set.include?(item 2) 1.980000 0.010000 1.990000 ( 1.984699) 48 | # array.include?(item 2) 2.170000 0.000000 2.170000 ( 2.167163) 49 | # ================================================================================ 50 | # set.include?(item 3) 2.110000 0.010000 2.120000 ( 2.125914) 51 | # array.include?(item 3) 2.450000 0.000000 2.450000 ( 2.445224) 52 | # ================================================================================ 53 | # set.include?(item 4) 2.090000 0.010000 2.100000 ( 2.094182) 54 | # array.include?(item 4) 2.920000 0.000000 2.920000 ( 2.924850) 55 | # ================================================================================ 56 | # set.include?(item 5) 2.000000 0.000000 2.000000 ( 2.000656) 57 | # array.include?(item 5) 3.540000 0.010000 3.550000 ( 3.547563) 58 | # ================================================================================ 59 | # set.include?(item 6) 2.030000 0.000000 2.030000 ( 2.032430) 60 | # array.include?(item 6) 3.800000 0.010000 3.810000 ( 3.810014) 61 | # ================================================================================ 62 | 63 | # Negative examples: 64 | # user system total real 65 | # set 1.940000 0.000000 1.940000 ( 1.941780) 66 | # array 4.240000 0.010000 4.250000 ( 4.238137) 67 | -------------------------------------------------------------------------------- /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 | NOT_RUBY_1_9_TAG = USE_TILDE_TAGS ? '~@ruby-1.9' : '"not @ruby-1.9"' 6 | 7 | def tags(tag = NOT_WIP_TAG) 8 | tags = [tag] 9 | tags << NOT_RUBY_1_9_TAG if RUBY_VERSION.to_f < 1.9 10 | tags.join(" --tags ") 11 | end 12 | %> 13 | 14 | default: --require features --tags <%= tags %> --format progress 15 | wip: --require features --tags @wip:3 --wip features 16 | -------------------------------------------------------------------------------- /features/.nav: -------------------------------------------------------------------------------- 1 | - built_in_matchers: 2 | - equality.feature 3 | - comparisons.feature 4 | - predicates.feature 5 | - types.feature 6 | - all.feature 7 | - be.feature 8 | - be_within.feature 9 | - exist.feature 10 | - change.feature 11 | - contain_exactly.feature 12 | - cover.feature 13 | - end_with.feature 14 | - exist.feature 15 | - have.feature 16 | - have_attributes.feature 17 | - include.feature 18 | - match.feature 19 | - operators.feature 20 | - raise_error.feature 21 | - respond_to.feature 22 | - satisfy.feature 23 | - start_with.feature 24 | - throw_symbol.feature 25 | - yield.feature 26 | - custom_matchers: 27 | - define_matcher.feature 28 | - define_diffable_matcher.feature 29 | - define_matcher_with_fluent_interface.feature 30 | - access_running_example.feature 31 | - define_matcher_outside_rspec.feature 32 | - aggregating_failures.feature 33 | - composing_matchers.feature 34 | - compound_expectations.feature 35 | - define_negated_matcher.feature 36 | - customized_message.feature 37 | - diffing.feature 38 | - implicit_docstrings.feature 39 | - syntax_configuration.feature 40 | - test_frameworks: 41 | - test_unit.feature 42 | - Changelog.md 43 | -------------------------------------------------------------------------------- /features/README.md: -------------------------------------------------------------------------------- 1 | # RSpec Expectations 2 | 3 | rspec-expectations is used to define expected outcomes. 4 | 5 | ```ruby 6 | RSpec.describe Account do 7 | it "has a balance of zero when first created" do 8 | expect(Account.new.balance).to eq(Money.new(0)) 9 | end 10 | end 11 | ``` 12 | 13 | ## Basic structure 14 | 15 | The basic structure of an rspec expectation is: 16 | 17 | ```ruby 18 | expect(actual).to matcher(expected) 19 | expect(actual).not_to matcher(expected) 20 | ``` 21 | 22 | Note: You can also use `expect(..).to_not` instead of `expect(..).not_to`. 23 | One is an alias to the other, so you can use whichever reads better to you. 24 | 25 | #### Examples 26 | 27 | ```ruby 28 | expect(5).to eq(5) 29 | expect(5).not_to eq(4) 30 | ``` 31 | 32 | ## What is a matcher? 33 | 34 | A matcher is any object that responds to the following methods: 35 | 36 | ```ruby 37 | matches?(actual) 38 | failure_message 39 | ``` 40 | 41 | These methods are also part of the matcher protocol, but are optional: 42 | 43 | ```ruby 44 | does_not_match?(actual) 45 | failure_message_when_negated 46 | description 47 | supports_block_expectations? 48 | ``` 49 | 50 | RSpec ships with a number of built-in matchers and a DSL for writing custom 51 | matchers. 52 | 53 | ## Issues 54 | 55 | The documentation for rspec-expectations is a work in progress. We'll be adding 56 | Cucumber features over time, and clarifying existing ones. If you have 57 | specific features you'd like to see added, find the existing documentation 58 | incomplete or confusing, or, better yet, wish to write a missing Cucumber 59 | feature yourself, please [submit an 60 | issue](http://github.com/rspec/rspec-expectations/issues) or a [pull 61 | request](http://github.com/rspec/rspec-expectations). 62 | -------------------------------------------------------------------------------- /features/aggregating_failures.feature: -------------------------------------------------------------------------------- 1 | Feature: Aggregating Failures 2 | 3 | Normally, an expectation failure causes the example to immediately abort. When you have multiple independent expectations, it's nice to be able to see all of the failures rather than just the first. One solution is to split off a separate example for each expectation, but if the setup for the examples is slow, that's going to take extra time and slow things down. `aggregate_failures` provides an alternate solution. It wraps a set of expectations with a block. Within the block, expectation failures will not immediatly abort like normal; instead, the failures will be aggregated into a single exception that is raised at the end of the block, allowing you to see all expectations that failed. 4 | 5 | `aggregate_failures` takes an optional string argument that will be used in the aggregated failure message as a label. 6 | 7 | RSpec::Core expands this feature a bit; see [the rspec-core docs](../rspec-core/expectation-framework-integration/aggregating-failures) for more detail. 8 | 9 | Note: The implementation of `aggregate_failures` uses a thread-local variable, which means that if you have an expectation failure in another thread, it'll abort like normal. 10 | 11 | Scenario: Multiple expectation failures within `aggregate_failures` are all reported 12 | Given a file named "spec/aggregated_failure_spec.rb" with: 13 | """ruby 14 | require 'rspec/expectations' 15 | include RSpec::Matchers 16 | 17 | Response = Struct.new(:status, :headers, :body) 18 | response = Response.new(404, { "Content-Type" => "text/plain" }, "Not Found") 19 | 20 | begin 21 | aggregate_failures "testing response" do 22 | expect(response.status).to eq(200) 23 | expect(response.headers["Content-Type"]).to eq("application/json") 24 | expect(response.body).to eq('{"message":"Success"}') 25 | end 26 | rescue RSpec::Expectations::MultipleExpectationsNotMetError => e 27 | puts e.message.gsub(/(:in).+/, '') 28 | exit(1) 29 | end 30 | """ 31 | When I run `ruby spec/aggregated_failure_spec.rb` 32 | Then it should fail with: 33 | """ 34 | Got 3 failures from failure aggregation block "testing response": 35 | 36 | 1) expected: 200 37 | got: 404 38 | 39 | (compared using ==) 40 | 41 | spec/aggregated_failure_spec.rb:9 42 | 43 | 2) expected: "application/json" 44 | got: "text/plain" 45 | 46 | (compared using ==) 47 | 48 | spec/aggregated_failure_spec.rb:10 49 | 50 | 3) expected: "{"message":"Success"}" 51 | got: "Not Found" 52 | 53 | (compared using ==) 54 | 55 | spec/aggregated_failure_spec.rb:11 56 | """ 57 | -------------------------------------------------------------------------------- /features/built_in_matchers/all.feature: -------------------------------------------------------------------------------- 1 | Feature: `all` matcher 2 | 3 | Use the `all` matcher to specify that a collection's objects all pass an expected matcher. This works on any enumerable object. 4 | 5 | ```ruby 6 | expect([1, 3, 5]).to all( be_odd ) 7 | expect([1, 3, 5]).to all( be_an(Integer) ) 8 | expect([1, 3, 5]).to all( be < 10 ) 9 | expect([1, 3, 4]).to all( be_odd ) # fails 10 | ``` 11 | 12 | The matcher also supports compound matchers: 13 | 14 | ```ruby 15 | expect([1, 3, 5]).to all( be_odd.and be < 10 ) 16 | expect([1, 4, 21]).to all( be_odd.or be < 10 ) 17 | ``` 18 | 19 | If you are looking for "any" member of a collection that passes an expectation, look at the `include`-matcher. 20 | 21 | Scenario: Array usage 22 | Given a file named "array_all_matcher_spec.rb" with: 23 | """ruby 24 | RSpec.describe [1, 3, 5] do 25 | it { is_expected.to all( be_odd ) } 26 | it { is_expected.to all( be_an(Integer) ) } 27 | it { is_expected.to all( be < 10 ) } 28 | 29 | # deliberate failures 30 | it { is_expected.to all( be_even ) } 31 | it { is_expected.to all( be_a(String) ) } 32 | it { is_expected.to all( be > 2 ) } 33 | end 34 | """ 35 | When I run `rspec array_all_matcher_spec.rb` 36 | Then the output should contain all of these: 37 | | 6 examples, 3 failures | 38 | | expected [1, 3, 5] to all be even | 39 | | expected [1, 3, 5] to all be a kind of String | 40 | | expected [1, 3, 5] to all be > 2 | 41 | 42 | Scenario: Compound matcher usage 43 | Given a file named "compound_all_matcher_spec.rb" with: 44 | """ruby 45 | RSpec.describe ['anything', 'everything', 'something'] do 46 | it { is_expected.to all( be_a(String).and include("thing") ) } 47 | it { is_expected.to all( be_a(String).and end_with("g") ) } 48 | it { is_expected.to all( start_with("s").or include("y") ) } 49 | 50 | # deliberate failures 51 | it { is_expected.to all( include("foo").and include("bar") ) } 52 | it { is_expected.to all( be_a(String).and start_with("a") ) } 53 | it { is_expected.to all( start_with("a").or include("z") ) } 54 | end 55 | """ 56 | When I run `rspec compound_all_matcher_spec.rb` 57 | Then the output should contain all of these: 58 | | 6 examples, 3 failures | 59 | | expected ["anything", "everything", "something"] to all include "foo" and include "bar" | 60 | | expected ["anything", "everything", "something"] to all be a kind of String and start with "a" | 61 | | expected ["anything", "everything", "something"] to all start with "a" or include "z" | 62 | -------------------------------------------------------------------------------- /features/built_in_matchers/be_within.feature: -------------------------------------------------------------------------------- 1 | Feature: `be_within` matcher 2 | 3 | Normal equality expectations do not work well for floating point values. 4 | Consider this irb session: 5 | 6 | ```shell 7 | > radius = 3 8 | => 3 9 | > area_of_circle = radius * radius * Math::PI 10 | => 28.2743338823081 11 | > area_of_circle == 28.2743338823081 12 | => false 13 | ``` 14 | 15 | Instead, you should use the `be_within` matcher to check that the value is within a delta of 16 | your expected value: 17 | 18 | ```ruby 19 | expect(area_of_circle).to be_within(0.1).of(28.3) 20 | ``` 21 | 22 | Note that the difference between the actual and expected values must be smaller than your 23 | delta; if it is equal, the matcher will fail. 24 | 25 | Scenario: Basic usage 26 | Given a file named "be_within_matcher_spec.rb" with: 27 | """ruby 28 | RSpec.describe 27.5 do 29 | it { is_expected.to be_within(0.5).of(27.9) } 30 | it { is_expected.to be_within(0.5).of(28.0) } 31 | it { is_expected.to be_within(0.5).of(27.1) } 32 | it { is_expected.to be_within(0.5).of(27.0) } 33 | 34 | it { is_expected.not_to be_within(0.5).of(28.1) } 35 | it { is_expected.not_to be_within(0.5).of(26.9) } 36 | 37 | # deliberate failures 38 | it { is_expected.not_to be_within(0.5).of(28) } 39 | it { is_expected.not_to be_within(0.5).of(27) } 40 | it { is_expected.to be_within(0.5).of(28.1) } 41 | it { is_expected.to be_within(0.5).of(26.9) } 42 | end 43 | """ 44 | When I run `rspec be_within_matcher_spec.rb` 45 | Then the output should contain all of these: 46 | | 10 examples, 4 failures | 47 | | expected 27.5 not to be within 0.5 of 28 | 48 | | expected 27.5 not to be within 0.5 of 27 | 49 | | expected 27.5 to be within 0.5 of 28.1 | 50 | | expected 27.5 to be within 0.5 of 26.9 | 51 | -------------------------------------------------------------------------------- /features/built_in_matchers/change.feature: -------------------------------------------------------------------------------- 1 | Feature: `change` matcher 2 | 3 | The `change` matcher is used to specify that a block of code changes some mutable state. 4 | You can specify what will change using either of two forms: 5 | 6 | * `expect { do_something }.to change(object, :attribute)` 7 | * `expect { do_something }.to change { object.attribute }` 8 | 9 | You can further qualify the change by chaining `from` and/or `to` or one of `by`, `by_at_most`, 10 | `by_at_least`. 11 | 12 | Background: 13 | Given a file named "lib/counter.rb" with: 14 | """ruby 15 | class Counter 16 | class << self 17 | def increment 18 | @count ||= 0 19 | @count += 1 20 | end 21 | 22 | def count 23 | @count ||= 0 24 | end 25 | end 26 | end 27 | """ 28 | 29 | @skip-when-ripper-unsupported 30 | Scenario: Expect change 31 | Given a file named "spec/example_spec.rb" with: 32 | """ruby 33 | require "counter" 34 | 35 | RSpec.describe Counter, "#increment" do 36 | it "should increment the count" do 37 | expect { Counter.increment }.to change { Counter.count }.from(0).to(1) 38 | end 39 | 40 | # deliberate failure 41 | it "should increment the count by 2" do 42 | expect { Counter.increment }.to change { Counter.count }.by(2) 43 | end 44 | end 45 | """ 46 | When I run `rspec spec/example_spec.rb` 47 | Then the output should contain "1 failure" 48 | Then the output should contain "expected `Counter.count` to have changed by 2, but was changed by 1" 49 | 50 | @skip-when-ripper-unsupported 51 | Scenario: Expect no change 52 | Given a file named "spec/example_spec.rb" with: 53 | """ruby 54 | require "counter" 55 | 56 | RSpec.describe Counter, "#increment" do 57 | it "should not increment the count by 1 (using not_to)" do 58 | expect { Counter.increment }.not_to change { Counter.count } 59 | end 60 | 61 | it "should not increment the count by 1 (using to_not)" do 62 | expect { Counter.increment }.to_not change { Counter.count } 63 | end 64 | end 65 | """ 66 | When I run `rspec spec/example_spec.rb` 67 | Then the output should contain "2 failures" 68 | Then the output should contain "expected `Counter.count` not to have changed, but did change from 1 to 2" 69 | -------------------------------------------------------------------------------- /features/built_in_matchers/comparisons.feature: -------------------------------------------------------------------------------- 1 | Feature: Comparison matchers 2 | 3 | RSpec provides a number of matchers that are based on Ruby's built-in operators. These 4 | can be used for generalized comparison of values. E.g. 5 | 6 | ```ruby 7 | expect(9).to be > 6 8 | expect(3).to be <= 3 9 | expect(1).to be < 6 10 | expect('a').to be < 'b' 11 | ``` 12 | 13 | Scenario: Numeric operator matchers 14 | Given a file named "numeric_operator_matchers_spec.rb" with: 15 | """ruby 16 | RSpec.describe 18 do 17 | it { is_expected.to be < 20 } 18 | it { is_expected.to be > 15 } 19 | it { is_expected.to be <= 19 } 20 | it { is_expected.to be >= 17 } 21 | 22 | # deliberate failures 23 | it { is_expected.to be < 15 } 24 | it { is_expected.to be > 20 } 25 | it { is_expected.to be <= 17 } 26 | it { is_expected.to be >= 19 } 27 | it { is_expected.to be < 'a' } 28 | end 29 | 30 | RSpec.describe 'a' do 31 | it { is_expected.to be < 'b' } 32 | 33 | # deliberate failures 34 | it { is_expected.to be < 18 } 35 | end 36 | """ 37 | When I run `rspec numeric_operator_matchers_spec.rb` 38 | Then the output should contain "11 examples, 6 failures" 39 | And the output should contain: 40 | """ 41 | Failure/Error: it { is_expected.to be < 15 } 42 | 43 | expected: < 15 44 | got: 18 45 | """ 46 | And the output should contain: 47 | """ 48 | Failure/Error: it { is_expected.to be > 20 } 49 | 50 | expected: > 20 51 | got: 18 52 | """ 53 | And the output should contain: 54 | """ 55 | Failure/Error: it { is_expected.to be <= 17 } 56 | 57 | expected: <= 17 58 | got: 18 59 | """ 60 | And the output should contain: 61 | """ 62 | Failure/Error: it { is_expected.to be >= 19 } 63 | 64 | expected: >= 19 65 | got: 18 66 | """ 67 | And the output should contain: 68 | """ 69 | Failure/Error: it { is_expected.to be < 'a' } 70 | 71 | expected: < "a" 72 | got: 18 73 | """ 74 | And the output should contain: 75 | """ 76 | Failure/Error: it { is_expected.to be < 18 } 77 | 78 | expected: < 18 79 | got: "a" 80 | """ 81 | 82 | 83 | Scenario: String operator matchers 84 | Given a file named "string_operator_matchers_spec.rb" with: 85 | """ruby 86 | RSpec.describe "Strawberry" do 87 | it { is_expected.to be < "Tomato" } 88 | it { is_expected.to be > "Apple" } 89 | it { is_expected.to be <= "Turnip" } 90 | it { is_expected.to be >= "Banana" } 91 | 92 | # deliberate failures 93 | it { is_expected.to be < "Cranberry" } 94 | it { is_expected.to be > "Zuchini" } 95 | it { is_expected.to be <= "Potato" } 96 | it { is_expected.to be >= "Tomato" } 97 | end 98 | """ 99 | When I run `rspec string_operator_matchers_spec.rb` 100 | Then the output should contain "8 examples, 4 failures" 101 | And the output should contain: 102 | """ 103 | Failure/Error: it { is_expected.to be < "Cranberry" } 104 | 105 | expected: < "Cranberry" 106 | got: "Strawberry" 107 | """ 108 | And the output should contain: 109 | """ 110 | Failure/Error: it { is_expected.to be > "Zuchini" } 111 | 112 | expected: > "Zuchini" 113 | got: "Strawberry" 114 | """ 115 | And the output should contain: 116 | """ 117 | Failure/Error: it { is_expected.to be <= "Potato" } 118 | 119 | expected: <= "Potato" 120 | got: "Strawberry" 121 | """ 122 | And the output should contain: 123 | """ 124 | Failure/Error: it { is_expected.to be >= "Tomato" } 125 | 126 | expected: >= "Tomato" 127 | got: "Strawberry" 128 | """ 129 | -------------------------------------------------------------------------------- /features/built_in_matchers/contain_exactly.feature: -------------------------------------------------------------------------------- 1 | Feature: `contain_exactly` matcher 2 | 3 | The `contain_exactly` matcher provides a way to test arrays against each other in a way 4 | that disregards differences in the ordering between the actual and expected array. 5 | For example: 6 | 7 | ```ruby 8 | expect([1, 2, 3]).to contain_exactly(2, 3, 1) # pass 9 | expect([:a, :c, :b]).to contain_exactly(:a, :c ) # fail 10 | ``` 11 | 12 | This matcher is also available as `match_array`, which expects the expected array to be 13 | given as a single array argument rather than as individual splatted elements. The above 14 | could also be written as: 15 | 16 | ```ruby 17 | expect([1, 2, 3]).to match_array [2, 3, 1] # pass 18 | expect([:a, :c, :b]).to match_array [:a, :c] # fail 19 | ``` 20 | 21 | Scenario: Array is expected to contain every value 22 | Given a file named "contain_exactly_matcher_spec.rb" with: 23 | """ruby 24 | RSpec.describe [1, 2, 3] do 25 | it { is_expected.to contain_exactly(1, 2, 3) } 26 | it { is_expected.to contain_exactly(1, 3, 2) } 27 | it { is_expected.to contain_exactly(2, 1, 3) } 28 | it { is_expected.to contain_exactly(2, 3, 1) } 29 | it { is_expected.to contain_exactly(3, 1, 2) } 30 | it { is_expected.to contain_exactly(3, 2, 1) } 31 | 32 | # deliberate failures 33 | it { is_expected.to contain_exactly(1, 2, 1) } 34 | end 35 | """ 36 | When I run `rspec contain_exactly_matcher_spec.rb` 37 | Then the output should contain "7 examples, 1 failure" 38 | And the output should contain: 39 | """ 40 | Failure/Error: it { is_expected.to contain_exactly(1, 2, 1) } 41 | 42 | expected collection contained: [1, 1, 2] 43 | actual collection contained: [1, 2, 3] 44 | the missing elements were: [1] 45 | the extra elements were: [3] 46 | """ 47 | 48 | Scenario: Array is not expected to contain every value 49 | Given a file named "contain_exactly_matcher_spec.rb" with: 50 | """ruby 51 | RSpec.describe [1, 2, 3] do 52 | it { is_expected.to_not contain_exactly(1, 2, 3, 4) } 53 | it { is_expected.to_not contain_exactly(1, 2) } 54 | 55 | # deliberate failures 56 | it { is_expected.to_not contain_exactly(1, 3, 2) } 57 | end 58 | """ 59 | When I run `rspec contain_exactly_matcher_spec.rb` 60 | Then the output should contain "3 examples, 1 failure" 61 | And the output should contain: 62 | """ 63 | Failure/Error: it { is_expected.to_not contain_exactly(1, 3, 2) } 64 | expected [1, 2, 3] not to contain exactly 1, 3, and 2 65 | """ 66 | -------------------------------------------------------------------------------- /features/built_in_matchers/cover.feature: -------------------------------------------------------------------------------- 1 | @ruby-1.9 2 | Feature: `cover` matcher 3 | 4 | Use the `cover` matcher to specify that a range covers one or more 5 | expected objects. This works on any object that responds to `#cover?` 6 | (such as a `Range`): 7 | 8 | ```ruby 9 | expect(1..10).to cover(5) 10 | expect(1..10).to cover(4, 6) 11 | expect(1..10).not_to cover(11) 12 | ``` 13 | 14 | Scenario: Range usage 15 | Given a file named "range_cover_matcher_spec.rb" with: 16 | """ruby 17 | RSpec.describe (1..10) do 18 | it { is_expected.to cover(4) } 19 | it { is_expected.to cover(6) } 20 | it { is_expected.to cover(8) } 21 | it { is_expected.to cover(4, 6) } 22 | it { is_expected.to cover(4, 6, 8) } 23 | it { is_expected.not_to cover(11) } 24 | it { is_expected.not_to cover(11, 12) } 25 | 26 | # deliberate failures 27 | it { is_expected.to cover(11) } 28 | it { is_expected.not_to cover(4) } 29 | it { is_expected.not_to cover(6) } 30 | it { is_expected.not_to cover(8) } 31 | it { is_expected.not_to cover(4, 6, 8) } 32 | 33 | # both of these should fail since it covers 5 but not 11 34 | it { is_expected.to cover(5, 11) } 35 | it { is_expected.not_to cover(5, 11) } 36 | end 37 | """ 38 | When I run `rspec range_cover_matcher_spec.rb` 39 | Then the output should contain all of these: 40 | | 14 examples, 7 failures | 41 | | expected 1..10 to cover 11 | 42 | | expected 1..10 not to cover 4 | 43 | | expected 1..10 not to cover 6 | 44 | | expected 1..10 not to cover 8 | 45 | | expected 1..10 not to cover 4, 6, and 8 | 46 | | expected 1..10 to cover 5 and 11 | 47 | | expected 1..10 not to cover 5 and 11 | 48 | -------------------------------------------------------------------------------- /features/built_in_matchers/end_with.feature: -------------------------------------------------------------------------------- 1 | Feature: `end_with` matcher 2 | 3 | Use the `end_with` matcher to specify that a string or array ends with the expected 4 | characters or elements. 5 | 6 | ```ruby 7 | expect("this string").to end_with "string" 8 | expect("this string").not_to end_with "stringy" 9 | expect([0, 1, 2]).to end_with 1, 2 10 | ``` 11 | 12 | Scenario: String usage 13 | Given a file named "example_spec.rb" with: 14 | """ruby 15 | RSpec.describe "this string" do 16 | it { is_expected.to end_with "string" } 17 | it { is_expected.not_to end_with "stringy" } 18 | 19 | # deliberate failures 20 | it { is_expected.not_to end_with "string" } 21 | it { is_expected.to end_with "stringy" } 22 | end 23 | """ 24 | When I run `rspec example_spec.rb` 25 | Then the output should contain all of these: 26 | | 4 examples, 2 failures | 27 | | expected "this string" not to end with "string" | 28 | | expected "this string" to end with "stringy" | 29 | 30 | Scenario: Array usage 31 | Given a file named "example_spec.rb" with: 32 | """ruby 33 | RSpec.describe [0, 1, 2, 3, 4] do 34 | it { is_expected.to end_with 4 } 35 | it { is_expected.to end_with 3, 4 } 36 | it { is_expected.not_to end_with 3 } 37 | it { is_expected.not_to end_with 0, 1, 2, 3, 4, 5 } 38 | 39 | # deliberate failures 40 | it { is_expected.not_to end_with 4 } 41 | it { is_expected.to end_with 3 } 42 | end 43 | """ 44 | When I run `rspec example_spec.rb` 45 | Then the output should contain all of these: 46 | | 6 examples, 2 failures | 47 | | expected [0, 1, 2, 3, 4] not to end with 4 | 48 | | expected [0, 1, 2, 3, 4] to end with 3 | 49 | -------------------------------------------------------------------------------- /features/built_in_matchers/exist.feature: -------------------------------------------------------------------------------- 1 | Feature: `exist` matcher 2 | 3 | The `exist` matcher is used to specify that something exists (as indicated by `#exist?` or `#exists?`): 4 | 5 | ```ruby 6 | expect(obj).to exist # passes if obj.exist? or obj.exists? 7 | ``` 8 | 9 | Scenario: Basic usage 10 | Given a file named "exist_matcher_spec.rb" with: 11 | """ruby 12 | class Planet 13 | attr_reader :name 14 | 15 | def initialize(name) 16 | @name = name 17 | end 18 | 19 | def inspect 20 | "" 21 | end 22 | 23 | def exist? # also works with exists? 24 | %w[Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune].include?(name) 25 | end 26 | end 27 | 28 | RSpec.describe "Earth" do 29 | let(:earth) { Planet.new("Earth") } 30 | specify { expect(earth).to exist } 31 | specify { expect(earth).not_to exist } # deliberate failure 32 | end 33 | 34 | RSpec.describe "Tatooine" do 35 | let(:tatooine) { Planet.new("Tatooine") } 36 | specify { expect(tatooine).to exist } # deliberate failure 37 | specify { expect(tatooine).not_to exist } 38 | end 39 | """ 40 | When I run `rspec exist_matcher_spec.rb` 41 | Then the output should contain all of these: 42 | | 4 examples, 2 failures | 43 | | expected not to exist | 44 | | expected to exist | 45 | -------------------------------------------------------------------------------- /features/built_in_matchers/have_attributes.feature: -------------------------------------------------------------------------------- 1 | Feature: `have_attributes` matcher 2 | 3 | Use the have_attributes matcher to specify that an object's attributes match the expected attributes: 4 | 5 | ```ruby 6 | Person = Struct.new(:name, :age) 7 | person = Person.new("Jim", 32) 8 | 9 | expect(person).to have_attributes(:name => "Jim", :age => 32) 10 | expect(person).to have_attributes(:name => a_string_starting_with("J"), :age => (a_value > 30) ) 11 | ``` 12 | 13 | The matcher will fail if actual doesn't respond to any of the expected attributes: 14 | 15 | ```ruby 16 | expect(person).to have_attributes(:name => "Jim", :color => 'red') 17 | ``` 18 | 19 | Scenario: Basic usage 20 | Given a file named "basic_have_attributes_matcher_spec.rb" with: 21 | """ruby 22 | Person = Struct.new(:name, :age) 23 | 24 | RSpec.describe Person.new("Jim", 32) do 25 | it { is_expected.to have_attributes(:name => "Jim") } 26 | it { is_expected.to have_attributes(:name => a_string_starting_with("J") ) } 27 | it { is_expected.to have_attributes(:age => 32) } 28 | it { is_expected.to have_attributes(:age => (a_value > 30) ) } 29 | it { is_expected.to have_attributes(:name => "Jim", :age => 32) } 30 | it { is_expected.to have_attributes(:name => a_string_starting_with("J"), :age => (a_value > 30) ) } 31 | it { is_expected.not_to have_attributes(:name => "Bob") } 32 | it { is_expected.not_to have_attributes(:age => 10) } 33 | it { is_expected.not_to have_attributes(:age => (a_value < 30) ) } 34 | 35 | # deliberate failures 36 | it { is_expected.to have_attributes(:name => "Bob") } 37 | it { is_expected.to have_attributes(:age => 10) } 38 | 39 | # fails if any of the attributes don't match 40 | it { is_expected.to have_attributes(:name => "Bob", :age => 32) } 41 | it { is_expected.to have_attributes(:name => "Jim", :age => 10) } 42 | it { is_expected.to have_attributes(:name => "Bob", :age => 10) } 43 | end 44 | """ 45 | When I run `rspec basic_have_attributes_matcher_spec.rb` 46 | Then the output should contain "14 examples, 5 failures" 47 | -------------------------------------------------------------------------------- /features/built_in_matchers/match.feature: -------------------------------------------------------------------------------- 1 | Feature: `match` matcher 2 | 3 | The `match` matcher calls `#match` on the object, passing if `#match` returns a truthy (not 4 | `false` or `nil`) value. `Regexp` and `String` both provide a `#match` method. 5 | 6 | ```ruby 7 | expect("a string").to match(/str/) # passes 8 | expect("a string").to match(/foo/) # fails 9 | expect(/foo/).to match("food") # passes 10 | expect(/foo/).to match("drinks") # fails 11 | ``` 12 | 13 | You can also use this matcher to match nested data structures when composing matchers. 14 | 15 | Scenario: String usage 16 | Given a file named "string_match_spec.rb" with: 17 | """ruby 18 | RSpec.describe "a string" do 19 | it { is_expected.to match(/str/) } 20 | it { is_expected.not_to match(/foo/) } 21 | 22 | # deliberate failures 23 | it { is_expected.not_to match(/str/) } 24 | it { is_expected.to match(/foo/) } 25 | end 26 | """ 27 | When I run `rspec string_match_spec.rb` 28 | Then the output should contain all of these: 29 | | 4 examples, 2 failures | 30 | | expected "a string" not to match /str/ | 31 | | expected "a string" to match /foo/ | 32 | 33 | Scenario: Regular expression usage 34 | Given a file named "regexp_match_spec.rb" with: 35 | """ruby 36 | RSpec.describe /foo/ do 37 | it { is_expected.to match("food") } 38 | it { is_expected.not_to match("drinks") } 39 | 40 | # deliberate failures 41 | it { is_expected.not_to match("food") } 42 | it { is_expected.to match("drinks") } 43 | end 44 | """ 45 | When I run `rspec regexp_match_spec.rb` 46 | Then the output should contain all of these: 47 | | 4 examples, 2 failures | 48 | | expected /foo/ not to match "food" | 49 | | expected /foo/ to match "drinks" | 50 | -------------------------------------------------------------------------------- /features/built_in_matchers/satisfy.feature: -------------------------------------------------------------------------------- 1 | Feature: `satisfy` matcher 2 | 3 | The `satisfy` matcher is extremely flexible and can handle almost anything you want to 4 | specify. It passes if the block you provide returns true: 5 | 6 | ```ruby 7 | expect(10).to satisfy { |v| v % 5 == 0 } 8 | expect(7).not_to satisfy { |v| v % 5 == 0 } 9 | ``` 10 | 11 | The default failure message ("expected [actual] to satisfy block") is not very descriptive or helpful. 12 | To add clarification, you can provide your own description as an argument: 13 | 14 | ```ruby 15 | expect(10).to satisfy("be a multiple of 5") do |v| 16 | v % 5 == 0 17 | end 18 | ``` 19 | 20 | @skip-when-ripper-unsupported 21 | Scenario: Basic usage 22 | Given a file named "satisfy_matcher_spec.rb" with: 23 | """ruby 24 | RSpec.describe 10 do 25 | it { is_expected.to satisfy { |v| v > 5 } } 26 | it { is_expected.not_to satisfy { |v| v > 15 } } 27 | 28 | # deliberate failures 29 | it { is_expected.not_to satisfy { |v| v > 5 } } 30 | it { is_expected.to satisfy { |v| v > 15 } } 31 | it { is_expected.to_not satisfy("be greater than 5") { |v| v > 5 } } 32 | it { is_expected.to satisfy("be greater than 15") { |v| v > 15 } } 33 | end 34 | """ 35 | When I run `rspec satisfy_matcher_spec.rb` 36 | Then the output should contain all of these: 37 | | 6 examples, 4 failures | 38 | | expected 10 not to satisfy expression `v > 5` | 39 | | expected 10 to satisfy expression `v > 15` | 40 | | expected 10 not to be greater than 5 | 41 | | expected 10 to be greater than 15 | 42 | -------------------------------------------------------------------------------- /features/built_in_matchers/start_with.feature: -------------------------------------------------------------------------------- 1 | Feature: `start_with` matcher 2 | 3 | Use the `start_with` matcher to specify that a string or array starts with the expected 4 | characters or elements. 5 | 6 | ```ruby 7 | expect("this string").to start_with("this") 8 | expect("this string").not_to start_with("that") 9 | expect([0,1,2]).to start_with(0, 1) 10 | ``` 11 | 12 | Scenario: With a string 13 | Given a file named "example_spec.rb" with: 14 | """ruby 15 | RSpec.describe "this string" do 16 | it { is_expected.to start_with "this" } 17 | it { is_expected.not_to start_with "that" } 18 | 19 | # deliberate failures 20 | it { is_expected.not_to start_with "this" } 21 | it { is_expected.to start_with "that" } 22 | end 23 | """ 24 | When I run `rspec example_spec.rb` 25 | Then the output should contain all of these: 26 | | 4 examples, 2 failures | 27 | | expected "this string" not to start with "this" | 28 | | expected "this string" to start with "that" | 29 | 30 | Scenario: With an array 31 | Given a file named "example_spec.rb" with: 32 | """ruby 33 | RSpec.describe [0, 1, 2, 3, 4] do 34 | it { is_expected.to start_with 0 } 35 | it { is_expected.to start_with(0, 1)} 36 | it { is_expected.not_to start_with(2) } 37 | it { is_expected.not_to start_with(0, 1, 2, 3, 4, 5) } 38 | 39 | # deliberate failures 40 | it { is_expected.not_to start_with 0 } 41 | it { is_expected.to start_with 3 } 42 | end 43 | """ 44 | When I run `rspec example_spec.rb` 45 | Then the output should contain all of these: 46 | | 6 examples, 2 failures | 47 | | expected [0, 1, 2, 3, 4] not to start with 0 | 48 | | expected [0, 1, 2, 3, 4] to start with 3 | 49 | -------------------------------------------------------------------------------- /features/built_in_matchers/throw_symbol.feature: -------------------------------------------------------------------------------- 1 | Feature: `throw_symbol` matcher 2 | 3 | The `throw_symbol` matcher is used to specify that a block of code throws a symbol. The most 4 | basic form passes if any symbol is thrown: 5 | 6 | ```ruby 7 | expect { throw :foo }.to throw_symbol 8 | ``` 9 | 10 | You'll often want to specify that a particular symbol is thrown: 11 | 12 | ```ruby 13 | expect { throw :foo }.to throw_symbol(:foo) 14 | ``` 15 | 16 | If you care about the additional argument given to throw, you can specify that as well: 17 | 18 | ```ruby 19 | expect { throw :foo, 7 }.to throw_symbol(:foo, 7) 20 | ``` 21 | 22 | Scenario: Basic usage 23 | Given a file named "throw_symbol_matcher_spec.rb" with: 24 | """ruby 25 | RSpec.describe "throw" do 26 | specify { expect { throw :foo }.to throw_symbol } 27 | specify { expect { throw :bar, 7 }.to throw_symbol } 28 | specify { expect { 5 + 5 }.not_to throw_symbol } 29 | 30 | # deliberate failures 31 | specify { expect { throw :foo }.not_to throw_symbol } 32 | specify { expect { throw :bar, 7 }.not_to throw_symbol } 33 | specify { expect { 5 + 5 }.to throw_symbol } 34 | end 35 | """ 36 | When I run `rspec throw_symbol_matcher_spec.rb` 37 | Then the output should contain all of these: 38 | | 6 examples, 3 failures | 39 | | expected no Symbol to be thrown, got :foo | 40 | | expected no Symbol to be thrown, got :bar | 41 | | expected a Symbol to be thrown, got nothing | 42 | 43 | Scenario: Specify thrown symbol 44 | Given a file named "throw_symbol_matcher_spec.rb" with: 45 | """ruby 46 | RSpec.describe "throw symbol" do 47 | specify { expect { throw :foo }.to throw_symbol(:foo) } 48 | specify { expect { throw :foo, 7 }.to throw_symbol(:foo) } 49 | specify { expect { 5 + 5 }.not_to throw_symbol(:foo) } 50 | specify { expect { throw :bar }.not_to throw_symbol(:foo) } 51 | 52 | # deliberate failures 53 | specify { expect { throw :foo }.not_to throw_symbol(:foo) } 54 | specify { expect { throw :foo, 7 }.not_to throw_symbol(:foo) } 55 | specify { expect { 5 + 5 }.to throw_symbol(:foo) } 56 | specify { expect { throw :bar }.to throw_symbol(:foo) } 57 | end 58 | """ 59 | When I run `rspec throw_symbol_matcher_spec.rb` 60 | Then the output should contain all of these: 61 | | 8 examples, 4 failures | 62 | | expected :foo not to be thrown, got :foo | 63 | | expected :foo not to be thrown, got :foo with 7 | 64 | | expected :foo to be thrown, got nothing | 65 | | expected :foo to be thrown, got :bar | 66 | 67 | Scenario: Specify thrown symbol and argument 68 | Given a file named "throw_symbol_argument_matcher_spec.rb" with: 69 | """ruby 70 | RSpec.describe "throw symbol with argument" do 71 | specify { expect { throw :foo, 7 }.to throw_symbol(:foo, 7) } 72 | specify { expect { throw :foo, 8 }.not_to throw_symbol(:foo, 7) } 73 | specify { expect { throw :bar, 7 }.not_to throw_symbol(:foo, 7) } 74 | specify { expect { throw :foo }.not_to throw_symbol(:foo, 7) } 75 | 76 | # deliberate failures 77 | specify { expect { throw :foo, 7 }.not_to throw_symbol(:foo, 7) } 78 | specify { expect { throw :foo, 8 }.to throw_symbol(:foo, 7) } 79 | specify { expect { throw :bar, 7 }.to throw_symbol(:foo, 7) } 80 | specify { expect { throw :foo }.to throw_symbol(:foo, 7) } 81 | end 82 | """ 83 | When I run `rspec throw_symbol_argument_matcher_spec.rb` 84 | Then the output should contain all of these: 85 | | 8 examples, 4 failures | 86 | | expected :foo with 7 not to be thrown, got :foo with 7 | 87 | | expected :foo with 7 to be thrown, got :foo with 8 | 88 | | expected :foo with 7 to be thrown, got :bar | 89 | | expected :foo with 7 to be thrown, got :foo with no argument | 90 | 91 | -------------------------------------------------------------------------------- /features/compound_expectations.feature: -------------------------------------------------------------------------------- 1 | Feature: Compound Expectations 2 | 3 | Matchers can be composed using `and` or `or` to make compound expectations. 4 | 5 | Scenario: Use `and` to chain expectations 6 | Given a file named "compound_and_matcher_spec.rb" with: 7 | """ruby 8 | RSpec.describe "A compound `and` matcher" do 9 | let(:string) { "foo bar bazz" } 10 | 11 | it "passes when both are true" do 12 | expect(string).to start_with("foo").and end_with("bazz") 13 | end 14 | 15 | it "passes when using boolean AND `&` alias" do 16 | expect(string).to start_with("foo") & end_with("bazz") 17 | end 18 | 19 | it "fails when the first matcher fails" do 20 | expect(string).to start_with("bar").and end_with("bazz") 21 | end 22 | 23 | it "fails when the second matcher fails" do 24 | expect(string).to start_with("foo").and end_with("bar") 25 | end 26 | end 27 | """ 28 | When I run `rspec compound_and_matcher_spec.rb` 29 | Then the output should contain "4 examples, 2 failures" 30 | 31 | Scenario: Use `or` to chain expectations 32 | Given a file named "stoplight_spec.rb" with: 33 | """ruby 34 | class StopLight 35 | def color 36 | %w[ green yellow red ].shuffle.first 37 | end 38 | end 39 | 40 | RSpec.describe StopLight, "#color" do 41 | let(:light) { StopLight.new } 42 | it "is green, yellow or red" do 43 | expect(light.color).to eq("green").or eq("yellow").or eq("red") 44 | end 45 | 46 | it "passes when using boolean OR `|` alias" do 47 | expect(light.color).to eq("green") | eq("yellow") | eq("red") 48 | end 49 | end 50 | """ 51 | When I run `rspec stoplight_spec.rb` 52 | Then the example should pass 53 | -------------------------------------------------------------------------------- /features/custom_matchers/access_running_example.feature: -------------------------------------------------------------------------------- 1 | Feature: Access the running example 2 | 3 | In the context of a custom matcher, you can call helper methods that are available from the 4 | current example's example group. This is used, for example, by rspec-rails in order to wrap 5 | rails' built-in assertions (which depend on helper methods available in the test context). 6 | 7 | Scenario: Call method defined on example from matcher 8 | Given a file named "example_spec.rb" with: 9 | """ruby 10 | RSpec::Matchers.define :bar do 11 | match do |_| 12 | foo == "foo" 13 | end 14 | end 15 | 16 | RSpec.describe "something" do 17 | def foo 18 | "foo" 19 | end 20 | 21 | it "does something" do 22 | expect("foo").to bar 23 | end 24 | end 25 | """ 26 | When I run `rspec ./example_spec.rb` 27 | Then the output should contain "1 example, 0 failures" 28 | 29 | Scenario: Call method _not_ defined on example from matcher 30 | Given a file named "example_spec.rb" with: 31 | """ruby 32 | RSpec::Matchers.define :bar do 33 | match do |_| 34 | foo == "foo" 35 | end 36 | end 37 | 38 | RSpec.describe "something" do 39 | it "does something" do 40 | expect("foo").to bar 41 | end 42 | end 43 | """ 44 | When I run `rspec ./example_spec.rb` 45 | Then the output should contain "1 example, 1 failure" 46 | And the output should match /undefined.*method/ 47 | And the output should contain "RSpec::Matchers::DSL::Matcher" 48 | And the output should not contain "ExampleGroup" 49 | -------------------------------------------------------------------------------- /features/custom_matchers/define_block_matcher.feature: -------------------------------------------------------------------------------- 1 | Feature: Defining a matcher supporting block expectations 2 | 3 | When you wish to support block expectations (e.g. `expect { ... }.to matcher`) with 4 | your custom matchers you must specify this. You can do this manually (or determinately 5 | based on some logic) by defining a `supports_block_expectation?` method or by using 6 | the DSL's `supports_block_expectations` shortcut method. 7 | 8 | Scenario: Define a block matcher manually 9 | Given a file named "block_matcher_spec.rb" with: 10 | """ruby 11 | RSpec::Matchers.define :support_blocks do 12 | match do |actual| 13 | actual.is_a? Proc 14 | end 15 | 16 | def supports_block_expectations? 17 | true # or some logic 18 | end 19 | end 20 | 21 | RSpec.describe "a custom block matcher" do 22 | specify { expect { }.to support_blocks } 23 | end 24 | """ 25 | When I run `rspec ./block_matcher_spec.rb` 26 | Then the example should pass 27 | 28 | Scenario: Define a block matcher using shortcut 29 | Given a file named "block_matcher_spec.rb" with: 30 | """ruby 31 | RSpec::Matchers.define :support_blocks do 32 | match do |actual| 33 | actual.is_a? Proc 34 | end 35 | 36 | supports_block_expectations 37 | end 38 | 39 | RSpec.describe "a custom block matcher" do 40 | specify { expect { }.to support_blocks } 41 | end 42 | """ 43 | When I run `rspec ./block_matcher_spec.rb` 44 | Then the example should pass 45 | 46 | Scenario: Define a block matcher using shortcut 47 | Given a file named "block_matcher_spec.rb" with: 48 | """ruby 49 | RSpec::Matchers.define :support_blocks_with_errors do 50 | match(:notify_expectation_failures => true) do |actual| 51 | actual.call 52 | true 53 | end 54 | 55 | supports_block_expectations 56 | end 57 | 58 | RSpec.describe "a custom block matcher" do 59 | specify do 60 | expect { 61 | expect(true).to eq false 62 | }.to support_blocks_with_errors 63 | end 64 | end 65 | """ 66 | When I run `rspec ./block_matcher_spec.rb` 67 | Then it should fail with: 68 | """ 69 | Failures: 70 | 71 | 1) a custom block matcher is expected to support blocks with errors 72 | Failure/Error: expect(true).to eq false 73 | 74 | expected: false 75 | got: true 76 | 77 | (compared using ==) 78 | """ 79 | -------------------------------------------------------------------------------- /features/custom_matchers/define_diffable_matcher.feature: -------------------------------------------------------------------------------- 1 | Feature: Defining a diffable matcher 2 | 3 | When a matcher is defined as diffable, the output will include a diff of the submitted objects when the objects are more than simple primitives. 4 | 5 | @skip-when-diff-lcs-1.3 6 | Scenario: Define a diffable matcher (with diff-lcs 1.4) 7 | Given a file named "diffable_matcher_spec.rb" with: 8 | """ruby 9 | RSpec::Matchers.define :be_just_like do |expected| 10 | match do |actual| 11 | actual == expected 12 | end 13 | 14 | diffable 15 | end 16 | 17 | RSpec.describe "two\nlines" do 18 | it { is_expected.to be_just_like("three\nlines") } 19 | end 20 | """ 21 | When I run `rspec ./diffable_matcher_spec.rb` 22 | Then it should fail with: 23 | """ 24 | Diff: 25 | @@ -1 +1 @@ 26 | -three 27 | +two 28 | """ 29 | 30 | @skip-when-diff-lcs-1.4 31 | Scenario: Define a diffable matcher (with diff-lcs 1.3) 32 | Given a file named "diffable_matcher_spec.rb" with: 33 | """ruby 34 | RSpec::Matchers.define :be_just_like do |expected| 35 | match do |actual| 36 | actual == expected 37 | end 38 | 39 | diffable 40 | end 41 | 42 | RSpec.describe "two\nlines" do 43 | it { is_expected.to be_just_like("three\nlines") } 44 | end 45 | """ 46 | When I run `rspec ./diffable_matcher_spec.rb` 47 | Then it should fail with: 48 | """ 49 | Diff: 50 | @@ -1,3 +1,3 @@ 51 | -three 52 | +two 53 | lines 54 | """ 55 | 56 | @skip-when-diff-lcs-1.3 @skip-when-diff-lcs-1.4.3 57 | Scenario: Redefine actual (with diff-lcs 1.4.4) 58 | 59 | Sometimes is necessary to overwrite actual to make diffing work, e.g. if `actual` is a name of a file you want to read from. For this to work you need to overwrite `@actual` in your matcher. 60 | 61 | Given a file named "redefine_actual_matcher_spec.rb" with: 62 | """ruby 63 | RSpec::Matchers.define :have_content do |expected| 64 | match do |actual| 65 | @actual = File.read(actual).chomp 66 | 67 | values_match? expected, @actual 68 | end 69 | 70 | diffable 71 | end 72 | 73 | RSpec.describe 'Compare files' do 74 | context 'when content is equal' do 75 | it { expect('data.txt').to have_content 'Data' } 76 | end 77 | 78 | context 'when files are different' do 79 | it { expect('data.txt').to have_content "No\nData\nhere" } 80 | end 81 | end 82 | """ 83 | And a file named "data.txt" with: 84 | """ 85 | Data 86 | """ 87 | When I run `rspec ./redefine_actual_matcher_spec.rb --format documentation` 88 | Then the exit status should not be 0 89 | And the output should contain: 90 | """ 91 | 2 examples, 1 failure 92 | """ 93 | And the output should contain: 94 | """ 95 | @@ -1,4 +1,2 @@ 96 | -No 97 | Data 98 | -here 99 | """ 100 | 101 | @skip-when-diff-lcs-1.4 102 | Scenario: Redefine actual (with diff-lcs 1.3) 103 | Given a file named "redefine_actual_matcher_spec.rb" with: 104 | """ruby 105 | RSpec::Matchers.define :have_content do |expected| 106 | match do |actual| 107 | @actual = File.read(actual).chomp 108 | 109 | values_match? expected, @actual 110 | end 111 | 112 | diffable 113 | end 114 | 115 | RSpec.describe 'Compare files' do 116 | context 'when content is equal' do 117 | it { expect('data.txt').to have_content 'Data' } 118 | end 119 | 120 | context 'when files are different' do 121 | it { expect('data.txt').to have_content "No\nData\nhere" } 122 | end 123 | end 124 | """ 125 | And a file named "data.txt" with: 126 | """ 127 | Data 128 | """ 129 | When I run `rspec ./redefine_actual_matcher_spec.rb --format documentation` 130 | Then the exit status should not be 0 131 | And the output should contain: 132 | """ 133 | 2 examples, 1 failure 134 | """ 135 | And the output should contain: 136 | """ 137 | @@ -1,4 +1,2 @@ 138 | -No 139 | Data 140 | -here 141 | """ 142 | -------------------------------------------------------------------------------- /features/custom_matchers/define_matcher_outside_rspec.feature: -------------------------------------------------------------------------------- 1 | Feature: Defining a matcher outside rspec 2 | 3 | You can define custom matchers when using rspec-expectations outside of rspec-core. 4 | 5 | Scenario: Define a matcher with default messages 6 | Given a file named "test_multiples.rb" with: 7 | """ruby 8 | require "minitest/autorun" 9 | require "rspec/expectations/minitest_integration" 10 | 11 | RSpec::Matchers.define :be_a_multiple_of do |expected| 12 | match do |actual| 13 | actual % expected == 0 14 | end 15 | end 16 | 17 | class TestMultiples < Minitest::Test 18 | def test_9_should_be_a_multiple_of_3 19 | expect(9).to be_a_multiple_of(3) 20 | end 21 | 22 | def test_9_should_be_a_multiple_of_4 23 | expect(9).to be_a_multiple_of(4) 24 | end 25 | end 26 | """ 27 | When I run `ruby test_multiples.rb` 28 | Then the exit status should not be 0 29 | And the output should contain "expected 9 to be a multiple of 4" 30 | And the output should contain "2 runs, 2 assertions, 1 failures, 0 errors" 31 | -------------------------------------------------------------------------------- /features/custom_matchers/define_matcher_with_fluent_interface.feature: -------------------------------------------------------------------------------- 1 | Feature: Defining a matcher with fluent interface 2 | 3 | Use the `chain` method to define matchers with a fluent interface. 4 | 5 | Scenario: Chained method with argument 6 | Given a file named "between_spec.rb" with: 7 | """ruby 8 | RSpec::Matchers.define :be_bigger_than do |first| 9 | match do |actual| 10 | (actual > first) && (actual < @second) 11 | end 12 | 13 | chain :and_smaller_than do |second| 14 | @second = second 15 | end 16 | end 17 | 18 | RSpec.describe 5 do 19 | it { is_expected.to be_bigger_than(4).and_smaller_than(6) } 20 | end 21 | """ 22 | When I run `rspec between_spec.rb --format documentation` 23 | Then the output should contain "1 example, 0 failures" 24 | And the output should contain "is expected to be bigger than 4" 25 | 26 | Scenario: Chained setter 27 | Given a file named "between_spec.rb" with: 28 | """ruby 29 | RSpec::Matchers.define :be_bigger_than do |first| 30 | match do |actual| 31 | (actual > first) && (actual < second) 32 | end 33 | 34 | chain :and_smaller_than, :second 35 | end 36 | 37 | RSpec.describe 5 do 38 | it { is_expected.to be_bigger_than(4).and_smaller_than(6) } 39 | end 40 | """ 41 | When I run `rspec between_spec.rb --format documentation` 42 | Then the output should contain "1 example, 0 failures" 43 | And the output should contain "is expected to be bigger than 4" 44 | 45 | Scenario: With `include_chain_clauses_in_custom_matcher_descriptions` configured to true, and chained method with argument 46 | Given a file named "between_spec.rb" with: 47 | """ruby 48 | RSpec.configure do |config| 49 | config.expect_with :rspec do |c| 50 | c.include_chain_clauses_in_custom_matcher_descriptions = true 51 | end 52 | end 53 | 54 | RSpec::Matchers.define :be_bigger_than do |first| 55 | match do |actual| 56 | (actual > first) && (actual < @second) 57 | end 58 | 59 | chain :and_smaller_than do |second| 60 | @second = second 61 | end 62 | end 63 | 64 | RSpec.describe 5 do 65 | it { is_expected.to be_bigger_than(4).and_smaller_than(6) } 66 | end 67 | """ 68 | When I run `rspec between_spec.rb --format documentation` 69 | Then the output should contain "1 example, 0 failures" 70 | And the output should contain "is expected to be bigger than 4 and smaller than 6" 71 | -------------------------------------------------------------------------------- /features/customized_message.feature: -------------------------------------------------------------------------------- 1 | Feature: Customized message 2 | 3 | RSpec tries to provide useful failure messages, but for cases in which you want more 4 | specific information, you can define your own message right in the example.This works for 5 | any matcher _other than the operator matchers_. 6 | 7 | Scenario: Customize failure message 8 | Given a file named "example_spec.rb" with: 9 | """ruby 10 | RSpec.describe Array do 11 | context "when created with `new`" do 12 | it "is empty" do 13 | array = Array.new 14 | array << 1 # trigger a failure to demonstrate the message 15 | expect(array).to be_empty, "expected empty array, got #{array.inspect}" 16 | end 17 | end 18 | end 19 | """ 20 | When I run `rspec example_spec.rb --format documentation` 21 | Then the output should contain "expected empty array, got [1]" 22 | 23 | Scenario: Customize failure message with a proc 24 | Given a file named "example_spec.rb" with: 25 | """ruby 26 | RSpec.describe Array do 27 | context "when created with `new`" do 28 | it "is empty" do 29 | array = Array.new 30 | array << 1 # trigger a failure to demonstrate the message 31 | expect(array).to be_empty, lambda { "expected empty array, got #{array.inspect}" } 32 | end 33 | end 34 | end 35 | """ 36 | When I run `rspec example_spec.rb --format documentation` 37 | Then the output should contain "expected empty array, got [1]" 38 | -------------------------------------------------------------------------------- /features/define_negated_matcher.feature: -------------------------------------------------------------------------------- 1 | Feature: Define negated matcher 2 | 3 | You can use `RSpec::Matchers.define_negated_matcher` to define a negated version of 4 | an existing matcher. This is particularly useful in composed matcher expressions. 5 | 6 | @skip-when-ripper-unsupported 7 | Scenario: Composed negated matcher expression 8 | Given a file named "composed_negated_expression_spec.rb" with: 9 | """ruby 10 | RSpec::Matchers.define_negated_matcher :an_array_excluding, :include 11 | 12 | RSpec.describe "A negated matcher" do 13 | let(:list) { 1.upto(10).to_a } 14 | 15 | it "can be used in a composed matcher expression" do 16 | expect { list.delete(5) }.to change { list }.to(an_array_excluding 5) 17 | end 18 | 19 | it "provides a good failure message based on the name" do 20 | # deliberate failure 21 | expect { list.delete(1) }.to change { list }.to(an_array_excluding 5) 22 | end 23 | end 24 | """ 25 | When I run `rspec composed_negated_expression_spec.rb` 26 | Then the output should contain all of these: 27 | | 2 examples, 1 failure | 28 | | 1) A negated matcher provides a good failure message based on the name | 29 | | Failure/Error: expect { list.delete(1) }.to change { list }.to(an_array_excluding 5) | 30 | | expected `list` to have changed to an array excluding 5, but is now [2, 3, 4, 5, 6, 7, 8, 9, 10] | 31 | -------------------------------------------------------------------------------- /features/diffing.feature: -------------------------------------------------------------------------------- 1 | Feature: Diffing 2 | 3 | When appropriate, failure messages will automatically include a diff. 4 | 5 | Scenario: Diff for a multiline string 6 | Given a file named "example_spec.rb" with: 7 | """ruby 8 | RSpec.describe "a multiline string" do 9 | it "is like another string" do 10 | expected = <<-EXPECTED 11 | this is the 12 | expected 13 | string 14 | EXPECTED 15 | actual = <<-ACTUAL 16 | this is the 17 | actual 18 | string 19 | ACTUAL 20 | expect(actual).to eq(expected) 21 | end 22 | end 23 | """ 24 | When I run `rspec example_spec.rb` 25 | Then the output should contain: 26 | """ 27 | Diff: 28 | @@ -1,4 +1,4 @@ 29 | this is the 30 | - expected 31 | + actual 32 | string 33 | """ 34 | 35 | @skip-when-diff-lcs-1.3 36 | Scenario: Diff for a multiline string and a regexp on diff-lcs 1.4 37 | Given a file named "example_spec.rb" with: 38 | """ruby 39 | RSpec.describe "a multiline string" do 40 | it "is like another string" do 41 | expected = /expected/m 42 | actual = <<-ACTUAL 43 | this is the 44 | actual 45 | string 46 | ACTUAL 47 | expect(actual).to match expected 48 | end 49 | end 50 | """ 51 | When I run `rspec example_spec.rb` 52 | Then the output should contain: 53 | """ 54 | Diff: 55 | @@ -1,3 +1,5 @@ 56 | -/expected/m 57 | +this is the 58 | + actual 59 | + string 60 | """ 61 | 62 | @skip-when-diff-lcs-1.4 63 | Scenario: Diff for a multiline string and a regexp on diff-lcs 1.3 64 | Given a file named "example_spec.rb" with: 65 | """ruby 66 | RSpec.describe "a multiline string" do 67 | it "is like another string" do 68 | expected = /expected/m 69 | actual = <<-ACTUAL 70 | this is the 71 | actual 72 | string 73 | ACTUAL 74 | expect(actual).to match expected 75 | end 76 | end 77 | """ 78 | When I run `rspec example_spec.rb` 79 | Then the output should contain: 80 | """ 81 | Diff: 82 | @@ -1,2 +1,4 @@ 83 | -/expected/m 84 | +this is the 85 | + actual 86 | + string 87 | """ 88 | 89 | Scenario: No diff for a single line strings 90 | Given a file named "example_spec.rb" with: 91 | """ruby 92 | RSpec.describe "a single line string" do 93 | it "is like another string" do 94 | expected = "this string" 95 | actual = "that string" 96 | expect(actual).to eq(expected) 97 | end 98 | end 99 | """ 100 | When I run `rspec example_spec.rb` 101 | Then the output should not contain "Diff:" 102 | 103 | Scenario: No diff for numbers 104 | Given a file named "example_spec.rb" with: 105 | """ruby 106 | RSpec.describe "a number" do 107 | it "is like another number" do 108 | expect(1).to eq(2) 109 | end 110 | end 111 | """ 112 | When I run `rspec example_spec.rb` 113 | Then the output should not contain "Diff:" 114 | -------------------------------------------------------------------------------- /features/implicit_docstrings.feature: -------------------------------------------------------------------------------- 1 | Feature: Implicit docstrings 2 | 3 | When you use rspec-expectations with rspec-core, RSpec is able to auto-generate the 4 | docstrings for examples for you based on the last expectation in the example. This can be 5 | handy when the matcher expresses exactly what you would write in your example docstring, 6 | but it can also be easily abused. We find that the freeform nature of the docstring provides 7 | a lot of value when used well (e.g. to document the "why" of a particular behavior), and you 8 | lose that kind of flexibility when you rely on the matcher to generate the docstring for you. 9 | 10 | In general, we recommend only using this feature when the matcher aligns _exactly_ with the 11 | docstring you would write. Even then, many users prefer the explicitness of the full 12 | docstring, so use this feature with care (if at all). 13 | 14 | Scenario: Run passing examples 15 | Given a file named "implicit_docstrings_spec.rb" with: 16 | """ruby 17 | RSpec.describe "Examples with no docstrings generate their own:" do 18 | specify { expect(3).to be < 5 } 19 | specify { expect([1,2,3]).to include(2) } 20 | specify { expect([1,2,3]).to respond_to(:size) } 21 | end 22 | """ 23 | 24 | When I run `rspec ./implicit_docstrings_spec.rb -fdoc` 25 | 26 | Then the output should contain "is expected to be < 5" 27 | And the output should contain "is expected to include 2" 28 | And the output should contain "is expected to respond to #size" 29 | 30 | Scenario: Run failing examples 31 | Given a file named "failing_implicit_docstrings_spec.rb" with: 32 | """ruby 33 | RSpec.describe "Failing examples with no descriptions" do 34 | # description is auto-generated per the last executed expectation 35 | specify do 36 | expect(3).to equal(2) 37 | expect(5).to equal(5) 38 | end 39 | 40 | specify { expect(3).to be > 5 } 41 | specify { expect([1,2,3]).to include(4) } 42 | specify { expect([1,2,3]).not_to respond_to(:size) } 43 | end 44 | """ 45 | 46 | When I run `rspec ./failing_implicit_docstrings_spec.rb -fdoc` 47 | 48 | Then the output should contain "is expected to equal 2" 49 | And the output should contain "is expected to be > 5" 50 | And the output should contain "is expected to include 4" 51 | And the output should contain "is expected not to respond to #size" 52 | -------------------------------------------------------------------------------- /features/step_definitions/additional_cli_steps.rb: -------------------------------------------------------------------------------- 1 | # Useful for when the output is slightly different on different versions of ruby 2 | Then /^the output should contain "([^"]*)" or "([^"]*)"$/ do |string1, string2| 3 | unless [string1, string2].any? { |s| all_output.include?(s) } 4 | fail %(Neither "#{string1}" or "#{string2}" were found in:\n#{all_output}) 5 | end 6 | end 7 | 8 | Then /^the output should contain all of these:$/ do |table| 9 | table.raw.flatten.each do |string| 10 | if RUBY_VERSION == '1.8.7' && string =~ /\{.+=>.+\}/ 11 | warn "Skipping checking #{string} on 1.8.7 because hash ordering is not consistent" 12 | elsif RUBY_VERSION.to_f > 3.3 13 | expect(all_output).to include_output_string string.gsub('undefined method `', "undefined method '") 14 | else 15 | expect(all_output).to include_output_string string 16 | end 17 | end 18 | end 19 | 20 | Then /^the example(?:s)? should(?: all)? pass$/ do 21 | step 'the output should contain "0 failures"' 22 | step 'the exit status should be 0' 23 | end 24 | 25 | Then /^the example should fail$/ do 26 | step 'the output should contain "1 failure"' 27 | step 'the exit status should not be 0' 28 | end 29 | -------------------------------------------------------------------------------- /features/support/diff_lcs_versions.rb: -------------------------------------------------------------------------------- 1 | require 'diff-lcs' 2 | 3 | Around "@skip-when-diff-lcs-1.4" do |scenario, block| 4 | if Diff::LCS::VERSION >= '1.4' 5 | skip_this_scenario "Skipping scenario #{scenario.name} on `diff-lcs` v#{Diff::LCS::VERSION}" 6 | else 7 | block.call 8 | end 9 | end 10 | 11 | Around "@skip-when-diff-lcs-1.4.3" do |scenario, block| 12 | if Diff::LCS::VERSION =~ /1\.4\.3/ 13 | skip_this_scenario "Skipping scenario #{scenario.name} on `diff-lcs` v#{Diff::LCS::VERSION}" 14 | else 15 | block.call 16 | end 17 | end 18 | 19 | Around "@skip-when-diff-lcs-1.3" do |scenario, block| 20 | if Diff::LCS::VERSION < '1.4' 21 | skip_this_scenario "Skipping scenario #{scenario.name} on `diff-lcs` v#{Diff::LCS::VERSION}" 22 | else 23 | block.call 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /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-disallowed-api': 'not @allow-disallowed-api' 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.expose_dsl_globally = false 23 | 24 | rspec.mock_with :rspec do |mocks| 25 | mocks.syntax = :expect 26 | end 27 | 28 | rspec.expect_with :rspec do |expectations| 29 | expectations.syntax = :expect 30 | end 31 | 32 | rspec.include DisallowOneLinerShould 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'aruba/cucumber' 2 | 3 | Aruba.configure do |config| 4 | if RUBY_PLATFORM =~ /java/ || defined?(Rubinius) 5 | config.exit_timeout = 60 6 | else 7 | config.exit_timeout = 10 8 | end 9 | end 10 | 11 | Before do 12 | if RUBY_PLATFORM == 'java' 13 | # disable JIT since these processes are so short lived 14 | set_environment_variable('JRUBY_OPTS', "-X-C #{ENV['JRUBY_OPTS']}") 15 | end 16 | 17 | if defined?(Rubinius) 18 | # disable JIT since these processes are so short lived 19 | set_environment_variable('RBXOPT', "-Xint=true #{ENV['RBXOPT']}") 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /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/support/ruby_features.rb: -------------------------------------------------------------------------------- 1 | Around "@skip-when-splat-args-unsupported" do |scenario, block| 2 | require 'rspec/support/ruby_features' 3 | 4 | if ::RSpec::Support::RubyFeatures.optional_and_splat_args_supported? 5 | block.call 6 | else 7 | skip_this_scenario "Skipping scenario #{scenario.name} because splat arguments are not supported" 8 | end 9 | end 10 | 11 | Around "@skip-when-keyword-args-unsupported" do |scenario, block| 12 | require 'rspec/support/ruby_features' 13 | 14 | if ::RSpec::Support::RubyFeatures.kw_args_supported? 15 | block.call 16 | else 17 | skip_this_scenario "Skipping scenario #{scenario.name} because keyword arguments are not supported" 18 | end 19 | end 20 | 21 | Around "@skip-when-required-keyword-args-unsupported" do |scenario, block| 22 | require 'rspec/support/ruby_features' 23 | 24 | if ::RSpec::Support::RubyFeatures.required_kw_args_supported? 25 | block.call 26 | else 27 | skip_this_scenario "Skipping scenario #{scenario.name} because required keyword arguments are not supported" 28 | end 29 | end 30 | 31 | Around "@skip-when-ripper-unsupported" do |scenario, block| 32 | require 'rspec/support/ruby_features' 33 | 34 | if ::RSpec::Support::RubyFeatures.ripper_supported? 35 | block.call 36 | else 37 | skip_this_scenario "Skipping scenario #{scenario.name} because Ripper is not supported" 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /features/syntax_configuration.feature: -------------------------------------------------------------------------------- 1 | @allow-disallowed-api 2 | Feature: Syntax Configuration 3 | 4 | The primary syntax provided by rspec-expectations is based on 5 | the `expect` method, which explicitly wraps an object or block 6 | of code in order to set an expectation on it. 7 | 8 | There's also an older `should`-based syntax, which relies upon `should` being 9 | monkey-patched onto every object in the system. However, this syntax can at times lead to 10 | some surprising failures, since RSpec does not own every object in the system and cannot 11 | guarantee that it will always work consistently. 12 | 13 | We recommend you use the `expect` syntax unless you have a specific reason you prefer the 14 | `should` syntax. We have no plans to ever completely remove the `should` syntax but starting 15 | in RSpec 3, a deprecation warning will be issued if you do not explicitly enable it, with the 16 | plan to disable it by default in RSpec 4 (and potentially move it into an external gem). 17 | 18 | If you have an old `should`-based project that you would like to upgrade to the `expect`, 19 | check out [transpec](http://yujinakayama.me/transpec/), which can perform the conversion automatically for you. 20 | 21 | Background: 22 | Given a file named "spec/syntaxes_spec.rb" with: 23 | """ruby 24 | require 'spec_helper' 25 | 26 | RSpec.describe "using the should syntax" do 27 | specify { 3.should eq(3) } 28 | specify { 3.should_not eq(4) } 29 | specify { lambda { raise "boom" }.should raise_error("boom") } 30 | specify { lambda { }.should_not raise_error } 31 | end 32 | 33 | RSpec.describe "using the expect syntax" do 34 | specify { expect(3).to eq(3) } 35 | specify { expect(3).not_to eq(4) } 36 | specify { expect { raise "boom" }.to raise_error("boom") } 37 | specify { expect { }.not_to raise_error } 38 | end 39 | """ 40 | 41 | Scenario: Both syntaxes are available by default 42 | Given a file named "spec/spec_helper.rb" with: 43 | """ruby 44 | """ 45 | When I run `rspec` 46 | Then the examples should all pass 47 | And the output should contain "Using `should` from rspec-expectations' old `:should` syntax without explicitly enabling the syntax is deprecated" 48 | 49 | Scenario: Disable should syntax 50 | Given a file named "spec/spec_helper.rb" with: 51 | """ruby 52 | RSpec.configure do |config| 53 | config.expect_with :rspec do |expectations| 54 | expectations.syntax = :expect 55 | end 56 | end 57 | """ 58 | When I run `rspec` 59 | Then the output should contain all of these: 60 | | 8 examples, 4 failures | 61 | | undefined method `should' | 62 | 63 | Scenario: Disable expect syntax 64 | Given a file named "spec/spec_helper.rb" with: 65 | """ruby 66 | RSpec.configure do |config| 67 | config.expect_with :rspec do |expectations| 68 | expectations.syntax = :should 69 | end 70 | config.mock_with :rspec do |mocks| 71 | mocks.syntax = :should 72 | end 73 | end 74 | """ 75 | When I run `rspec` 76 | Then the output should contain all of these: 77 | | 8 examples, 4 failures | 78 | | undefined method `expect' | 79 | 80 | Scenario: Explicitly enable both syntaxes 81 | Given a file named "spec/spec_helper.rb" with: 82 | """ruby 83 | RSpec.configure do |config| 84 | config.expect_with :rspec do |expectations| 85 | expectations.syntax = [:should, :expect] 86 | end 87 | end 88 | """ 89 | When I run `rspec` 90 | Then the examples should all pass 91 | And the output should not contain "deprecated" 92 | 93 | -------------------------------------------------------------------------------- /features/test_frameworks/minitest.feature: -------------------------------------------------------------------------------- 1 | Feature: Minitest integration 2 | 3 | rspec-expectations is a stand-alone gem that can be used without the rest of RSpec. If you 4 | like minitest as your test runner, but prefer RSpec's approach to expressing expectations, 5 | you can have both. 6 | 7 | To integrate rspec-expectations with minitest, require `rspec/expectations/minitest_integration`. 8 | 9 | Scenario: Use rspec/expectations with minitest 10 | Given a file named "rspec_expectations_test.rb" with: 11 | """ruby 12 | require 'minitest/autorun' 13 | require 'rspec/expectations/minitest_integration' 14 | 15 | class RSpecExpectationsTest < Minitest::Test 16 | RSpec::Matchers.define :be_an_integer do 17 | match { |actual| Integer === actual } 18 | end 19 | 20 | def be_an_int 21 | # This is actually an internal rspec-expectations API, but is used 22 | # here to demonstrate that deprecation warnings from within 23 | # rspec-expectations work correctly without depending on rspec-core 24 | RSpec.deprecate(:be_an_int, :replacement => :be_an_integer) 25 | be_an_integer 26 | end 27 | 28 | def test_passing_expectation 29 | expect(1 + 3).to eq 4 30 | end 31 | 32 | def test_failing_expectation 33 | expect([1, 2]).to be_empty 34 | end 35 | 36 | def test_custom_matcher_with_deprecation_warning 37 | expect(1).to be_an_int 38 | end 39 | 40 | def test_using_aggregate_failures 41 | aggregate_failures do 42 | expect(1).to be_even 43 | expect(2).to be_odd 44 | end 45 | end 46 | end 47 | """ 48 | When I run `ruby rspec_expectations_test.rb` 49 | Then the output should contain "4 runs, 5 assertions, 2 failures, 0 errors" 50 | And the output should contain "expected `[1, 2].empty?` to be truthy, got false" 51 | And the output should contain "be_an_int is deprecated" 52 | And the output should contain "Got 2 failures from failure aggregation block" 53 | 54 | Scenario: Use rspec/expectations with minitest/spec 55 | Given a file named "rspec_expectations_spec.rb" with: 56 | """ruby 57 | require 'minitest/autorun' 58 | require 'minitest/spec' 59 | require 'rspec/expectations/minitest_integration' 60 | 61 | describe "Using RSpec::Expectations with Minitest::Spec" do 62 | RSpec::Matchers.define :be_an_integer do 63 | match { |actual| Integer === actual } 64 | end 65 | 66 | it 'passes an expectation' do 67 | expect(1 + 3).to eq 4 68 | end 69 | 70 | it 'fails an expectation' do 71 | expect([1, 2]).to be_empty 72 | end 73 | 74 | it 'passes a block expectation' do 75 | expect { 1 / 0 }.to raise_error(ZeroDivisionError) 76 | end 77 | 78 | it 'fails a block expectation' do 79 | expect { 1 / 1 }.to raise_error(ZeroDivisionError) 80 | end 81 | 82 | it 'passes a negative expectation (using `not_to`)' do 83 | expect(1).not_to eq 2 84 | end 85 | 86 | it 'fails a negative expectation (using `to_not`)' do 87 | expect(1).to_not eq 1 88 | end 89 | 90 | it 'fails multiple expectations' do 91 | aggregate_failures do 92 | expect(1).to be_even 93 | expect(2).to be_odd 94 | end 95 | end 96 | 97 | it 'passes a minitest expectation' do 98 | expect(1 + 3).must_equal 4 99 | end 100 | 101 | it 'fails a minitest expectation' do 102 | expect([1, 2]).must_be :empty? 103 | end 104 | end 105 | """ 106 | When I run `ruby rspec_expectations_spec.rb` 107 | Then the output should contain "9 runs, 10 assertions, 5 failures, 0 errors" 108 | And the output should contain "expected `[1, 2].empty?` to be truthy, got false" 109 | And the output should contain "expected ZeroDivisionError but nothing was raised" 110 | And the output should contain "Got 2 failures from failure aggregation block" 111 | And the output should contain "Expected [1, 2] to be empty?" 112 | -------------------------------------------------------------------------------- /lib/rspec/expectations.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/support' 2 | RSpec::Support.require_rspec_support "caller_filter" 3 | RSpec::Support.require_rspec_support "warnings" 4 | RSpec::Support.require_rspec_support "object_formatter" 5 | 6 | require 'rspec/matchers' 7 | 8 | RSpec::Support.define_optimized_require_for_rspec(:expectations) { |f| require_relative(f) } 9 | 10 | %w[ 11 | expectation_target 12 | configuration 13 | fail_with 14 | handler 15 | version 16 | ].each { |file| RSpec::Support.require_rspec_expectations(file) } 17 | 18 | module RSpec 19 | # RSpec::Expectations provides a simple, readable API to express 20 | # the expected outcomes in a code example. To express an expected 21 | # outcome, wrap an object or block in `expect`, call `to` or `to_not` 22 | # (aliased as `not_to`) and pass it a matcher object: 23 | # 24 | # expect(order.total).to eq(Money.new(5.55, :USD)) 25 | # expect(list).to include(user) 26 | # expect(message).not_to match(/foo/) 27 | # expect { do_something }.to raise_error 28 | # 29 | # The last form (the block form) is needed to match against ruby constructs 30 | # that are not objects, but can only be observed when executing a block 31 | # of code. This includes raising errors, throwing symbols, yielding, 32 | # and changing values. 33 | # 34 | # When `expect(...).to` is invoked with a matcher, it turns around 35 | # and calls `matcher.matches?()`. For example, 36 | # in the expression: 37 | # 38 | # expect(order.total).to eq(Money.new(5.55, :USD)) 39 | # 40 | # ...`eq(Money.new(5.55, :USD))` returns a matcher object, and it results 41 | # in the equivalent of `eq.matches?(order.total)`. If `matches?` returns 42 | # `true`, the expectation is met and execution continues. If `false`, then 43 | # the spec fails with the message returned by `eq.failure_message`. 44 | # 45 | # Given the expression: 46 | # 47 | # expect(order.entries).not_to include(entry) 48 | # 49 | # ...the `not_to` method (also available as `to_not`) invokes the equivalent of 50 | # `include.matches?(order.entries)`, but it interprets `false` as success, and 51 | # `true` as a failure, using the message generated by 52 | # `include.failure_message_when_negated`. 53 | # 54 | # rspec-expectations ships with a standard set of useful matchers, and writing 55 | # your own matchers is quite simple. 56 | # 57 | # See [RSpec::Matchers](../RSpec/Matchers) for more information about the 58 | # built-in matchers that ship with rspec-expectations, and how to write your 59 | # own custom matchers. 60 | module Expectations 61 | # Exception raised when an expectation fails. 62 | # 63 | # @note We subclass Exception so that in a stub implementation if 64 | # the user sets an expectation, it can't be caught in their 65 | # code by a bare `rescue`. 66 | # @api public 67 | class ExpectationNotMetError < Exception 68 | end 69 | 70 | # Exception raised from `aggregate_failures` when multiple expectations fail. 71 | # 72 | # @note The constant is defined here but the extensive logic of this class 73 | # is lazily defined when `FailureAggregator` is autoloaded, since we do 74 | # not need to waste time defining that functionality unless 75 | # `aggregate_failures` is used. 76 | class MultipleExpectationsNotMetError < ExpectationNotMetError 77 | end 78 | 79 | autoload :BlockSnippetExtractor, "rspec/expectations/block_snippet_extractor" 80 | autoload :FailureAggregator, "rspec/expectations/failure_aggregator" 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/rspec/expectations/fail_with.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Expectations 3 | class << self 4 | # @private 5 | class Differ 6 | # @private 7 | OBJECT_PREPARER = lambda do |object| 8 | RSpec::Matchers::Composable.surface_descriptions_in(object) 9 | end 10 | end 11 | 12 | # @private 13 | def differ 14 | RSpec::Support::Differ.new( 15 | :object_preparer => Differ::OBJECT_PREPARER, 16 | :color => RSpec::Matchers.configuration.color? 17 | ) 18 | end 19 | 20 | # Raises an RSpec::Expectations::ExpectationNotMetError with message. 21 | # @param [String] message 22 | # @param [Object] expected 23 | # @param [Object] actual 24 | # 25 | # Adds a diff to the failure message when `expected` and `actual` are 26 | # both present. 27 | def fail_with(message, expected=nil, actual=nil) 28 | unless message 29 | raise ArgumentError, "Failure message is nil. Does your matcher define the " \ 30 | "appropriate failure_message[_when_negated] method to return a string?" 31 | end 32 | 33 | message = ::RSpec::Matchers::MultiMatcherDiff.from(expected, actual).message_with_diff(message, differ) 34 | 35 | RSpec::Support.notify_failure(RSpec::Expectations::ExpectationNotMetError.new message) 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/rspec/expectations/minitest_integration.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/expectations' 2 | 3 | Minitest::Test.class_eval do 4 | include ::RSpec::Matchers 5 | 6 | # This `expect` will only be called if the user is using Minitest < 5.6 7 | # or if they are _not_ using Minitest::Spec on 5.6+. Minitest::Spec on 5.6+ 8 | # defines its own `expect` and will have the assertions incremented via our 9 | # definitions of `to`/`not_to`/`to_not` below. 10 | def expect(*a, &b) 11 | self.assertions += 1 12 | super 13 | end 14 | 15 | # Convert a `MultipleExpectationsNotMetError` to a `Minitest::Assertion` error so 16 | # it gets counted in minitest's summary stats as a failure rather than an error. 17 | # It would be nice to make `MultipleExpectationsNotMetError` subclass 18 | # `Minitest::Assertion`, but Minitest's implementation does not treat subclasses 19 | # the same, so this is the best we can do. 20 | def aggregate_failures(*args, &block) 21 | super 22 | rescue RSpec::Expectations::MultipleExpectationsNotMetError => e 23 | assertion_failed = Minitest::Assertion.new(e.message) 24 | assertion_failed.set_backtrace e.backtrace 25 | raise assertion_failed 26 | end 27 | end 28 | 29 | # Older versions of Minitest (e.g. before 5.6) do not define 30 | # `Minitest::Expectation`. 31 | if defined?(::Minitest::Expectation) 32 | Minitest::Expectation.class_eval do 33 | include RSpec::Expectations::ExpectationTarget::InstanceMethods 34 | 35 | def to(*args) 36 | ctx.assertions += 1 37 | super 38 | end 39 | 40 | def not_to(*args) 41 | ctx.assertions += 1 42 | super 43 | end 44 | 45 | def to_not(*args) 46 | ctx.assertions += 1 47 | super 48 | end 49 | end 50 | end 51 | 52 | module RSpec 53 | module Expectations 54 | remove_const :ExpectationNotMetError 55 | # Exception raised when an expectation fails. 56 | const_set :ExpectationNotMetError, ::Minitest::Assertion 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/rspec/expectations/version.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Expectations 3 | # @private 4 | module Version 5 | STRING = '3.14.0.pre' 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/rspec/matchers/built_in.rb: -------------------------------------------------------------------------------- 1 | RSpec::Support.require_rspec_matchers "built_in/base_matcher" 2 | 3 | module RSpec 4 | module Matchers 5 | # Container module for all built-in matchers. The matcher classes are here 6 | # (rather than directly under `RSpec::Matchers`) in order to prevent name 7 | # collisions, since `RSpec::Matchers` gets included into the user's namespace. 8 | # 9 | # Autoloading is used to delay when the matcher classes get loaded, allowing 10 | # rspec-matchers to boot faster, and avoiding loading matchers the user is 11 | # not using. 12 | module BuiltIn 13 | autoload :BeAKindOf, 'rspec/matchers/built_in/be_kind_of' 14 | autoload :BeAnInstanceOf, 'rspec/matchers/built_in/be_instance_of' 15 | autoload :BeBetween, 'rspec/matchers/built_in/be_between' 16 | autoload :Be, 'rspec/matchers/built_in/be' 17 | autoload :BeComparedTo, 'rspec/matchers/built_in/be' 18 | autoload :BeFalsey, 'rspec/matchers/built_in/be' 19 | autoload :BeHelpers, 'rspec/matchers/built_in/be' 20 | autoload :BeNil, 'rspec/matchers/built_in/be' 21 | autoload :BePredicate, 'rspec/matchers/built_in/has' 22 | autoload :BeTruthy, 'rspec/matchers/built_in/be' 23 | autoload :BeWithin, 'rspec/matchers/built_in/be_within' 24 | autoload :Change, 'rspec/matchers/built_in/change' 25 | autoload :Compound, 'rspec/matchers/built_in/compound' 26 | autoload :ContainExactly, 'rspec/matchers/built_in/contain_exactly' 27 | autoload :Cover, 'rspec/matchers/built_in/cover' 28 | autoload :EndWith, 'rspec/matchers/built_in/start_or_end_with' 29 | autoload :Eq, 'rspec/matchers/built_in/eq' 30 | autoload :Eql, 'rspec/matchers/built_in/eql' 31 | autoload :Equal, 'rspec/matchers/built_in/equal' 32 | autoload :Exist, 'rspec/matchers/built_in/exist' 33 | autoload :Has, 'rspec/matchers/built_in/has' 34 | autoload :HaveAttributes, 'rspec/matchers/built_in/have_attributes' 35 | autoload :Include, 'rspec/matchers/built_in/include' 36 | autoload :All, 'rspec/matchers/built_in/all' 37 | autoload :Match, 'rspec/matchers/built_in/match' 38 | autoload :NegativeOperatorMatcher, 'rspec/matchers/built_in/operators' 39 | autoload :OperatorMatcher, 'rspec/matchers/built_in/operators' 40 | autoload :Output, 'rspec/matchers/built_in/output' 41 | autoload :PositiveOperatorMatcher, 'rspec/matchers/built_in/operators' 42 | autoload :RaiseError, 'rspec/matchers/built_in/raise_error' 43 | autoload :RespondTo, 'rspec/matchers/built_in/respond_to' 44 | autoload :Satisfy, 'rspec/matchers/built_in/satisfy' 45 | autoload :StartWith, 'rspec/matchers/built_in/start_or_end_with' 46 | autoload :ThrowSymbol, 'rspec/matchers/built_in/throw_symbol' 47 | autoload :YieldControl, 'rspec/matchers/built_in/yield' 48 | autoload :YieldSuccessiveArgs, 'rspec/matchers/built_in/yield' 49 | autoload :YieldWithArgs, 'rspec/matchers/built_in/yield' 50 | autoload :YieldWithNoArgs, 'rspec/matchers/built_in/yield' 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/rspec/matchers/built_in/all.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | module BuiltIn 4 | # @api private 5 | # Provides the implementation for `all`. 6 | # Not intended to be instantiated directly. 7 | class All < BaseMatcher 8 | # @private 9 | attr_reader :matcher, :failed_objects 10 | 11 | def initialize(matcher) 12 | @matcher = matcher 13 | @failed_objects = {} 14 | end 15 | 16 | # @private 17 | def does_not_match?(_actual) 18 | raise NotImplementedError, '`expect().not_to all( matcher )` is not supported.' 19 | end 20 | 21 | # @api private 22 | # @return [String] 23 | def failure_message 24 | unless iterable? 25 | return "#{improve_hash_formatting(super)}, but was not iterable" 26 | end 27 | 28 | all_messages = [improve_hash_formatting(super)] 29 | failed_objects.each do |index, matcher_failure_message| 30 | all_messages << failure_message_for_item(index, matcher_failure_message) 31 | end 32 | all_messages.join("\n\n") 33 | end 34 | 35 | # @api private 36 | # @return [String] 37 | def description 38 | improve_hash_formatting "all #{description_of matcher}" 39 | end 40 | 41 | private 42 | 43 | def match(_expected, _actual) 44 | return false unless iterable? 45 | 46 | index_failed_objects 47 | failed_objects.empty? 48 | end 49 | 50 | def index_failed_objects 51 | actual.each_with_index do |actual_item, index| 52 | cloned_matcher = matcher.clone 53 | matches = cloned_matcher.matches?(actual_item) 54 | failed_objects[index] = cloned_matcher.failure_message unless matches 55 | end 56 | end 57 | 58 | def failure_message_for_item(index, failure_message) 59 | failure_message = indent_multiline_message(add_new_line_if_needed(failure_message)) 60 | indent_multiline_message("object at index #{index} failed to match:#{failure_message}") 61 | end 62 | 63 | def add_new_line_if_needed(message) 64 | message.start_with?("\n") ? message : "\n#{message}" 65 | end 66 | 67 | def indent_multiline_message(message) 68 | message = message.sub(/\n+\z/, '') 69 | message.lines.map do |line| 70 | line =~ /\S/ ? ' ' + line : line 71 | end.join 72 | end 73 | 74 | def initialize_copy(other) 75 | @matcher = @matcher.clone 76 | @failed_objects = @failed_objects.clone 77 | super 78 | end 79 | 80 | def iterable? 81 | @actual.respond_to?(:each_with_index) 82 | end 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/rspec/matchers/built_in/be_between.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | module BuiltIn 4 | # @api private 5 | # Provides the implementation for `be_between`. 6 | # Not intended to be instantiated directly. 7 | class BeBetween < BaseMatcher 8 | def initialize(min, max) 9 | @min, @max = min, max 10 | inclusive 11 | end 12 | 13 | # @api public 14 | # Makes the between comparison inclusive. 15 | # 16 | # @example 17 | # expect(3).to be_between(2, 3).inclusive 18 | # 19 | # @note The matcher is inclusive by default; this simply provides 20 | # a way to be more explicit about it. 21 | def inclusive 22 | @less_than_operator = :<= 23 | @greater_than_operator = :>= 24 | @mode = :inclusive 25 | self 26 | end 27 | 28 | # @api public 29 | # Makes the between comparison exclusive. 30 | # 31 | # @example 32 | # expect(3).to be_between(2, 4).exclusive 33 | def exclusive 34 | @less_than_operator = :< 35 | @greater_than_operator = :> 36 | @mode = :exclusive 37 | self 38 | end 39 | 40 | # @api private 41 | # @return [Boolean] 42 | def matches?(actual) 43 | @actual = actual 44 | comparable? && compare 45 | rescue ArgumentError 46 | false 47 | end 48 | 49 | # @api private 50 | # @return [String] 51 | def failure_message 52 | "#{super}#{not_comparable_clause}" 53 | end 54 | 55 | # @api private 56 | # @return [String] 57 | def description 58 | "be between #{description_of @min} and #{description_of @max} (#{@mode})" 59 | end 60 | 61 | private 62 | 63 | def comparable? 64 | @actual.respond_to?(@less_than_operator) && @actual.respond_to?(@greater_than_operator) 65 | end 66 | 67 | def not_comparable_clause 68 | ", but it does not respond to `#{@less_than_operator}` and `#{@greater_than_operator}`" unless comparable? 69 | end 70 | 71 | def compare 72 | @actual.__send__(@greater_than_operator, @min) && @actual.__send__(@less_than_operator, @max) 73 | end 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/rspec/matchers/built_in/be_instance_of.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | module BuiltIn 4 | # @api private 5 | # Provides the implementation for `be_an_instance_of`. 6 | # Not intended to be instantiated directly. 7 | class BeAnInstanceOf < BaseMatcher 8 | # @api private 9 | # @return [String] 10 | def description 11 | "be an instance of #{expected}" 12 | end 13 | 14 | private 15 | 16 | def match(expected, actual) 17 | actual.instance_of?(expected) 18 | rescue NoMethodError 19 | raise ::ArgumentError, "The #{matcher_name} matcher requires that " \ 20 | "the actual object responds to #instance_of? method " \ 21 | "but a `NoMethodError` was encountered instead." 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/rspec/matchers/built_in/be_kind_of.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | module BuiltIn 4 | # @api private 5 | # Provides the implementation for `be_a_kind_of`. 6 | # Not intended to be instantiated directly. 7 | class BeAKindOf < BaseMatcher 8 | private 9 | 10 | def match(expected, actual) 11 | actual.kind_of?(expected) 12 | rescue NoMethodError 13 | raise ::ArgumentError, "The #{matcher_name} matcher requires that " \ 14 | "the actual object responds to #kind_of? method " \ 15 | "but a `NoMethodError` was encountered instead." 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/rspec/matchers/built_in/be_within.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | module BuiltIn 4 | # @api private 5 | # Provides the implementation for `be_within`. 6 | # Not intended to be instantiated directly. 7 | class BeWithin < BaseMatcher 8 | def initialize(delta) 9 | @delta = delta 10 | end 11 | 12 | # @api public 13 | # Sets the expected value. 14 | def of(expected) 15 | @expected = expected 16 | @tolerance = @delta 17 | @unit = '' 18 | self 19 | end 20 | 21 | # @api public 22 | # Sets the expected value, and makes the matcher do 23 | # a percent comparison. 24 | def percent_of(expected) 25 | @expected = expected 26 | @tolerance = @expected.abs * @delta / 100.0 27 | @unit = '%' 28 | self 29 | end 30 | 31 | # @private 32 | def matches?(actual) 33 | @actual = actual 34 | raise needs_expected unless defined? @expected 35 | numeric? && (@actual - @expected).abs <= @tolerance 36 | end 37 | 38 | # @api private 39 | # @return [String] 40 | def failure_message 41 | "expected #{actual_formatted} to #{description}#{not_numeric_clause}" 42 | end 43 | 44 | # @api private 45 | # @return [String] 46 | def failure_message_when_negated 47 | "expected #{actual_formatted} not to #{description}" 48 | end 49 | 50 | # @api private 51 | # @return [String] 52 | def description 53 | "be within #{@delta}#{@unit} of #{expected_formatted}" 54 | end 55 | 56 | private 57 | 58 | def numeric? 59 | @actual.respond_to?(:-) 60 | end 61 | 62 | def needs_expected 63 | ArgumentError.new "You must set an expected value using #of: be_within(#{@delta}).of(expected_value)" 64 | end 65 | 66 | def not_numeric_clause 67 | ", but it could not be treated as a numeric value" unless numeric? 68 | end 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/rspec/matchers/built_in/cover.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | module BuiltIn 4 | # @api private 5 | # Provides the implementation for `cover`. 6 | # Not intended to be instantiated directly. 7 | class Cover < BaseMatcher 8 | def initialize(*expected) 9 | @expected = expected 10 | end 11 | 12 | def matches?(range) 13 | @actual = range 14 | @expected.all? { |e| range.cover?(e) } 15 | end 16 | 17 | def does_not_match?(range) 18 | @actual = range 19 | expected.none? { |e| range.cover?(e) } 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/rspec/matchers/built_in/eq.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | module BuiltIn 4 | # @api private 5 | # Provides the implementation for `eq`. 6 | # Not intended to be instantiated directly. 7 | class Eq < BaseMatcher 8 | # @api private 9 | # @return [String] 10 | def failure_message 11 | if string_encoding_differs? 12 | "\nexpected: #{format_encoding(expected)} #{expected_formatted}\n got: #{format_encoding(actual)} #{actual_formatted}\n\n(compared using ==)\n" 13 | else 14 | "\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n" 15 | end 16 | end 17 | 18 | # @api private 19 | # @return [String] 20 | def failure_message_when_negated 21 | "\nexpected: value != #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n" 22 | end 23 | 24 | # @api private 25 | # @return [String] 26 | def description 27 | "eq #{expected_formatted}" 28 | end 29 | 30 | # @api private 31 | # @return [Boolean] 32 | def diffable? 33 | true 34 | end 35 | 36 | private 37 | 38 | def match(expected, actual) 39 | actual == expected 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/rspec/matchers/built_in/eql.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | module BuiltIn 4 | # @api private 5 | # Provides the implementation for `eql`. 6 | # Not intended to be instantiated directly. 7 | class Eql < BaseMatcher 8 | # @api private 9 | # @return [String] 10 | def failure_message 11 | if string_encoding_differs? 12 | "\nexpected: #{format_encoding(expected)} #{expected_formatted}\n got: #{format_encoding(actual)} #{actual_formatted}\n\n(compared using eql?)\n" 13 | else 14 | "\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using eql?)\n" 15 | end 16 | end 17 | 18 | # @api private 19 | # @return [String] 20 | def failure_message_when_negated 21 | "\nexpected: value != #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using eql?)\n" 22 | end 23 | 24 | # @api private 25 | # @return [Boolean] 26 | def diffable? 27 | true 28 | end 29 | 30 | private 31 | 32 | def match(expected, actual) 33 | actual.eql? expected 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/rspec/matchers/built_in/equal.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | module BuiltIn 4 | # @api private 5 | # Provides the implementation for `equal`. 6 | # Not intended to be instantiated directly. 7 | class Equal < BaseMatcher 8 | # @api private 9 | # @return [String] 10 | def failure_message 11 | if expected_is_a_literal_singleton? 12 | simple_failure_message 13 | else 14 | detailed_failure_message 15 | end 16 | end 17 | 18 | # @api private 19 | # @return [String] 20 | def failure_message_when_negated 21 | <<-MESSAGE 22 | 23 | expected not #{inspect_object(actual)} 24 | got #{inspect_object(expected)} 25 | 26 | Compared using equal?, which compares object identity. 27 | 28 | MESSAGE 29 | end 30 | 31 | # @api private 32 | # @return [Boolean] 33 | def diffable? 34 | !expected_is_a_literal_singleton? 35 | end 36 | 37 | private 38 | 39 | def match(expected, actual) 40 | actual.equal? expected 41 | end 42 | 43 | LITERAL_SINGLETONS = [true, false, nil] 44 | 45 | def expected_is_a_literal_singleton? 46 | LITERAL_SINGLETONS.include?(expected) 47 | end 48 | 49 | def actual_inspected 50 | if LITERAL_SINGLETONS.include?(actual) 51 | actual_formatted 52 | else 53 | inspect_object(actual) 54 | end 55 | end 56 | 57 | def simple_failure_message 58 | "\nexpected #{expected_formatted}\n got #{actual_inspected}\n" 59 | end 60 | 61 | def detailed_failure_message 62 | <<-MESSAGE 63 | 64 | expected #{inspect_object(expected)} 65 | got #{inspect_object(actual)} 66 | 67 | Compared using equal?, which compares object identity, 68 | but expected and actual are not the same object. Use 69 | `expect(actual).to eq(expected)` if you don't care about 70 | object identity in this example. 71 | 72 | MESSAGE 73 | end 74 | 75 | def inspect_object(o) 76 | "#<#{o.class}:#{o.object_id}> => #{RSpec::Support::ObjectFormatter.format(o)}" 77 | end 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/rspec/matchers/built_in/exist.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | module BuiltIn 4 | # @api private 5 | # Provides the implementation for `exist`. 6 | # Not intended to be instantiated directly. 7 | class Exist < BaseMatcher 8 | def initialize(*expected) 9 | @expected = expected 10 | end 11 | 12 | # @api private 13 | # @return [Boolean] 14 | def matches?(actual) 15 | @actual = actual 16 | @test = ExistenceTest.new @actual, @expected 17 | @test.valid_test? && @test.actual_exists? 18 | end 19 | 20 | # @api private 21 | # @return [Boolean] 22 | def does_not_match?(actual) 23 | @actual = actual 24 | @test = ExistenceTest.new @actual, @expected 25 | @test.valid_test? && !@test.actual_exists? 26 | end 27 | 28 | # @api private 29 | # @return [String] 30 | def failure_message 31 | "expected #{actual_formatted} to exist#{@test.validity_message}" 32 | end 33 | 34 | # @api private 35 | # @return [String] 36 | def failure_message_when_negated 37 | "expected #{actual_formatted} not to exist#{@test.validity_message}" 38 | end 39 | 40 | # @api private 41 | # Simple class for memoizing actual/expected for this matcher 42 | # and examining the match 43 | class ExistenceTest < Struct.new(:actual, :expected) 44 | # @api private 45 | # @return [Boolean] 46 | def valid_test? 47 | uniq_truthy_values.size == 1 48 | end 49 | 50 | # @api private 51 | # @return [Boolean] 52 | def actual_exists? 53 | existence_values.first 54 | end 55 | 56 | # @api private 57 | # @return [String] 58 | def validity_message 59 | case uniq_truthy_values.size 60 | when 0 61 | " but it does not respond to either `exist?` or `exists?`" 62 | when 2 63 | " but `exist?` and `exists?` returned different values:\n\n"\ 64 | " exist?: #{existence_values.first}\n"\ 65 | "exists?: #{existence_values.last}" 66 | end 67 | end 68 | 69 | private 70 | 71 | def uniq_truthy_values 72 | @uniq_truthy_values ||= existence_values.map { |v| !!v }.uniq 73 | end 74 | 75 | def existence_values 76 | @existence_values ||= predicates.map { |p| actual.__send__(p, *expected) } 77 | end 78 | 79 | def predicates 80 | @predicates ||= [:exist?, :exists?].select { |p| actual.respond_to?(p) && !deprecated(p, actual) } 81 | end 82 | 83 | def deprecated(predicate, actual) 84 | predicate == :exists? && (File == actual || FileTest == actual || Dir == actual) 85 | end 86 | end 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/rspec/matchers/built_in/have_attributes.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | module BuiltIn 4 | # @api private 5 | # Provides the implementation for `have_attributes`. 6 | # Not intended to be instantiated directly. 7 | class HaveAttributes < BaseMatcher 8 | # @private 9 | attr_reader :respond_to_failed 10 | 11 | def initialize(expected) 12 | @expected = expected 13 | @values = {} 14 | @respond_to_failed = false 15 | @negated = false 16 | end 17 | 18 | # @private 19 | def actual 20 | @values 21 | end 22 | 23 | # @api private 24 | # @return [Boolean] 25 | def matches?(actual) 26 | @actual = actual 27 | @negated = false 28 | return false unless respond_to_attributes? 29 | perform_match(:all?) 30 | end 31 | 32 | # @api private 33 | # @return [Boolean] 34 | def does_not_match?(actual) 35 | @actual = actual 36 | @negated = true 37 | return false unless respond_to_attributes? 38 | perform_match(:none?) 39 | end 40 | 41 | # @api private 42 | # @return [String] 43 | def description 44 | described_items = surface_descriptions_in(expected) 45 | improve_hash_formatting "have attributes #{RSpec::Support::ObjectFormatter.format(described_items)}" 46 | end 47 | 48 | # @api private 49 | # @return [Boolean] 50 | def diffable? 51 | !@respond_to_failed && !@negated 52 | end 53 | 54 | # @api private 55 | # @return [String] 56 | def failure_message 57 | respond_to_failure_message_or do 58 | "expected #{actual_formatted} to #{description} but had attributes #{ formatted_values }" 59 | end 60 | end 61 | 62 | # @api private 63 | # @return [String] 64 | def failure_message_when_negated 65 | respond_to_failure_message_or { "expected #{actual_formatted} not to #{description}" } 66 | end 67 | 68 | private 69 | 70 | def cache_all_values 71 | @values = {} 72 | expected.each do |attribute_key, _attribute_value| 73 | actual_value = @actual.__send__(attribute_key) 74 | @values[attribute_key] = actual_value 75 | end 76 | end 77 | 78 | def perform_match(predicate) 79 | cache_all_values 80 | expected.__send__(predicate) do |attribute_key, attribute_value| 81 | actual_has_attribute?(attribute_key, attribute_value) 82 | end 83 | end 84 | 85 | def actual_has_attribute?(attribute_key, attribute_value) 86 | values_match?(attribute_value, @values.fetch(attribute_key)) 87 | end 88 | 89 | def respond_to_attributes? 90 | matches = respond_to_matcher.matches?(@actual) 91 | @respond_to_failed = !matches 92 | matches 93 | end 94 | 95 | def respond_to_matcher 96 | @respond_to_matcher ||= RespondTo.new(*expected.keys).with(0).arguments.tap { |m| m.ignoring_method_signature_failure! } 97 | end 98 | 99 | def respond_to_failure_message_or 100 | if respond_to_failed 101 | respond_to_matcher.failure_message 102 | else 103 | improve_hash_formatting(yield) 104 | end 105 | end 106 | 107 | def formatted_values 108 | values = RSpec::Support::ObjectFormatter.format(@values) 109 | improve_hash_formatting(values) 110 | end 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /lib/rspec/matchers/built_in/match.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | module BuiltIn 4 | # @api private 5 | # Provides the implementation for `match`. 6 | # Not intended to be instantiated directly. 7 | class Match < BaseMatcher 8 | def initialize(expected) 9 | super(expected) 10 | 11 | @expected_captures = nil 12 | end 13 | # @api private 14 | # @return [String] 15 | def description 16 | if @expected_captures && @expected.match(actual) 17 | "match #{surface_descriptions_in(expected).inspect} with captures #{surface_descriptions_in(@expected_captures).inspect}" 18 | else 19 | "match #{surface_descriptions_in(expected).inspect}" 20 | end 21 | end 22 | 23 | # @api private 24 | # @return [Boolean] 25 | def diffable? 26 | true 27 | end 28 | 29 | # Used to specify the captures we match against 30 | # @return [self] 31 | def with_captures(*captures) 32 | @expected_captures = captures 33 | self 34 | end 35 | 36 | private 37 | 38 | def match(expected, actual) 39 | return match_captures(expected, actual) if @expected_captures 40 | return true if values_match?(expected, actual) 41 | return false unless can_safely_call_match?(expected, actual) 42 | actual.match(expected) 43 | end 44 | 45 | def can_safely_call_match?(expected, actual) 46 | return false unless actual.respond_to?(:match) 47 | 48 | !(RSpec::Matchers.is_a_matcher?(expected) && 49 | (String === actual || Regexp === actual)) 50 | end 51 | 52 | def match_captures(expected, actual) 53 | match = actual.match(expected) 54 | if match 55 | match = ReliableMatchData.new(match) 56 | if match.names.empty? 57 | values_match?(@expected_captures, match.captures) 58 | else 59 | expected_matcher = @expected_captures.last 60 | values_match?(expected_matcher, Hash[match.names.zip(match.captures)]) || 61 | values_match?(expected_matcher, Hash[match.names.map(&:to_sym).zip(match.captures)]) || 62 | values_match?(@expected_captures, match.captures) 63 | end 64 | else 65 | false 66 | end 67 | end 68 | end 69 | 70 | # @api private 71 | # Used to wrap match data and make it reliable for 1.8.7 72 | class ReliableMatchData 73 | def initialize(match_data) 74 | @match_data = match_data 75 | end 76 | 77 | if RUBY_VERSION == "1.8.7" 78 | # @api private 79 | # Returns match data names for named captures 80 | # @return Array 81 | # :nocov: 82 | def names 83 | [] 84 | end 85 | # :nocov: 86 | else 87 | # @api private 88 | # Returns match data names for named captures 89 | # @return Array 90 | def names 91 | match_data.names 92 | end 93 | end 94 | 95 | # @api private 96 | # returns an array of captures from the match data 97 | # @return Array 98 | def captures 99 | match_data.captures 100 | end 101 | 102 | protected 103 | 104 | attr_reader :match_data 105 | end 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /lib/rspec/matchers/built_in/satisfy.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | module BuiltIn 4 | # @api private 5 | # Provides the implementation for `satisfy`. 6 | # Not intended to be instantiated directly. 7 | class Satisfy < BaseMatcher 8 | def initialize(description=nil, &block) 9 | @description = description 10 | @block = block 11 | end 12 | 13 | # @private 14 | def matches?(actual, &block) 15 | @block = block if block 16 | @actual = actual 17 | @block.call(actual) 18 | end 19 | 20 | # @private 21 | def description 22 | @description ||= "satisfy #{block_representation}" 23 | end 24 | 25 | # @api private 26 | # @return [String] 27 | def failure_message 28 | "expected #{actual_formatted} to #{description}" 29 | end 30 | 31 | # @api private 32 | # @return [String] 33 | def failure_message_when_negated 34 | "expected #{actual_formatted} not to #{description}" 35 | end 36 | 37 | private 38 | 39 | if RSpec::Support::RubyFeatures.ripper_supported? 40 | def block_representation 41 | if (block_snippet = extract_block_snippet) 42 | "expression `#{block_snippet}`" 43 | else 44 | 'block' 45 | end 46 | end 47 | 48 | def extract_block_snippet 49 | return nil unless @block 50 | Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@block, matcher_name) 51 | end 52 | else 53 | # :nocov: 54 | def block_representation 55 | 'block' 56 | end 57 | # :nocov: 58 | end 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/rspec/matchers/built_in/start_or_end_with.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | module BuiltIn 4 | # @api private 5 | # Base class for the `end_with` and `start_with` matchers. 6 | # Not intended to be instantiated directly. 7 | class StartOrEndWith < BaseMatcher 8 | def initialize(*expected) 9 | @actual_does_not_have_ordered_elements = false 10 | @expected = expected.length == 1 ? expected.first : expected 11 | end 12 | 13 | # @api private 14 | # @return [String] 15 | def failure_message 16 | super.tap do |msg| 17 | if @actual_does_not_have_ordered_elements 18 | msg << ", but it does not have ordered elements" 19 | elsif !actual.respond_to?(:[]) 20 | msg << ", but it cannot be indexed using #[]" 21 | end 22 | end 23 | end 24 | 25 | # @api private 26 | # @return [String] 27 | def description 28 | return super unless Hash === expected 29 | english_name = EnglishPhrasing.split_words(self.class.matcher_name) 30 | description_of_expected = surface_descriptions_in(expected).inspect 31 | "#{english_name} #{description_of_expected}" 32 | end 33 | 34 | private 35 | 36 | def match(_expected, actual) 37 | return false unless actual.respond_to?(:[]) 38 | 39 | begin 40 | return true if subsets_comparable? && subset_matches? 41 | element_matches? 42 | rescue ArgumentError 43 | @actual_does_not_have_ordered_elements = true 44 | return false 45 | end 46 | end 47 | 48 | def subsets_comparable? 49 | # Structs support the Enumerable interface but don't really have 50 | # the semantics of a subset of a larger set... 51 | return false if Struct === expected 52 | 53 | expected.respond_to?(:length) 54 | end 55 | end 56 | 57 | # For RSpec 3.1, the base class was named `StartAndEndWith`. For SemVer reasons, 58 | # we still provide this constant until 4.0. 59 | # @deprecated Use StartOrEndWith instead. 60 | # @private 61 | StartAndEndWith = StartOrEndWith 62 | 63 | # @api private 64 | # Provides the implementation for `start_with`. 65 | # Not intended to be instantiated directly. 66 | class StartWith < StartOrEndWith 67 | private 68 | 69 | def subset_matches? 70 | values_match?(expected, actual[0, expected.length]) 71 | end 72 | 73 | def element_matches? 74 | values_match?(expected, actual[0]) 75 | end 76 | end 77 | 78 | # @api private 79 | # Provides the implementation for `end_with`. 80 | # Not intended to be instantiated directly. 81 | class EndWith < StartOrEndWith 82 | private 83 | 84 | def subset_matches? 85 | values_match?(expected, actual[-expected.length, expected.length]) 86 | end 87 | 88 | def element_matches? 89 | values_match?(expected, actual[-1]) 90 | end 91 | end 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /lib/rspec/matchers/english_phrasing.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | # Facilitates converting ruby objects to English phrases. 4 | module EnglishPhrasing 5 | # Converts a symbol into an English expression. 6 | # 7 | # split_words(:banana_creme_pie) #=> "banana creme pie" 8 | # 9 | def self.split_words(sym) 10 | sym.to_s.tr('_', ' ') 11 | end 12 | 13 | # @note The returned string has a leading space except 14 | # when given an empty list. 15 | # 16 | # Converts an object (often a collection of objects) 17 | # into an English list. 18 | # 19 | # list(['banana', 'kiwi', 'mango']) 20 | # #=> " \"banana\", \"kiwi\", and \"mango\"" 21 | # 22 | # Given an empty collection, returns the empty string. 23 | # 24 | # list([]) #=> "" 25 | # 26 | def self.list(obj) 27 | return " #{RSpec::Support::ObjectFormatter.format(obj)}" if !obj || Struct === obj || Hash === obj 28 | items = Array(obj).map { |w| RSpec::Support::ObjectFormatter.format(w) } 29 | case items.length 30 | when 0 31 | "" 32 | when 1 33 | " #{items[0]}" 34 | when 2 35 | " #{items[0]} and #{items[1]}" 36 | else 37 | " #{items[0...-1].join(', ')}, and #{items[-1]}" 38 | end 39 | end 40 | 41 | if RUBY_VERSION == '1.8.7' 42 | # Not sure why, but on travis on 1.8.7 we have gotten these warnings: 43 | # lib/rspec/matchers/english_phrasing.rb:28: warning: default `to_a' will be obsolete 44 | # So it appears that `Array` can trigger that (e.g. by calling `to_a` on the passed object?) 45 | # So here we replace `Kernel#Array` with our own warning-free implementation for 1.8.7. 46 | # @private 47 | # rubocop:disable Naming/MethodName 48 | # :nocov: 49 | def self.Array(obj) 50 | case obj 51 | when Array then obj 52 | else [obj] 53 | end 54 | end 55 | # :nocov: 56 | # rubocop:enable Naming/MethodName 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/rspec/matchers/fail_matchers.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/expectations' 2 | 3 | module RSpec 4 | module Matchers 5 | # Matchers for testing RSpec matchers. Include them with: 6 | # 7 | # require 'rspec/matchers/fail_matchers' 8 | # RSpec.configure do |config| 9 | # config.include RSpec::Matchers::FailMatchers 10 | # end 11 | # 12 | module FailMatchers 13 | # Matches if an expectation fails 14 | # 15 | # @example 16 | # expect { some_expectation }.to fail 17 | def fail(&block) 18 | raise_error(RSpec::Expectations::ExpectationNotMetError, &block) 19 | end 20 | 21 | # Matches if an expectation fails with the provided message 22 | # 23 | # @example 24 | # expect { some_expectation }.to fail_with("some failure message") 25 | # expect { some_expectation }.to fail_with(/some failure message/) 26 | def fail_with(message) 27 | raise_error(RSpec::Expectations::ExpectationNotMetError, message) 28 | end 29 | 30 | # Matches if an expectation fails including the provided message 31 | # 32 | # @example 33 | # expect { some_expectation }.to fail_including("portion of some failure message") 34 | def fail_including(*snippets) 35 | raise_error( 36 | RSpec::Expectations::ExpectationNotMetError, 37 | a_string_including(*snippets) 38 | ) 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/rspec/matchers/generated_descriptions.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | class << self 4 | # @private 5 | attr_accessor :last_matcher, :last_expectation_handler 6 | end 7 | 8 | # @api private 9 | # Used by rspec-core to clear the state used to generate 10 | # descriptions after an example. 11 | def self.clear_generated_description 12 | self.last_matcher = nil 13 | self.last_expectation_handler = nil 14 | end 15 | 16 | # @api private 17 | # Generates an an example description based on the last expectation. 18 | # Used by rspec-core's one-liner syntax. 19 | def self.generated_description 20 | return nil if last_expectation_handler.nil? 21 | "#{last_expectation_handler.verb} #{last_description}" 22 | end 23 | 24 | # @private 25 | def self.last_description 26 | last_matcher.respond_to?(:description) ? last_matcher.description : <<-MESSAGE 27 | When you call a matcher in an example without a String, like this: 28 | 29 | specify { expect(object).to matcher } 30 | 31 | or this: 32 | 33 | it { is_expected.to matcher } 34 | 35 | RSpec expects the matcher to have a #description method. You should either 36 | add a String to the example this matcher is being used in, or give it a 37 | description method. Then you won't have to suffer this lengthy warning again. 38 | MESSAGE 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/rspec/matchers/matcher_delegator.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | # Provides a base class with as little methods as possible, so that 4 | # most methods can be delegated via `method_missing`. 5 | # 6 | # On Ruby 2.0+ BasicObject could be used for this purpose, but it 7 | # introduce some extra complexity with constant resolution, so the 8 | # BlankSlate pattern was prefered. 9 | # @private 10 | class BaseDelegator 11 | kept_methods = [ 12 | # Methods that raise warnings if removed. 13 | :__id__, :__send__, :object_id, 14 | 15 | # Methods that are explicitly undefined in some subclasses. 16 | :==, :===, 17 | 18 | # Methods we keep on purpose. 19 | :class, :respond_to?, :__method__, :method, :dup, 20 | :clone, :initialize_dup, :initialize_copy, :initialize_clone, 21 | ] 22 | instance_methods.each do |method| 23 | unless kept_methods.include?(method.to_sym) 24 | undef_method(method) 25 | end 26 | end 27 | end 28 | 29 | # Provides the necessary plumbing to wrap a matcher with a decorator. 30 | # @private 31 | class MatcherDelegator < BaseDelegator 32 | include Composable 33 | attr_reader :base_matcher 34 | 35 | def initialize(base_matcher) 36 | @base_matcher = base_matcher 37 | end 38 | 39 | def method_missing(*args, &block) 40 | base_matcher.__send__(*args, &block) 41 | end 42 | 43 | if ::RUBY_VERSION.to_f > 1.8 44 | def respond_to_missing?(name, include_all=false) 45 | super || base_matcher.respond_to?(name, include_all) 46 | end 47 | else 48 | # :nocov: 49 | def respond_to?(name, include_all=false) 50 | super || base_matcher.respond_to?(name, include_all) 51 | end 52 | # :nocov: 53 | end 54 | 55 | def initialize_copy(other) 56 | @base_matcher = @base_matcher.clone 57 | super 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/rspec/matchers/multi_matcher_diff.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | # @api private 4 | # Handles list of expected and actual value pairs when there is a need 5 | # to render multiple diffs. Also can handle one pair. 6 | class MultiMatcherDiff 7 | # @private 8 | # Default diff label when there is only one matcher in diff 9 | # output 10 | DEFAULT_DIFF_LABEL = "Diff:".freeze 11 | 12 | # @private 13 | # Maximum readable matcher description length 14 | DESCRIPTION_MAX_LENGTH = 65 15 | 16 | def initialize(expected_list) 17 | @expected_list = expected_list 18 | end 19 | 20 | # @api private 21 | # Wraps provided expected value in instance of 22 | # MultiMatcherDiff. If provided value is already an 23 | # MultiMatcherDiff then it just returns it. 24 | # @param [Any] expected value to be wrapped 25 | # @param [Any] actual value 26 | # @return [RSpec::Matchers::MultiMatcherDiff] 27 | def self.from(expected, actual) 28 | return expected if self === expected 29 | new([[expected, DEFAULT_DIFF_LABEL, actual]]) 30 | end 31 | 32 | # @api private 33 | # Wraps provided matcher list in instance of 34 | # MultiMatcherDiff. 35 | # @param [Array] matchers list of matchers to wrap 36 | # @return [RSpec::Matchers::MultiMatcherDiff] 37 | def self.for_many_matchers(matchers) 38 | new(matchers.map { |m| [m.expected, diff_label_for(m), m.actual] }) 39 | end 40 | 41 | # @api private 42 | # Returns message with diff(s) appended for provided differ 43 | # factory and actual value if there are any 44 | # @param [String] message original failure message 45 | # @param [Proc] differ 46 | # @return [String] 47 | def message_with_diff(message, differ) 48 | diff = diffs(differ) 49 | message = "#{message}\n#{diff}" unless diff.empty? 50 | message 51 | end 52 | 53 | private 54 | 55 | class << self 56 | private 57 | 58 | def diff_label_for(matcher) 59 | "Diff for (#{truncated(RSpec::Support::ObjectFormatter.format(matcher))}):" 60 | end 61 | 62 | def truncated(description) 63 | return description if description.length <= DESCRIPTION_MAX_LENGTH 64 | description[0...DESCRIPTION_MAX_LENGTH - 3] << "..." 65 | end 66 | end 67 | 68 | def diffs(differ) 69 | @expected_list.map do |(expected, diff_label, actual)| 70 | diff = differ.diff(actual, expected) 71 | next if diff.strip.empty? 72 | if diff == "\e[0m\n\e[0m" 73 | "#{diff_label}\n" \ 74 | " " 75 | else 76 | "#{diff_label}#{diff}" 77 | end 78 | end.compact.join("\n") 79 | end 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /maintenance-branch: -------------------------------------------------------------------------------- 1 | main 2 | -------------------------------------------------------------------------------- /rspec-expectations.gemspec: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path("../lib", __FILE__) 2 | require "rspec/expectations/version" 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "rspec-expectations" 6 | s.version = RSpec::Expectations::Version::STRING 7 | s.platform = Gem::Platform::RUBY 8 | s.license = "MIT" 9 | s.authors = ["Steven Baker", "David Chelimsky", "Myron Marston"] 10 | s.email = "rspec@googlegroups.com" 11 | s.homepage = "https://github.com/rspec/rspec-expectations" 12 | s.summary = "rspec-expectations-#{RSpec::Expectations::Version::STRING}" 13 | s.description = "rspec-expectations provides a simple, readable API to express expected outcomes of a code example." 14 | 15 | s.metadata = { 16 | 'bug_tracker_uri' => 'https://github.com/rspec/rspec-expectations/issues', 17 | 'changelog_uri' => "https://github.com/rspec/rspec-expectations/blob/v#{s.version}/Changelog.md", 18 | 'documentation_uri' => 'https://rspec.info/documentation/', 19 | 'mailing_list_uri' => 'https://groups.google.com/forum/#!forum/rspec', 20 | 'source_code_uri' => 'https://github.com/rspec/rspec-expectations', 21 | } 22 | 23 | s.files = `git ls-files -- lib/*`.split("\n") 24 | s.files += %w[README.md LICENSE.md Changelog.md .yardopts .document] 25 | s.test_files = [] 26 | s.rdoc_options = ["--charset=UTF-8"] 27 | s.require_path = "lib" 28 | 29 | s.required_ruby_version = '>= 1.8.7' # rubocop:disable Gemspec/RequiredRubyVersion 30 | 31 | private_key = File.expand_path('~/.gem/rspec-gem-private_key.pem') 32 | if File.exist?(private_key) 33 | s.signing_key = private_key 34 | s.cert_chain = [File.expand_path('~/.gem/rspec-gem-public_cert.pem')] 35 | end 36 | 37 | if RSpec::Expectations::Version::STRING =~ /[a-zA-Z]+/ 38 | # pin to exact version for rc's and betas 39 | s.add_runtime_dependency "rspec-support", "= #{RSpec::Expectations::Version::STRING}" 40 | else 41 | # pin to major/minor ignoring patch 42 | s.add_runtime_dependency "rspec-support", "~> #{RSpec::Expectations::Version::STRING.split('.')[0..1].concat(['0']).join('.')}" 43 | end 44 | 45 | s.add_runtime_dependency "diff-lcs", ">= 1.2.0", "< 2.0" 46 | 47 | s.add_development_dependency "aruba", "~> 0.14.10" 48 | s.add_development_dependency 'cucumber', '>= 1.3' 49 | s.add_development_dependency 'minitest', '~> 5.2' 50 | s.add_development_dependency 'rake', '> 10.0.0' 51 | end 52 | -------------------------------------------------------------------------------- /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/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/rspec/expectations/extensions/kernel_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Object, "#should" do 2 | before(:example) do 3 | @target = "target" 4 | @matcher = double("matcher", :supports_value_expectations? => true) 5 | allow(@matcher).to receive(:matches?).and_return(true) 6 | allow(@matcher).to receive(:failure_message) 7 | end 8 | 9 | it "accepts and interacts with a matcher" do 10 | expect(@matcher).to receive(:matches?).with(@target).and_return(true) 11 | expect(@target).to @matcher 12 | end 13 | 14 | it "asks for a failure_message when matches? returns false" do 15 | expect(@matcher).to receive(:matches?).with(@target).and_return(false) 16 | expect(@matcher).to receive(:failure_message).and_return("the failure message") 17 | expect { 18 | expect(@target).to @matcher 19 | }.to fail_with("the failure message") 20 | end 21 | 22 | context "on interpreters that have BasicObject", :if => defined?(BasicObject) do 23 | let(:proxy_class) do 24 | Class.new(BasicObject) do 25 | def initialize(target) 26 | @target = target 27 | end 28 | 29 | def proxied? 30 | true 31 | end 32 | 33 | def respond_to?(method, *args) 34 | method.to_sym == :proxied? || @target.respond_to?(symbol, *args) 35 | end 36 | 37 | def method_missing(name, *args) 38 | @target.send(name, *args) 39 | end 40 | end 41 | end 42 | 43 | it 'works properly on BasicObject-subclassed proxy objects' do 44 | expect(proxy_class.new(Object.new)).to be_proxied 45 | end 46 | 47 | it 'does not break the deprecation check on BasicObject-subclassed proxy objects' do 48 | begin 49 | should_enabled = RSpec::Expectations::Syntax.should_enabled? 50 | RSpec::Expectations::Syntax.enable_should unless should_enabled 51 | proxy_class.new(BasicObject.new).should be_proxied 52 | ensure 53 | RSpec::Expectations::Syntax.disable_should if should_enabled 54 | end 55 | end 56 | end 57 | end 58 | 59 | RSpec.describe Object, "#should_not" do 60 | before(:example) do 61 | @target = "target" 62 | @matcher = double("matcher", :supports_value_expectations? => true) 63 | end 64 | 65 | it "accepts and interacts with a matcher" do 66 | expect(@matcher).to receive(:matches?).with(@target).and_return(false) 67 | allow(@matcher).to receive(:failure_message_when_negated) 68 | 69 | expect(@target).not_to @matcher 70 | end 71 | 72 | it "asks for a failure_message_when_negated when matches? returns true" do 73 | expect(@matcher).to receive(:matches?).with(@target).and_return(true) 74 | expect(@matcher).to receive(:failure_message_when_negated).and_return("the failure message for should not") 75 | expect { 76 | expect(@target).not_to @matcher 77 | }.to fail_with("the failure message for should not") 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /spec/rspec/expectations/fail_with_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe RSpec::Expectations, "#fail_with" do 2 | let(:differ) { double("differ") } 3 | 4 | before(:example) do 5 | allow(RSpec::Matchers.configuration).to receive_messages(:color? => false) 6 | allow(RSpec::Expectations).to receive(:differ) { differ } 7 | end 8 | 9 | it "includes a diff if expected and actual are diffable" do 10 | expect(differ).to receive(:diff).and_return("diff text") 11 | 12 | expect { 13 | RSpec::Expectations.fail_with "message", "abc", "def" 14 | }.to fail_with("message\nDiff:diff text") 15 | end 16 | 17 | it "does not include the diff if expected and actual are not diffable" do 18 | expect(differ).to receive(:diff).and_return("") 19 | 20 | expect { 21 | RSpec::Expectations.fail_with "message", "abc", "def" 22 | }.to fail_with("message") 23 | end 24 | 25 | it "raises an error if message is not present" do 26 | expect(differ).not_to receive(:diff) 27 | 28 | expect { 29 | RSpec::Expectations.fail_with nil 30 | }.to raise_error(ArgumentError, /Failure message is nil\./) 31 | end 32 | end 33 | 34 | RSpec.describe RSpec::Expectations, "#fail_with with matchers" do 35 | include RSpec::Support::Spec::DiffHelpers 36 | 37 | before do 38 | allow(RSpec::Matchers.configuration).to receive_messages(:color? => false) 39 | end 40 | 41 | it "uses matcher descriptions in place of matchers in diffs" do 42 | expected = [a_string_matching(/foo/), a_string_matching(/bar/)] 43 | actual = ["poo", "car"] 44 | 45 | expected_diff = dedent(<<-EOS) 46 | | 47 | |@@ #{one_line_header} @@ 48 | |-["poo", "car"] 49 | |+[(a string matching /foo/), (a string matching /bar/)] 50 | | 51 | EOS 52 | 53 | expect { 54 | RSpec::Expectations.fail_with "message", actual, expected 55 | }.to fail_with("message\nDiff:#{expected_diff}") 56 | end 57 | end 58 | 59 | RSpec.describe RSpec::Expectations, "#fail_with with --color" do 60 | include RSpec::Support::Spec::DiffHelpers 61 | 62 | before do 63 | allow(RSpec::Matchers.configuration).to receive_messages(:color? => true) 64 | end 65 | 66 | it "tells the differ to use color" do 67 | expected = "foo bar baz\n" 68 | actual = "foo bang baz\n" 69 | expected_diff = "\e[0m\n\e[0m\e[34m@@ #{one_line_header} @@\n\e[0m\e[31m-foo bang baz\n\e[0m\e[32m+foo bar baz\n\e[0m" 70 | 71 | expect { 72 | RSpec::Expectations.fail_with "message", actual, expected 73 | }.to fail_with("message\nDiff:#{expected_diff}") 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/rspec/expectations/minitest_integration_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | RSpec.describe Matchers do 3 | 4 | let(:sample_matchers) do 5 | [:be, 6 | :be_instance_of, 7 | :be_kind_of] 8 | end 9 | 10 | context "once required", :slow do 11 | include MinitestIntegration 12 | 13 | it "includes itself in Minitest::Test, and sets up our exceptions to be counted as assertion failures" do 14 | with_minitest_loaded do 15 | minitest_case = ::Minitest::Test.allocate 16 | expect(minitest_case).to respond_to(*sample_matchers) 17 | 18 | expect(RSpec::Expectations::ExpectationNotMetError).to be ::Minitest::Assertion 19 | end 20 | end 21 | 22 | end 23 | 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/rspec/expectations/syntax_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Expectations 3 | RSpec.describe Syntax do 4 | context "when passing a message to an expectation" do 5 | let(:warner) { ::Kernel } 6 | 7 | let(:string_like_object) do 8 | Struct.new(:to_str, :to_s).new(*(["Ceci n'est pas une Chaine."]*2)) 9 | end 10 | 11 | let(:insufficiently_string_like_object) do 12 | Struct.new(:to_s).new("Ceci n'est pas une Chaine.") 13 | end 14 | 15 | let(:callable_object) do 16 | Struct.new(:call).new("Ceci n'est pas une Chaine.") 17 | end 18 | 19 | describe "expect(...).to" do 20 | it "prints a warning when the message object isn't a String" do 21 | expect(warner).to receive(:warn).with(/ignoring.*message/) 22 | expect(3).to eq(3), :not_a_string 23 | end 24 | 25 | it "doesn't print a warning when message is a String" do 26 | expect(warner).not_to receive(:warn) 27 | expect(3).to eq(3), "a string" 28 | end 29 | 30 | it "doesn't print a warning when message responds to to_str" do 31 | expect(warner).not_to receive(:warn) 32 | expect(3).to eq(3), string_like_object 33 | end 34 | 35 | it "prints a warning when the message object handles to_s but not to_str" do 36 | expect(warner).to receive(:warn).with(/ignoring.*message/) 37 | expect(3).to eq(3), insufficiently_string_like_object 38 | end 39 | 40 | it "doesn't print a warning when message responds to call" do 41 | expect(warner).not_to receive(:warn) 42 | expect(3).to eq(3), callable_object 43 | end 44 | end 45 | 46 | describe "expect(...).not_to" do 47 | it "prints a warning when the message object isn't a String" do 48 | expect(warner).to receive(:warn).with(/ignoring.*message/) 49 | expect(3).not_to eq(4), :not_a_string 50 | end 51 | 52 | it "doesn't print a warning when message is a String" do 53 | expect(warner).not_to receive(:warn) 54 | expect(3).not_to eq(4), "a string" 55 | end 56 | 57 | it "doesn't print a warning when message responds to to_str" do 58 | expect(warner).not_to receive(:warn) 59 | expect(3).not_to eq(4), string_like_object 60 | end 61 | 62 | it "prints a warning when the message object handles to_s but not to_str" do 63 | expect(warner).to receive(:warn).with(/ignoring.*message/) 64 | expect(3).not_to eq(4), insufficiently_string_like_object 65 | end 66 | 67 | it "doesn't print a warning when message responds to call" do 68 | expect(warner).not_to receive(:warn) 69 | expect(3).not_to eq(4), callable_object 70 | end 71 | end 72 | end 73 | 74 | describe "enabling the should syntax on something other than the default syntax host" do 75 | include_context "with the default expectation syntax" 76 | 77 | it "continues to warn about the should syntax" do 78 | my_host = Class.new 79 | expect(RSpec).to receive(:deprecate) 80 | Syntax.enable_should(my_host) 81 | 82 | 3.should eq 3 83 | end 84 | end 85 | end 86 | end 87 | 88 | RSpec.describe Expectations do 89 | it "does not inadvertently define BasicObject on 1.8", :if => RUBY_VERSION.to_f < 1.9 do 90 | expect(defined?(::BasicObject)).to be nil 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /spec/rspec/expectations_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/support/spec/library_wide_checks' 2 | 3 | RSpec.describe "RSpec::Expectations" do 4 | allowed_loaded_features = [ 5 | /stringio/, # Used by `output` matcher. Can't be easily avoided. 6 | /rbconfig/ # required by rspec-support 7 | ] 8 | 9 | # Truffleruby cext files 10 | allowed_loaded_features << /\/truffle\/cext/ if RSpec::Support::Ruby.truffleruby? 11 | 12 | it_behaves_like "library wide checks", "rspec-expectations", 13 | :preamble_for_lib => [ 14 | # We define minitest constants because rspec/expectations/minitest_integration 15 | # expects these constants to already be defined. 16 | "module Minitest; class Assertion; end; module Test; end; end", 17 | 'require "rspec/expectations"' 18 | ], 19 | :allowed_loaded_feature_regexps => allowed_loaded_features 20 | 21 | it 'does not allow expectation failures to be caught by a bare rescue' do 22 | expect { 23 | expect(2).to eq(3) rescue nil 24 | }.to fail_including("expected: 3") 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/rspec/matchers/built_in/be_instance_of_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | [:be_an_instance_of, :be_instance_of].each do |method| 4 | RSpec.describe "expect(actual).to #{method}(expected)" do 5 | it_behaves_like "an RSpec value matcher", :valid_value => "a", :invalid_value => 5 do 6 | let(:matcher) { send(method, String) } 7 | end 8 | 9 | it "passes if actual is instance of expected class" do 10 | expect("a").to send(method, String) 11 | end 12 | 13 | it "fails if actual is instance of subclass of expected class" do 14 | expect { 15 | expect(5).to send(method, Numeric) 16 | }.to fail_with("expected 5 to be an instance of Numeric") 17 | end 18 | 19 | it "fails with failure message for should unless actual is instance of expected class" do 20 | expect { 21 | expect("foo").to send(method, Array) 22 | }.to fail_with('expected "foo" to be an instance of Array') 23 | end 24 | 25 | it "provides a description" do 26 | matcher = be_an_instance_of(Integer) 27 | matcher.matches?(Numeric) 28 | expect(matcher.description).to eq "be an instance of Integer" 29 | end 30 | 31 | context "when expected provides an expanded inspect, e.g. AR::Base" do 32 | let(:user_klass) do 33 | Class.new do 34 | def self.inspect 35 | "User(id: integer, name: string)" 36 | end 37 | end 38 | end 39 | 40 | before { stub_const("User", user_klass) } 41 | 42 | it "provides a description including only the class name" do 43 | matcher = be_an_instance_of(User) 44 | expect(matcher.description).to eq "be an instance of User" 45 | end 46 | end 47 | 48 | context "when the actual object does not respond to #instance_of? method" do 49 | let(:klass) { Class.new { undef_method :instance_of? } } 50 | 51 | let(:actual_object) { klass.new } 52 | 53 | it "raises ArgumentError" do 54 | message = "The be_an_instance_of matcher requires that "\ 55 | "the actual object responds to #instance_of? method " \ 56 | "but a `NoMethodError` was encountered instead." 57 | expect { 58 | expect(actual_object).to send(method, klass) 59 | }.to raise_error ::ArgumentError, message 60 | end 61 | end 62 | end 63 | 64 | RSpec.describe "expect(actual).not_to #{method}(expected)" do 65 | it "fails with failure message for should_not if actual is instance of expected class" do 66 | expect { 67 | expect("foo").not_to send(method, String) 68 | }.to fail_with('expected "foo" not to be an instance of String') 69 | end 70 | 71 | context "when the actual object does not respond to #instance_of? method" do 72 | let(:klass) { Class.new { undef_method :instance_of? } } 73 | 74 | let(:actual_object) { klass.new } 75 | 76 | it "raises ArgumentError" do 77 | message = "The be_an_instance_of matcher requires that "\ 78 | "the actual object responds to #instance_of? method " \ 79 | "but a `NoMethodError` was encountered instead." 80 | expect { 81 | expect(actual_object).not_to send(method, klass) 82 | }.to raise_error ::ArgumentError, message 83 | end 84 | end 85 | end 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /spec/rspec/matchers/built_in/be_kind_of_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | [:be_a_kind_of, :be_kind_of].each do |method| 4 | RSpec.describe "expect(actual).to #{method}(expected)" do 5 | it_behaves_like "an RSpec value matcher", :valid_value => 5, :invalid_value => "a" do 6 | let(:matcher) { send(method, Integer) } 7 | end 8 | 9 | it "passes if actual is instance of expected class" do 10 | expect("string").to send(method, String) 11 | end 12 | 13 | it "passes if actual is instance of subclass of expected class" do 14 | expect(5).to send(method, Numeric) 15 | end 16 | 17 | it "fails with failure message for should unless actual is kind of expected class" do 18 | expect { 19 | expect("foo").to send(method, Array) 20 | }.to fail_with('expected "foo" to be a kind of Array') 21 | end 22 | 23 | it "provides a description" do 24 | matcher = be_a_kind_of(String) 25 | matcher.matches?("this") 26 | expect(matcher.description).to eq "be a kind of String" 27 | end 28 | 29 | context "when the actual object does not respond to #kind_of? method" do 30 | let(:actual_object) do 31 | Class.new { undef_method :kind_of? }.new 32 | end 33 | 34 | it "raises ArgumentError" do 35 | message = "The be_a_kind_of matcher requires that " \ 36 | "the actual object responds to #kind_of? method " \ 37 | "but a `NoMethodError` was encountered instead." 38 | 39 | expect { 40 | expect(actual_object).to send(method, actual_object.class) 41 | }.to raise_error ::ArgumentError, message 42 | 43 | expect { 44 | expect(actual_object).to send(method, Object) 45 | }.to raise_error ::ArgumentError, message 46 | end 47 | end 48 | 49 | context "when the actual object does not respond to #is_a? method" do 50 | let(:actual_object) do 51 | Class.new { undef_method :is_a? }.new 52 | end 53 | 54 | it "provides correct result" do 55 | expect(actual_object).to send(method, actual_object.class) 56 | expect(actual_object).to send(method, Object) 57 | end 58 | end 59 | end 60 | 61 | RSpec.describe "expect(actual).not_to #{method}(expected)" do 62 | it "fails with failure message for should_not if actual is kind of expected class" do 63 | expect { 64 | expect("foo").not_to send(method, String) 65 | }.to fail_with('expected "foo" not to be a kind of String') 66 | end 67 | 68 | context "when the actual object does not respond to #kind_of? method" do 69 | let(:actual_object) do 70 | Class.new { undef_method :kind_of? }.new 71 | end 72 | 73 | it "raises ArgumentError" do 74 | message = "The be_a_kind_of matcher requires that " \ 75 | "the actual object responds to #kind_of? method " \ 76 | "but a `NoMethodError` was encountered instead." 77 | 78 | expect { 79 | expect(actual_object).not_to send(method, String) 80 | }.to raise_error ArgumentError, message 81 | end 82 | end 83 | 84 | context "when the actual object does not respond to #is_a? method" do 85 | let(:actual_object) do 86 | Class.new { undef_method :is_a? }.new 87 | end 88 | 89 | it "provides correct result" do 90 | expect(actual_object).not_to send(method, String) 91 | end 92 | end 93 | end 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /spec/rspec/matchers/built_in/captures_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "expect(regex).to match(string).with_captures" do 2 | context "with a string target" do 3 | it "does match a regex with a missing capture" do 4 | expect("a123a").to match(/(a)(b)?/).with_captures("a", nil) 5 | end 6 | 7 | it "does not match a regex with an incorrect match" do 8 | expect("a123a").not_to match(/(a)/).with_captures("b") 9 | end 10 | 11 | it "matches a regex without named captures" do 12 | expect("a123a").to match(/(a)/).with_captures("a") 13 | end 14 | 15 | it "uses the match description if the regex doesn't match" do 16 | expect { 17 | expect(/(a)/).to match("123").with_captures 18 | }.to fail_with(/expected \/\(a\)\/ to match "123"/) 19 | end 20 | 21 | if RUBY_VERSION != "1.8.7" 22 | it "matches a regex with named captures" do 23 | expect("a123a").to match(Regexp.new("(?123)")).with_captures(:num => "123") 24 | end 25 | 26 | it "matches a regex with a nested matcher" do 27 | expect("a123a").to match(Regexp.new("(?123)(asdf)?")).with_captures(a_hash_including(:num => "123")) 28 | end 29 | 30 | it "does not match a regex with an incorrect named group match" do 31 | expect("a123a").not_to match(Regexp.new("(?a)")).with_captures(:name => "b") 32 | end 33 | 34 | it "has a sensible failure description with a hash including matcher" do 35 | expect { 36 | expect("a123a").not_to match(Regexp.new("(?123)(asdf)?")).with_captures(a_hash_including(:num => "123")) 37 | }.to fail_with(/num => "123"/) 38 | end 39 | 40 | it "matches named captures when not passing a hash" do 41 | expect("a123a").to match(Regexp.new("(?123)")).with_captures("123") 42 | end 43 | end 44 | end 45 | 46 | context "with a regex target" do 47 | it "does match a regex with a missing capture" do 48 | expect(/(a)(b)?/).to match("a123a").with_captures("a", nil) 49 | end 50 | 51 | it "does not match a regex with an incorrect match" do 52 | expect(/(a)/).not_to match("a123a").with_captures("b") 53 | end 54 | 55 | it "matches a regex without named captures" do 56 | expect(/(a)/).to match("a123a").with_captures("a") 57 | end 58 | 59 | it "uses the match description if the regex doesn't match" do 60 | expect { 61 | expect(/(a)/).to match("123").with_captures 62 | }.to fail_with(/expected \/\(a\)\/ to match "123"/) 63 | end 64 | 65 | if RUBY_VERSION != "1.8.7" 66 | it "matches a regex with named captures" do 67 | expect(Regexp.new("(?123)")).to match("a123a").with_captures(:num => "123") 68 | end 69 | 70 | it "matches a regex with a nested matcher" do 71 | expect(Regexp.new("(?123)(asdf)?")).to match("a123a").with_captures(a_hash_including(:num => "123")) 72 | end 73 | 74 | it "does not match a regex with an incorrect named group match" do 75 | expect(Regexp.new("(?a)")).not_to match("a123a").with_captures(:name => "b") 76 | end 77 | 78 | it "has a sensible failure description with a hash including matcher" do 79 | expect { 80 | expect(Regexp.new("(?123)(asdf)?")).not_to match("a123a").with_captures(a_hash_including(:num => "123")) 81 | }.to fail_with(/num => "123"/) 82 | end 83 | 84 | it "matches named captures when not passing a hash" do 85 | expect(Regexp.new("(?123)")).to match("a123a").with_captures("123") 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/rspec/matchers/built_in/cover_spec.rb: -------------------------------------------------------------------------------- 1 | if (1..2).respond_to?(:cover?) 2 | RSpec.describe "expect(...).to cover(expected)" do 3 | it_behaves_like "an RSpec value matcher", :valid_value => (1..10), :invalid_value => (20..30) do 4 | let(:matcher) { cover(5) } 5 | end 6 | 7 | context "for a range target" do 8 | it "passes if target covers expected" do 9 | expect((1..10)).to cover(5) 10 | end 11 | 12 | it "fails if target does not cover expected" do 13 | expect { 14 | expect((1..10)).to cover(11) 15 | }.to fail_with("expected 1..10 to cover 11") 16 | end 17 | end 18 | end 19 | 20 | RSpec.describe "expect(...).to cover(with, multiple, args)" do 21 | context "for a range target" do 22 | it "passes if target covers all items" do 23 | expect((1..10)).to cover(4, 6) 24 | end 25 | 26 | it "fails if target does not cover any one of the items" do 27 | expect { 28 | expect((1..10)).to cover(4, 6, 11) 29 | }.to fail_with("expected 1..10 to cover 4, 6, and 11") 30 | end 31 | end 32 | end 33 | 34 | RSpec.describe "expect(...).not_to cover(expected)" do 35 | context "for a range target" do 36 | it "passes if target does not cover expected" do 37 | expect((1..10)).not_to cover(11) 38 | end 39 | 40 | it "fails if target covers expected" do 41 | expect { 42 | expect((1..10)).not_to cover(5) 43 | }.to fail_with("expected 1..10 not to cover 5") 44 | end 45 | end 46 | end 47 | 48 | RSpec.describe "expect(...).not_to cover(with, multiple, args)" do 49 | context "for a range target" do 50 | it "passes if the target does not cover any of the expected" do 51 | expect((1..10)).not_to cover(11, 12, 13) 52 | end 53 | 54 | it "fails if the target covers all of the expected" do 55 | expect { 56 | expect((1..10)).not_to cover(4, 6) 57 | }.to fail_with("expected 1..10 not to cover 4 and 6") 58 | end 59 | 60 | it "fails if the target covers some (but not all) of the expected" do 61 | expect { 62 | expect((1..10)).not_to cover(5, 11) 63 | }.to fail_with("expected 1..10 not to cover 5 and 11") 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/rspec/matchers/built_in/eql_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | RSpec.describe "eql" do 4 | it_behaves_like "an RSpec value matcher", :valid_value => 1, :invalid_value => 2 do 5 | let(:matcher) { eql(1) } 6 | end 7 | 8 | it "is diffable" do 9 | expect(eql(1)).to be_diffable 10 | end 11 | 12 | it "matches when actual.eql?(expected)" do 13 | expect(1).to eql(1) 14 | end 15 | 16 | it "does not match when !actual.eql?(expected)" do 17 | expect(1).not_to eql(2) 18 | end 19 | 20 | it "describes itself" do 21 | matcher = eql(1) 22 | matcher.matches?(1) 23 | expect(matcher.description).to eq "eql 1" 24 | end 25 | 26 | it "provides message, expected and actual on #failure_message" do 27 | matcher = eql("1") 28 | matcher.matches?(1) 29 | expect(matcher.failure_message).to eq "\nexpected: \"1\"\n got: 1\n\n(compared using eql?)\n" 30 | end 31 | 32 | it "provides message, expected and actual on #negative_failure_message" do 33 | matcher = eql(1) 34 | matcher.matches?(1) 35 | expect(matcher.failure_message_when_negated).to eq "\nexpected: value != 1\n got: 1\n\n(compared using eql?)\n" 36 | end 37 | 38 | # Older versions of Ruby such as less than 1.9 do not have String#encoding available, they are an array of bytes 39 | if String.method_defined?(:encoding) 40 | context "with String encoding as UTF-16LE" do 41 | it "provides message, expected and actual on #failure_message when string encoding is the same" do 42 | matcher = eql('abc'.encode('UTF-16LE')) 43 | matcher.matches?('def'.encode('UTF-16LE')) 44 | expect(matcher.failure_message).to eq "\nexpected: \"abc\"\n got: \"def\"\n\n(compared using eql?)\n" 45 | end 46 | 47 | it "matches when actual is BINARY encoding and expected is UTF-8 encoding with the same chars" do 48 | expect('abc'.encode('BINARY')).to eq 'abc'.encode('UTF-8') 49 | end 50 | 51 | it "provides message, expected and actual with encoding details on #failure_message when string encoding is different" do 52 | matcher = eql('abc'.encode('UTF-16LE')) 53 | matcher.matches?('abc'.dup.force_encoding('ASCII-8BIT')) 54 | expect(matcher.failure_message).to eq "\nexpected: # \"abc\"\n got: # \"abc\"\n\n(compared using eql?)\n" 55 | end 56 | 57 | it "provides message, expected and actual on #negative_failure_message" do 58 | matcher = eql('abc'.encode('UTF-16LE')) 59 | matcher.matches?('abc'.encode('UTF-16LE')) 60 | expect(matcher.failure_message_when_negated).to eq "\nexpected: value != \"abc\"\n got: \"abc\"\n\n(compared using eql?)\n" 61 | end 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /spec/rspec/matchers/built_in/satisfy_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "expect(...).to satisfy { block }" do 2 | it_behaves_like "an RSpec value matcher", :valid_value => true, :invalid_value => false do 3 | let(:matcher) { satisfy { |v| v } } 4 | end 5 | 6 | it "describes itself" do 7 | expect(satisfy.description).to eq("satisfy block") 8 | end 9 | 10 | it "passes if block returns true" do 11 | expect(true).to satisfy { |val| val } 12 | expect(true).to satisfy do |val| 13 | val 14 | end 15 | end 16 | 17 | context "when no custom description is provided" do 18 | context 'in Ripper supported environment', :if => RSpec::Support::RubyFeatures.ripper_supported? do 19 | it "fails with block snippet if block returns false" do 20 | expect { 21 | expect(false).to satisfy { |val| val } 22 | }.to fail_with("expected false to satisfy expression `val`") 23 | 24 | expect do 25 | expect(false).to satisfy do |val| 26 | val 27 | end 28 | end.to fail_with("expected false to satisfy expression `val`") 29 | end 30 | 31 | context 'when used with an alias name' do 32 | alias_matcher :fulfill, :satisfy 33 | 34 | it 'can extract the block snippet' do 35 | expect { 36 | expect(false).to fulfill { |val| val } 37 | }.to fail_with("expected false to fulfill expression `val`") 38 | end 39 | end 40 | end 41 | 42 | context 'in Ripper unsupported environment', :unless => RSpec::Support::RubyFeatures.ripper_supported? do 43 | it "fails without block snippet if block returns false" do 44 | expect { 45 | expect(false).to satisfy { |val| val } 46 | }.to fail_with("expected false to satisfy block") 47 | 48 | expect do 49 | expect(false).to satisfy do |val| 50 | val 51 | end 52 | end.to fail_with("expected false to satisfy block") 53 | end 54 | end 55 | end 56 | 57 | context "when a custom description is provided" do 58 | it "describes itself" do 59 | expect(satisfy("be awesome").description).to eq("be awesome") 60 | end 61 | 62 | it "passes if block returns true" do 63 | expect(true).to satisfy("be true") { |val| val } 64 | expect(true).to satisfy("be true") do |val| 65 | val 66 | end 67 | end 68 | 69 | it "fails with the custom description if block returns false" do 70 | expect { 71 | expect(false).to satisfy("be true") { |val| val } 72 | }.to fail_with("expected false to be true") 73 | expect do 74 | expect(false).to satisfy("be true") do |val| 75 | val 76 | end 77 | end.to fail_with("expected false to be true") 78 | end 79 | end 80 | end 81 | 82 | RSpec.describe "expect(...).not_to satisfy { block }" do 83 | it "passes if block returns false" do 84 | expect(false).not_to satisfy { |val| val } 85 | expect(false).not_to satisfy do |val| 86 | val 87 | end 88 | end 89 | 90 | context "when no custom description is provided" do 91 | context 'in Ripper supported environment', :if => RSpec::Support::RubyFeatures.ripper_supported? do 92 | it "fails with block snippet if block returns true" do 93 | expect { 94 | expect(true).not_to satisfy { |val| val } 95 | }.to fail_with("expected true not to satisfy expression `val`") 96 | end 97 | end 98 | 99 | context 'in Ripper unsupported environment', :unless => RSpec::Support::RubyFeatures.ripper_supported? do 100 | it "fails without block snippet if block returns true" do 101 | expect { 102 | expect(true).not_to satisfy { |val| val } 103 | }.to fail_with("expected true not to satisfy block") 104 | end 105 | end 106 | end 107 | 108 | context "when a custom description is provided" do 109 | it "passes if block returns false" do 110 | expect(false).not_to satisfy("be true") { |val| val } 111 | expect(false).not_to satisfy("be true") do |val| 112 | val 113 | end 114 | end 115 | 116 | it "fails with the custom description if block returns true" do 117 | expect { 118 | expect(true).not_to satisfy("be true") { |val| val } 119 | }.to fail_with("expected true not to be true") 120 | end 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /spec/rspec/matchers/english_phrasing_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | RSpec.describe EnglishPhrasing do 4 | describe ".split_words" do 5 | it "replaces underscores with spaces" do 6 | expect( 7 | described_class.split_words(:banana_creme_pie) 8 | ).to eq("banana creme pie") 9 | end 10 | 11 | it "first casts its argument to string" do 12 | arg = double(:to_s => "banana") 13 | expect(described_class.split_words(arg)).to eq("banana") 14 | end 15 | end 16 | 17 | describe ".list" do 18 | context "given nil" do 19 | it "returns value from inspect, and a leading space" do 20 | expect(described_class.list(nil)).to eq(" nil") 21 | end 22 | end 23 | 24 | context "given a Struct" do 25 | it "returns value from inspect, and a leading space" do 26 | banana = Struct.new("Banana", :flavor).new 27 | expect( 28 | described_class.list(banana) 29 | ).to eq(" #{banana.inspect}") 30 | end 31 | end 32 | 33 | context "given a Hash" do 34 | it "returns value from inspect, and a leading space" do 35 | banana = { :flavor => 'Banana' } 36 | expect( 37 | described_class.list(banana) 38 | ).to eq(" #{banana.inspect}") 39 | end 40 | end 41 | 42 | context "given an Enumerable other than a Hash" do 43 | before do 44 | allow(RSpec::Support::ObjectFormatter).to( 45 | receive(:format).and_return("Banana") 46 | ) 47 | end 48 | 49 | context "with zero items" do 50 | it "returns the empty string" do 51 | expect(described_class.list([])).to eq("") 52 | end 53 | end 54 | 55 | context "with one item" do 56 | let(:list) { [double] } 57 | it "returns description, and a leading space" do 58 | expect(described_class.list(list)).to eq(" Banana") 59 | expect(RSpec::Support::ObjectFormatter).to( 60 | have_received(:format).once 61 | ) 62 | end 63 | end 64 | 65 | context "with two items" do 66 | let(:list) { [double, double] } 67 | it "returns descriptions, and a leading space" do 68 | expect(described_class.list(list)).to eq(" Banana and Banana") 69 | expect(RSpec::Support::ObjectFormatter).to( 70 | have_received(:format).twice 71 | ) 72 | end 73 | end 74 | 75 | context "with three items" do 76 | let(:list) { [double, double, double] } 77 | it "returns descriptions, and a leading space" do 78 | expect( 79 | described_class.list(list) 80 | ).to eq(" Banana, Banana, and Banana") 81 | expect(RSpec::Support::ObjectFormatter).to( 82 | have_received(:format).exactly(3).times 83 | ) 84 | end 85 | end 86 | end 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/rspec/matchers/legacy_spec.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Matchers 3 | RSpec.describe "Legacy matchers" do 4 | it 'still provides a `LegacyMacherAdapter` constant because 3.0 was released with ' \ 5 | 'it and it would be a SemVer violation to remove it before 4.0' do 6 | expect(Expectations::LegacyMacherAdapter).to be(Expectations::LegacyMatcherAdapter) 7 | end 8 | 9 | shared_examples "a matcher written against a legacy protocol" do |matcher_class| 10 | matcher = matcher_class.new 11 | before { allow_deprecation } 12 | 13 | backwards_compat_matcher = Class.new(matcher_class) do 14 | def failure_message; "failure when positive"; end 15 | def failure_message_when_negated; "failure when negative"; end 16 | end.new 17 | 18 | it 'is still considered to be a matcher' do 19 | expect(Matchers.is_a_matcher?(matcher)).to be true 20 | end 21 | 22 | context 'when matched positively' do 23 | it 'returns the positive expectation failure message' do 24 | expect { 25 | expect(false).to matcher 26 | }.to fail_with("failure when positive") 27 | end 28 | 29 | it 'warns about the deprecated protocol' do 30 | expect_warn_deprecation(/legacy\s+RSpec\s+matcher.+#{__FILE__}:#{__LINE__ + 1}/m) 31 | expect(true).to matcher 32 | end 33 | 34 | it 'does not warn when it also defines the current methods (i.e. to be compatible on multiple RSpec versions)' do 35 | expect_no_deprecations 36 | 37 | expect { 38 | expect(false).to backwards_compat_matcher 39 | }.to fail_with("failure when positive") 40 | end 41 | end 42 | 43 | context 'when matched negatively' do 44 | it 'returns the negative expectation failure message' do 45 | expect { 46 | expect(true).not_to matcher 47 | }.to fail_with("failure when negative") 48 | end 49 | 50 | it 'warns about the deprecated protocol' do 51 | expect_warn_deprecation(/legacy\s+RSpec\s+matcher.+#{__FILE__}:#{__LINE__ + 1}/m) 52 | expect(false).not_to matcher 53 | end 54 | 55 | it 'does not warn when it also defines the current methods (i.e. to be compatible on multiple RSpec versions)' do 56 | expect_no_deprecations 57 | 58 | expect { 59 | expect(true).not_to backwards_compat_matcher 60 | }.to fail_with("failure when negative") 61 | end 62 | 63 | def pending_on_rbx 64 | return unless defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx' 65 | pending "intermittently fails on RBX due to https://github.com/rubinius/rubinius/issues/2845" 66 | end 67 | 68 | it 'calls `does_not_match?` if it is defined on the matcher' do 69 | pending_on_rbx 70 | 71 | called = false 72 | with_does_not_match = Class.new(matcher_class) do 73 | define_method(:does_not_match?) { |actual| called = true; !actual } 74 | end.new 75 | 76 | expect(false).not_to with_does_not_match 77 | expect(called).to be true 78 | end 79 | end 80 | end 81 | 82 | context "written using the RSpec 2.x `failure_message_for_should` and `failure_message_for_should_not` protocol" do 83 | matcher_class = Class.new do 84 | def matches?(actual); actual; end 85 | def failure_message_for_should; "failure when positive"; end 86 | def failure_message_for_should_not; "failure when negative"; end 87 | end 88 | 89 | it_behaves_like "a matcher written against a legacy protocol", matcher_class 90 | end 91 | 92 | context "written using the older `failure_message` and `negative_failure_message` protocol" do 93 | matcher_class = Class.new do 94 | def matches?(actual); actual; end 95 | def failure_message; "failure when positive"; end 96 | def negative_failure_message; "failure when negative"; end 97 | end 98 | 99 | it_behaves_like "a matcher written against a legacy protocol", matcher_class 100 | end 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /spec/support/matchers.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/matchers/fail_matchers' 2 | 3 | RSpec::Matchers.define :include_method do |expected| 4 | match do |actual| 5 | actual.map { |m| m.to_s }.include?(expected.to_s) 6 | end 7 | end 8 | 9 | RSpec::Matchers.define :custom_include do |*args| 10 | match { |actual| expect(actual).to include(*args) } 11 | end 12 | 13 | RSpec::Matchers.define :be_a_clone_of do |expected| 14 | match do |actual| 15 | !actual.equal?(expected) && 16 | actual.class.equal?(expected.class) && 17 | state_of(actual) == state_of(expected) 18 | end 19 | 20 | def state_of(object) 21 | ivar_names = object.instance_variables 22 | Hash[ivar_names.map { |n| [n, object.instance_variable_get(n)] }] 23 | end 24 | end 25 | 26 | RSpec::Matchers.define :have_string_length do |expected| 27 | match do |actual| 28 | @actual = actual 29 | string_length == expected 30 | end 31 | 32 | def string_length 33 | @string_length ||= @actual.length 34 | end 35 | end 36 | 37 | RSpec.configure do |config| 38 | config.include RSpec::Matchers::FailMatchers 39 | end 40 | 41 | RSpec::Matchers.define_negated_matcher :a_string_excluding, :a_string_including 42 | RSpec::Matchers.define_negated_matcher :a_string_not_matching, :match 43 | -------------------------------------------------------------------------------- /spec/support/shared_examples/block_matcher.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples "an RSpec block-only matcher" do |*options| 2 | # Note: Ruby 1.8 expects you to call a block with arguments if it is 3 | # declared that accept arguments. In this case, some of the specs 4 | # that include examples from this shared example group do not pass 5 | # arguments. A workaround is to use splat and pick the first argument 6 | # if it was passed. 7 | options = options.first || {} 8 | 9 | # Note: do not use `matcher` in 2 expectation expressions in a single 10 | # example here. In some cases (such as `change { x += 2 }.to(2)`), it 11 | # will fail because using it a second time will apply `x += 2` twice, 12 | # changing the value to 4. 13 | 14 | matcher :always_passes do 15 | supports_block_expectations 16 | match do |actual| 17 | actual.call 18 | true 19 | end 20 | end 21 | 22 | matcher :always_fails do 23 | supports_block_expectations 24 | match do |actual| 25 | actual.call 26 | false 27 | end 28 | end 29 | 30 | let(:valid_expectation) { expect { valid_block } } 31 | let(:invalid_expectation) { expect { invalid_block } } 32 | 33 | let(:valid_block_lambda) { lambda { valid_block } } 34 | let(:invalid_block_lambda) { lambda { invalid_block } } 35 | 36 | include_examples "an RSpec matcher", options 37 | 38 | it 'preserves the symmetric property of `==`' do 39 | expect(matcher).to eq(matcher) 40 | expect(matcher).not_to eq(valid_block_lambda) 41 | expect(valid_block_lambda).not_to eq(matcher) 42 | end 43 | 44 | it 'matches a valid block when using #=== so it can be composed' do 45 | expect(matcher).to be === valid_block_lambda 46 | end 47 | 48 | it 'does not match an invalid block when using #=== so it can be composed' do 49 | expect(matcher).not_to be === invalid_block_lambda 50 | end 51 | 52 | it 'matches a valid block when using #=== so it can be composed' do 53 | expect(matcher).to be === valid_block_lambda 54 | end 55 | 56 | it 'does not match an invalid block when using #=== so it can be composed' do 57 | expect(matcher).not_to be === invalid_block_lambda 58 | end 59 | 60 | it 'uses the `ObjectFormatter` for `failure_message`' do 61 | allow(RSpec::Support::ObjectFormatter).to receive(:format).and_return("detailed inspect") 62 | expect { invalid_expectation.to matcher }.to raise_error do |error| 63 | # Undo our stub so it doesn't affect the `include` matcher below. 64 | allow(RSpec::Support::ObjectFormatter).to receive(:format).and_call_original 65 | expect(error.message).to include("detailed inspect") 66 | end 67 | end unless options[:failure_message_uses_no_inspect] 68 | 69 | it 'fails gracefully when given a value' do 70 | expect { 71 | expect(3).to matcher 72 | }.to fail_with(/was not( given)? a block/) 73 | 74 | unless options[:disallows_negation] 75 | expect { 76 | expect(3).not_to matcher 77 | }.to fail_with(/was not( given)? a block/) 78 | end 79 | end 80 | 81 | it 'prints a deprecation warning when given a value' do 82 | expect_warn_deprecation(/The implicit block expectation syntax is deprecated, you should pass/) 83 | expect { expect(3).to matcher }.to fail 84 | end unless options[:skip_deprecation_check] || options[:expects_lambda] 85 | 86 | it 'prints a deprecation warning when given a value and negated' do 87 | expect_warn_deprecation(/The implicit block expectation syntax is deprecated, you should pass/) 88 | expect { expect(3).not_to matcher }.to fail 89 | end unless options[:disallows_negation] || options[:expects_lambda] 90 | 91 | it 'allows lambda expectation target' do 92 | allow_deprecation 93 | expect(valid_block_lambda).to matcher 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /spec/support/shared_examples/matcher.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples "an RSpec matcher" do |options| 2 | # Note: do not use `matcher` in 2 expectation expressions in a single 3 | # example here. In some cases (such as `change { }.to(2)`), it will fail 4 | # because using it a second time will apply `x += 2` twice, changing 5 | # the value to 4. 6 | 7 | it 'allows additional matchers to be chained off it using `and`' do 8 | valid_expectation.to matcher.and always_passes 9 | end 10 | 11 | it 'can be chained off of an existing matcher using `and`' do 12 | valid_expectation.to always_passes.and matcher 13 | end 14 | 15 | it 'allows additional matchers to be chained off it using `or`' do 16 | valid_expectation.to matcher.or always_fails 17 | end 18 | 19 | it 'can be chained off of an existing matcher using `or`' do 20 | valid_expectation.to always_fails.or matcher 21 | end 22 | 23 | it 'implements the full matcher protocol' do 24 | expect(matcher).to respond_to( 25 | :matches?, 26 | :failure_message, 27 | :description, 28 | :supports_block_expectations?, 29 | :supports_value_expectations?, 30 | :expects_call_stack_jump? 31 | ) 32 | 33 | # We do not require failure_message_when_negated and does_not_match? 34 | # Because some matchers purposefully do not support negation. 35 | end 36 | 37 | it 'can match negatively properly' do 38 | invalid_expectation.not_to matcher 39 | 40 | expect { 41 | valid_expectation.not_to matcher 42 | }.to fail 43 | end unless options[:disallows_negation] 44 | end 45 | -------------------------------------------------------------------------------- /spec/support/shared_examples/value_matcher.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples "an RSpec value matcher" do |options| 2 | let(:valid_value) { options.fetch(:valid_value) } 3 | let(:invalid_value) { options.fetch(:invalid_value) } 4 | 5 | matcher :always_passes do 6 | match { |_actual| true } 7 | end 8 | 9 | matcher :always_fails do 10 | match { |_actual| false } 11 | end 12 | 13 | def valid_expectation 14 | expect(valid_value) 15 | end 16 | 17 | def invalid_expectation 18 | expect(invalid_value) 19 | end 20 | 21 | include_examples "an RSpec matcher", options 22 | 23 | it 'preserves the symmetric property of `==`' do 24 | expect(matcher).to eq(matcher) 25 | expect(matcher).not_to eq(valid_value) 26 | expect(valid_value).not_to eq(matcher) 27 | end 28 | 29 | it 'matches a valid value when using #=== so it can be composed' do 30 | expect(matcher).to be === valid_value 31 | end 32 | 33 | it 'does not match an invalid value when using #=== so it can be composed' do 34 | expect(matcher).not_to be === invalid_value 35 | end 36 | 37 | it 'can be used in a composed matcher expression' do 38 | expect([valid_value, invalid_value]).to include(matcher) 39 | 40 | expect { 41 | expect([invalid_value]).to include(matcher) 42 | }.to fail_including("include (#{matcher.description})") 43 | end 44 | 45 | it 'uses the `ObjectFormatter` for `failure_message`' do 46 | allow(RSpec::Support::ObjectFormatter).to receive(:format).and_return("detailed inspect") 47 | matcher.matches?(invalid_value) 48 | message = matcher.failure_message 49 | 50 | # Undo our stub so it doesn't affect the `include` matcher below. 51 | allow(RSpec::Support::ObjectFormatter).to receive(:format).and_call_original 52 | expect(message).to include("detailed inspect") 53 | end 54 | 55 | it 'fails when given a block' do 56 | expect { 57 | expect { 2 + 2 }.to matcher 58 | }.to fail_with(/must pass an argument rather than a block/) 59 | 60 | unless options[:disallows_negation] 61 | expect { 62 | expect { 2 + 2 }.not_to matcher 63 | }.to fail_with(/must pass an argument rather than a block/) 64 | end 65 | end 66 | end 67 | --------------------------------------------------------------------------------