mocktail 1.2.3 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +6 -5
- data/.gitignore +3 -0
- data/.standard.yml +8 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile +8 -2
- data/Gemfile.lock +105 -38
- data/README.md +20 -922
- data/Rakefile +0 -1
- data/bin/console +1 -2
- data/bin/tapioca +29 -0
- data/docs/code_of_conduct.md +8 -0
- data/docs/configuring_minitest.md +20 -0
- data/docs/configuring_rspec.md +19 -0
- data/docs/example_test.md +594 -0
- data/docs/faq/existing_tests.md +47 -0
- data/docs/faq/mocking_http.md +151 -0
- data/docs/faq/mocking_the_subject.md +161 -0
- data/docs/faq/mocking_time.md +34 -0
- data/docs/faq/partial_mocks.md +34 -0
- data/docs/faq/verifying_real_interactions.md +52 -0
- data/docs/img/delegator_tree.png +0 -0
- data/docs/img/example_test.png +0 -0
- data/docs/img/extract_transform_load.png +0 -0
- data/docs/img/mocktail_sorbet.jpg +0 -0
- data/docs/img/mocktail_untyped.jpg +0 -0
- data/docs/img/spacer.png +0 -0
- data/docs/installation_sorbet.md +99 -0
- data/docs/installation_untyped.md +29 -0
- data/docs/other_uses.md +32 -0
- data/docs/stubbing_and_verifying.md +515 -0
- data/docs/support/api.md +819 -0
- data/docs/support/example_test.rb +94 -0
- data/docs/support/glossary.md +426 -0
- data/docs/tdd/class_methods.md +44 -0
- data/docs/tdd/poro/dependency_inception.md +136 -0
- data/docs/tdd/poro/dependency_injection.md +82 -0
- data/docs/tdd/poro.md +15 -0
- data/docs/tdd/third_party.md +220 -0
- data/docs/tdd.md +15 -0
- data/lib/mocktail/collects_calls.rb +2 -0
- data/lib/mocktail/debug.rb +16 -12
- data/lib/mocktail/dsl.rb +2 -0
- data/lib/mocktail/errors.rb +2 -0
- data/lib/mocktail/explains_nils.rb +2 -0
- data/lib/mocktail/explains_thing.rb +7 -4
- data/lib/mocktail/grabs_original_method_parameters.rb +30 -0
- data/lib/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +3 -1
- data/lib/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +5 -1
- data/lib/mocktail/handles_dry_call/fulfills_stubbing.rb +2 -0
- data/lib/mocktail/handles_dry_call/logs_call.rb +2 -0
- data/lib/mocktail/handles_dry_call/validates_arguments.rb +6 -4
- data/lib/mocktail/handles_dry_call.rb +2 -0
- data/lib/mocktail/handles_dry_new_call.rb +2 -0
- data/lib/mocktail/imitates_type/ensures_imitation_support.rb +2 -0
- data/lib/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +4 -1
- data/lib/mocktail/imitates_type/makes_double/declares_dry_class.rb +32 -20
- data/lib/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +2 -0
- data/lib/mocktail/imitates_type/makes_double.rb +3 -0
- data/lib/mocktail/imitates_type.rb +3 -1
- data/lib/mocktail/initialize_based_on_type_system_mode_switching.rb +9 -0
- data/lib/mocktail/initializes_mocktail.rb +5 -0
- data/lib/mocktail/matcher_presentation.rb +4 -2
- data/lib/mocktail/matchers/any.rb +4 -3
- data/lib/mocktail/matchers/base.rb +10 -2
- data/lib/mocktail/matchers/captor.rb +9 -0
- data/lib/mocktail/matchers/includes.rb +2 -0
- data/lib/mocktail/matchers/includes_hash.rb +9 -0
- data/lib/mocktail/matchers/includes_key.rb +9 -0
- data/lib/mocktail/matchers/includes_string.rb +9 -0
- data/lib/mocktail/matchers/is_a.rb +2 -0
- data/lib/mocktail/matchers/matches.rb +2 -0
- data/lib/mocktail/matchers/not.rb +2 -0
- data/lib/mocktail/matchers/numeric.rb +5 -4
- data/lib/mocktail/matchers/that.rb +2 -0
- data/lib/mocktail/matchers.rb +3 -0
- data/lib/mocktail/raises_neato_no_method_error.rb +2 -0
- data/lib/mocktail/records_demonstration.rb +2 -0
- data/lib/mocktail/registers_matcher.rb +8 -3
- data/lib/mocktail/registers_stubbing.rb +2 -0
- data/lib/mocktail/replaces_next.rb +7 -1
- data/lib/mocktail/replaces_type/redefines_new.rb +3 -1
- data/lib/mocktail/replaces_type/redefines_singleton_methods.rb +14 -2
- data/lib/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +37 -0
- data/lib/mocktail/replaces_type.rb +6 -0
- data/lib/mocktail/resets_state.rb +2 -0
- data/lib/mocktail/share/bind.rb +7 -5
- data/lib/mocktail/share/cleans_backtrace.rb +3 -5
- data/lib/mocktail/share/creates_identifier.rb +16 -9
- data/lib/mocktail/share/determines_matching_calls.rb +4 -2
- data/lib/mocktail/share/stringifies_call.rb +6 -3
- data/lib/mocktail/share/stringifies_method_name.rb +3 -1
- data/lib/mocktail/simulates_argument_error/reconciles_args_with_params.rb +2 -0
- data/lib/mocktail/simulates_argument_error/recreates_message.rb +2 -0
- data/lib/mocktail/simulates_argument_error/transforms_params.rb +16 -9
- data/lib/mocktail/simulates_argument_error.rb +2 -0
- data/lib/mocktail/sorbet/mocktail/collects_calls.rb +18 -0
- data/lib/mocktail/sorbet/mocktail/debug.rb +55 -0
- data/lib/mocktail/sorbet/mocktail/dsl.rb +46 -0
- data/lib/mocktail/sorbet/mocktail/errors.rb +19 -0
- data/lib/mocktail/sorbet/mocktail/explains_nils.rb +41 -0
- data/lib/mocktail/sorbet/mocktail/explains_thing.rb +137 -0
- data/lib/mocktail/sorbet/mocktail/grabs_original_method_parameters.rb +33 -0
- data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +27 -0
- data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +24 -0
- data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing.rb +45 -0
- data/lib/mocktail/sorbet/mocktail/handles_dry_call/logs_call.rb +12 -0
- data/lib/mocktail/sorbet/mocktail/handles_dry_call/validates_arguments.rb +45 -0
- data/lib/mocktail/sorbet/mocktail/handles_dry_call.rb +25 -0
- data/lib/mocktail/sorbet/mocktail/handles_dry_new_call.rb +42 -0
- data/lib/mocktail/sorbet/mocktail/imitates_type/ensures_imitation_support.rb +16 -0
- data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +73 -0
- data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/declares_dry_class.rb +136 -0
- data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +28 -0
- data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double.rb +29 -0
- data/lib/mocktail/sorbet/mocktail/imitates_type.rb +29 -0
- data/lib/mocktail/sorbet/mocktail/initialize_based_on_type_system_mode_switching.rb +11 -0
- data/lib/mocktail/sorbet/mocktail/initializes_mocktail.rb +25 -0
- data/lib/mocktail/sorbet/mocktail/matcher_presentation.rb +21 -0
- data/lib/mocktail/sorbet/mocktail/matchers/any.rb +27 -0
- data/lib/mocktail/sorbet/mocktail/matchers/base.rb +39 -0
- data/lib/mocktail/sorbet/mocktail/matchers/captor.rb +76 -0
- data/lib/mocktail/sorbet/mocktail/matchers/includes.rb +32 -0
- data/lib/mocktail/sorbet/mocktail/matchers/includes_hash.rb +12 -0
- data/lib/mocktail/sorbet/mocktail/matchers/includes_key.rb +12 -0
- data/lib/mocktail/sorbet/mocktail/matchers/includes_string.rb +12 -0
- data/lib/mocktail/sorbet/mocktail/matchers/is_a.rb +17 -0
- data/lib/mocktail/sorbet/mocktail/matchers/matches.rb +19 -0
- data/lib/mocktail/sorbet/mocktail/matchers/not.rb +17 -0
- data/lib/mocktail/sorbet/mocktail/matchers/numeric.rb +27 -0
- data/lib/mocktail/sorbet/mocktail/matchers/that.rb +32 -0
- data/lib/mocktail/sorbet/mocktail/matchers.rb +19 -0
- data/lib/mocktail/sorbet/mocktail/raises_neato_no_method_error.rb +93 -0
- data/lib/mocktail/sorbet/mocktail/records_demonstration.rb +43 -0
- data/lib/mocktail/sorbet/mocktail/registers_matcher.rb +65 -0
- data/lib/mocktail/sorbet/mocktail/registers_stubbing.rb +31 -0
- data/lib/mocktail/sorbet/mocktail/replaces_next.rb +55 -0
- data/lib/mocktail/sorbet/mocktail/replaces_type/redefines_new.rb +32 -0
- data/lib/mocktail/sorbet/mocktail/replaces_type/redefines_singleton_methods.rb +80 -0
- data/lib/mocktail/sorbet/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +39 -0
- data/lib/mocktail/sorbet/mocktail/replaces_type.rb +36 -0
- data/lib/mocktail/sorbet/mocktail/resets_state.rb +14 -0
- data/lib/mocktail/sorbet/mocktail/share/bind.rb +18 -0
- data/lib/mocktail/sorbet/mocktail/share/cleans_backtrace.rb +22 -0
- data/lib/mocktail/sorbet/mocktail/share/creates_identifier.rb +39 -0
- data/lib/mocktail/sorbet/mocktail/share/determines_matching_calls.rb +72 -0
- data/lib/mocktail/sorbet/mocktail/share/stringifies_call.rb +84 -0
- data/lib/mocktail/sorbet/mocktail/share/stringifies_method_name.rb +16 -0
- data/lib/mocktail/sorbet/mocktail/simulates_argument_error/reconciles_args_with_params.rb +27 -0
- data/lib/mocktail/sorbet/mocktail/simulates_argument_error/recreates_message.rb +34 -0
- data/lib/mocktail/sorbet/mocktail/simulates_argument_error/transforms_params.rb +58 -0
- data/lib/mocktail/sorbet/mocktail/simulates_argument_error.rb +36 -0
- data/lib/mocktail/sorbet/mocktail/sorbet.rb +3 -0
- data/lib/mocktail/sorbet/mocktail/stringifies_method_signature.rb +53 -0
- data/lib/mocktail/sorbet/mocktail/typed.rb +5 -0
- data/lib/mocktail/sorbet/mocktail/value/cabinet.rb +91 -0
- data/lib/mocktail/sorbet/mocktail/value/call.rb +51 -0
- data/lib/mocktail/sorbet/mocktail/value/demo_config.rb +10 -0
- data/lib/mocktail/sorbet/mocktail/value/double.rb +10 -0
- data/lib/mocktail/sorbet/mocktail/value/double_data.rb +15 -0
- data/lib/mocktail/sorbet/mocktail/value/explanation.rb +68 -0
- data/lib/mocktail/sorbet/mocktail/value/explanation_data.rb +19 -0
- data/lib/mocktail/sorbet/mocktail/value/fake_method_data.rb +11 -0
- data/lib/mocktail/sorbet/mocktail/value/matcher_registry.rb +27 -0
- data/lib/mocktail/sorbet/mocktail/value/no_explanation_data.rb +20 -0
- data/lib/mocktail/sorbet/mocktail/value/signature.rb +35 -0
- data/lib/mocktail/sorbet/mocktail/value/stubbing.rb +26 -0
- data/lib/mocktail/sorbet/mocktail/value/top_shelf.rb +79 -0
- data/lib/mocktail/sorbet/mocktail/value/type_replacement.rb +11 -0
- data/lib/mocktail/sorbet/mocktail/value/type_replacement_data.rb +19 -0
- data/lib/mocktail/sorbet/mocktail/value/unsatisfying_call.rb +9 -0
- data/lib/mocktail/sorbet/mocktail/value/unsatisfying_call_explanation.rb +24 -0
- data/lib/mocktail/sorbet/mocktail/value.rb +19 -0
- data/lib/mocktail/sorbet/mocktail/verifies_call/finds_verifiable_calls.rb +21 -0
- data/lib/mocktail/sorbet/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +15 -0
- data/lib/mocktail/sorbet/mocktail/verifies_call/raises_verification_error.rb +74 -0
- data/lib/mocktail/sorbet/mocktail/verifies_call.rb +37 -0
- data/lib/mocktail/sorbet/mocktail/version.rb +12 -0
- data/lib/mocktail/sorbet/mocktail.rb +154 -0
- data/lib/mocktail/sorbet.rb +1 -0
- data/lib/mocktail/stringifies_method_signature.rb +2 -0
- data/lib/mocktail/typed.rb +3 -0
- data/lib/mocktail/value/cabinet.rb +8 -1
- data/lib/mocktail/value/call.rb +44 -12
- data/lib/mocktail/value/demo_config.rb +6 -7
- data/lib/mocktail/value/double.rb +6 -7
- data/lib/mocktail/value/double_data.rb +11 -7
- data/lib/mocktail/value/explanation.rb +28 -3
- data/lib/mocktail/value/explanation_data.rb +14 -0
- data/lib/mocktail/value/fake_method_data.rb +7 -6
- data/lib/mocktail/value/matcher_registry.rb +2 -0
- data/lib/mocktail/value/no_explanation_data.rb +16 -0
- data/lib/mocktail/value/signature.rb +19 -27
- data/lib/mocktail/value/stubbing.rb +11 -12
- data/lib/mocktail/value/top_shelf.rb +5 -0
- data/lib/mocktail/value/type_replacement.rb +7 -8
- data/lib/mocktail/value/type_replacement_data.rb +10 -7
- data/lib/mocktail/value/unsatisfying_call.rb +5 -6
- data/lib/mocktail/value/unsatisfying_call_explanation.rb +18 -0
- data/lib/mocktail/value.rb +5 -2
- data/lib/mocktail/verifies_call/finds_verifiable_calls.rb +2 -0
- data/lib/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +2 -0
- data/lib/mocktail/verifies_call/raises_verification_error.rb +2 -0
- data/lib/mocktail/verifies_call.rb +3 -0
- data/lib/mocktail/version.rb +8 -1
- data/lib/mocktail.rb +46 -5
- data/mocktail.gemspec +8 -4
- data/rbi/mocktail-pregenerated.rbi +1923 -0
- data/rbi/mocktail.rbi +77 -0
- data/rbi/sorbet-runtime.rbi +29 -0
- data/script/build +47 -0
- data/script/setup +17 -0
- data/script/spoom_me +7 -0
- data/script/strip_sigils +17 -0
- data/script/test +33 -0
- data/script/test_double_require_warnings +46 -0
- data/script/update +21 -0
- data/sorbet/config +12 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1083 -0
- data/sorbet/rbi/gems/docile@1.4.0.rbi +376 -0
- data/sorbet/rbi/gems/json@2.6.3.rbi +1533 -0
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
- data/sorbet/rbi/gems/lint_roller@1.0.0.rbi +230 -0
- data/sorbet/rbi/gems/m@1.6.1.rbi +257 -0
- data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
- data/sorbet/rbi/gems/minitest@5.18.0.rbi +1491 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
- data/sorbet/rbi/gems/parallel@1.23.0.rbi +273 -0
- data/sorbet/rbi/gems/parser@3.2.2.1.rbi +7253 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
- data/sorbet/rbi/gems/rake@13.0.6.rbi +3024 -0
- data/sorbet/rbi/gems/rbi@0.0.16.rbi +3049 -0
- data/sorbet/rbi/gems/regexp_parser@2.8.0.rbi +3748 -0
- data/sorbet/rbi/gems/rexml@3.2.5.rbi +4717 -0
- data/sorbet/rbi/gems/rubocop-ast@1.29.0.rbi +7003 -0
- data/sorbet/rbi/gems/rubocop-performance@1.18.0.rbi +8 -0
- data/sorbet/rbi/gems/rubocop-sorbet@0.7.0.rbi +1043 -0
- data/sorbet/rbi/gems/rubocop@1.52.0.rbi +55500 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
- data/sorbet/rbi/gems/simplecov-html@0.12.3.rbi +216 -0
- data/sorbet/rbi/gems/simplecov@0.22.0.rbi +2148 -0
- data/sorbet/rbi/gems/simplecov_json_formatter@0.1.4.rbi +8 -0
- data/sorbet/rbi/gems/spoom@1.2.1.rbi +2536 -0
- data/sorbet/rbi/gems/standard-custom@1.0.1.rbi +63 -0
- data/sorbet/rbi/gems/standard-performance@1.1.0.rbi +8 -0
- data/sorbet/rbi/gems/standard@1.29.0.rbi +850 -0
- data/sorbet/rbi/gems/tapioca@0.11.6.rbi +3301 -0
- data/sorbet/rbi/gems/thor@1.2.2.rbi +3965 -0
- data/sorbet/rbi/gems/unicode-display_width@2.4.2.rbi +65 -0
- data/sorbet/rbi/gems/unparser@0.6.7.rbi +4524 -0
- data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
- data/sorbet/rbi/gems/yard@0.9.34.rbi +18219 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +4 -0
- data/spoom_data/05f0c49.json +1 -0
- data/spoom_data/1657012.json +1 -0
- data/spoom_data/167434b.json +1 -0
- data/spoom_data/1d0ba5b.json +1 -0
- data/spoom_data/1ffa724.json +1 -0
- data/spoom_data/2129b3d.json +1 -0
- data/spoom_data/24b1c92.json +1 -0
- data/spoom_data/2c46aee.json +1 -0
- data/spoom_data/305ec0b.json +1 -0
- data/spoom_data/30e9528.json +1 -0
- data/spoom_data/4638cd5.json +1 -0
- data/spoom_data/47c7dad.json +1 -0
- data/spoom_data/4b1edef.json +1 -0
- data/spoom_data/4de157f.json +1 -0
- data/spoom_data/526e7db.json +1 -0
- data/spoom_data/5d093b9.json +1 -0
- data/spoom_data/5db3b43.json +1 -0
- data/spoom_data/5fe2a65.json +1 -0
- data/spoom_data/6891312.json +1 -0
- data/spoom_data/6b0fef4.json +1 -0
- data/spoom_data/6b83d12.json +1 -0
- data/spoom_data/74c83c2.json +1 -0
- data/spoom_data/7644ff4.json +1 -0
- data/spoom_data/79054db.json +1 -0
- data/spoom_data/814e515.json +1 -0
- data/spoom_data/88c3b60.json +1 -0
- data/spoom_data/8bd4b6e.json +1 -0
- data/spoom_data/93f8153.json +1 -0
- data/spoom_data/95242fe.json +1 -0
- data/spoom_data/97f4c09.json +1 -0
- data/spoom_data/a13d150.json +1 -0
- data/spoom_data/a17f215.json +1 -0
- data/spoom_data/b705a9d.json +1 -0
- data/spoom_data/ba19195.json +1 -0
- data/spoom_data/bef51ca.json +1 -0
- data/spoom_data/dbb595a.json +1 -0
- data/spoom_data/e8fab92.json +1 -0
- data/spoom_data/f166c87.json +1 -0
- data/spoom_data/f57992d.json +1 -0
- data/spoom_data/f5a1e40.json +1 -0
- data/spoom_data/f6d6431.json +1 -0
- data/spoom_data/f72b67c.json +1 -0
- data/spoom_data/fc2f231.json +1 -0
- data/spoom_report.html +1248 -0
- data/src/mocktail/collects_calls.rb +18 -0
- data/src/mocktail/debug.rb +55 -0
- data/src/mocktail/dsl.rb +46 -0
- data/src/mocktail/errors.rb +19 -0
- data/src/mocktail/explains_nils.rb +41 -0
- data/src/mocktail/explains_thing.rb +137 -0
- data/src/mocktail/grabs_original_method_parameters.rb +33 -0
- data/src/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +27 -0
- data/src/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +24 -0
- data/src/mocktail/handles_dry_call/fulfills_stubbing.rb +45 -0
- data/src/mocktail/handles_dry_call/logs_call.rb +12 -0
- data/src/mocktail/handles_dry_call/validates_arguments.rb +45 -0
- data/src/mocktail/handles_dry_call.rb +25 -0
- data/src/mocktail/handles_dry_new_call.rb +42 -0
- data/src/mocktail/imitates_type/ensures_imitation_support.rb +16 -0
- data/src/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +73 -0
- data/src/mocktail/imitates_type/makes_double/declares_dry_class.rb +136 -0
- data/src/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +28 -0
- data/src/mocktail/imitates_type/makes_double.rb +29 -0
- data/src/mocktail/imitates_type.rb +29 -0
- data/src/mocktail/initialize_based_on_type_system_mode_switching.rb +11 -0
- data/src/mocktail/initializes_mocktail.rb +25 -0
- data/src/mocktail/matcher_presentation.rb +21 -0
- data/src/mocktail/matchers/any.rb +27 -0
- data/src/mocktail/matchers/base.rb +39 -0
- data/src/mocktail/matchers/captor.rb +76 -0
- data/src/mocktail/matchers/includes.rb +32 -0
- data/src/mocktail/matchers/includes_hash.rb +12 -0
- data/src/mocktail/matchers/includes_key.rb +12 -0
- data/src/mocktail/matchers/includes_string.rb +12 -0
- data/src/mocktail/matchers/is_a.rb +17 -0
- data/src/mocktail/matchers/matches.rb +19 -0
- data/src/mocktail/matchers/not.rb +17 -0
- data/src/mocktail/matchers/numeric.rb +27 -0
- data/src/mocktail/matchers/that.rb +32 -0
- data/src/mocktail/matchers.rb +19 -0
- data/src/mocktail/raises_neato_no_method_error.rb +93 -0
- data/src/mocktail/records_demonstration.rb +43 -0
- data/src/mocktail/registers_matcher.rb +65 -0
- data/src/mocktail/registers_stubbing.rb +31 -0
- data/src/mocktail/replaces_next.rb +55 -0
- data/src/mocktail/replaces_type/redefines_new.rb +32 -0
- data/src/mocktail/replaces_type/redefines_singleton_methods.rb +80 -0
- data/src/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +39 -0
- data/src/mocktail/replaces_type.rb +36 -0
- data/src/mocktail/resets_state.rb +14 -0
- data/src/mocktail/share/bind.rb +18 -0
- data/src/mocktail/share/cleans_backtrace.rb +22 -0
- data/src/mocktail/share/creates_identifier.rb +39 -0
- data/src/mocktail/share/determines_matching_calls.rb +72 -0
- data/src/mocktail/share/stringifies_call.rb +84 -0
- data/src/mocktail/share/stringifies_method_name.rb +16 -0
- data/src/mocktail/simulates_argument_error/reconciles_args_with_params.rb +27 -0
- data/src/mocktail/simulates_argument_error/recreates_message.rb +34 -0
- data/src/mocktail/simulates_argument_error/transforms_params.rb +58 -0
- data/src/mocktail/simulates_argument_error.rb +36 -0
- data/src/mocktail/sorbet.rb +3 -0
- data/src/mocktail/stringifies_method_signature.rb +53 -0
- data/src/mocktail/typed.rb +5 -0
- data/src/mocktail/value/cabinet.rb +91 -0
- data/src/mocktail/value/call.rb +51 -0
- data/src/mocktail/value/demo_config.rb +10 -0
- data/src/mocktail/value/double.rb +10 -0
- data/src/mocktail/value/double_data.rb +15 -0
- data/src/mocktail/value/explanation.rb +68 -0
- data/src/mocktail/value/explanation_data.rb +19 -0
- data/src/mocktail/value/fake_method_data.rb +11 -0
- data/src/mocktail/value/matcher_registry.rb +27 -0
- data/src/mocktail/value/no_explanation_data.rb +20 -0
- data/src/mocktail/value/signature.rb +35 -0
- data/src/mocktail/value/stubbing.rb +26 -0
- data/src/mocktail/value/top_shelf.rb +79 -0
- data/src/mocktail/value/type_replacement.rb +11 -0
- data/src/mocktail/value/type_replacement_data.rb +19 -0
- data/src/mocktail/value/unsatisfying_call.rb +9 -0
- data/src/mocktail/value/unsatisfying_call_explanation.rb +24 -0
- data/src/mocktail/value.rb +19 -0
- data/src/mocktail/verifies_call/finds_verifiable_calls.rb +21 -0
- data/src/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +15 -0
- data/src/mocktail/verifies_call/raises_verification_error.rb +74 -0
- data/src/mocktail/verifies_call.rb +37 -0
- data/src/mocktail/version.rb +12 -0
- data/src/mocktail.rb +154 -0
- data/sub_projects/rbi_generator/Gemfile +4 -0
- data/sub_projects/rbi_generator/Gemfile.lock +63 -0
- data/sub_projects/rbi_generator/sorbet/rbi/gems/.gitattributes +1 -0
- data/sub_projects/sorbet_user/Gemfile +13 -0
- data/sub_projects/sorbet_user/Gemfile.lock +126 -0
- data/sub_projects/sorbet_user/Rakefile +10 -0
- data/sub_projects/sorbet_user/bin/tapioca +27 -0
- data/sub_projects/sorbet_user/rbi/mocktail.rbi +9 -0
- data/sub_projects/sorbet_user/script/test +13 -0
- data/sub_projects/sorbet_user/sorbet/config +4 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/.gitattributes +1 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/ast@2.4.3.rbi +585 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/benchmark@0.4.0.rbi +618 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/erubi@1.13.1.rbi +155 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/json@2.21.2.rbi +2140 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/language_server-protocol@3.17.0.4.rbi +14244 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/lint_roller@1.1.0.rbi +239 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/logger@1.7.0.rbi +963 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/m@1.6.2.rbi +258 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/method_source@1.1.0.rbi +9 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/minitest@5.27.0.rbi +1549 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/mocktail@3.0.0.rbi +1923 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/parallel@1.26.3.rbi +291 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/parser@3.3.7.4.rbi +5451 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/prism@1.4.0.rbi +41732 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/racc@1.8.1.rbi +164 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/rake@13.2.1.rbi +3028 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/rbi@0.3.1.rbi +6599 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/rbs@3.9.2.rbi +6976 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/regexp_parser@2.10.0.rbi +3795 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop-ast@1.43.0.rbi +7717 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop-performance@1.24.0.rbi +9 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop-sorbet@0.9.0.rbi +9 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop@1.73.2.rbi +61306 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/sorbet-eraser@0.3.1.rbi +8 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/spoom@1.6.1.rbi +7274 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/standard-custom@1.0.2.rbi +8 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/standard-performance@1.7.0.rbi +9 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/standard-sorbet@0.0.3.rbi +53 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/standard@1.47.0.rbi +935 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/tapioca@0.16.11.rbi +3628 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/thor@1.3.2.rbi +4378 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/unicode-display_width@3.1.4.rbi +132 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/unicode-emoji@4.2.0.rbi +254 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
- data/sub_projects/sorbet_user/sorbet/rbi/gems/yard@0.9.45.rbi +18916 -0
- data/sub_projects/sorbet_user/sorbet/tapioca/config.yml +13 -0
- data/sub_projects/sorbet_user/sorbet/tapioca/require.rb +4 -0
- data/sub_projects/sorbet_user/test/ensure_type_safety_test.rb +21 -0
- data/sub_projects/sorbet_user/test/paint_by_number_test.rb +113 -0
- data/sub_projects/sorbet_user/test/sorbet_test.rb +289 -0
- data/sub_projects/sorbet_user/test/test_helper.rb +35 -0
- data/sub_projects/untyped_user/Gemfile +4 -0
- data/sub_projects/untyped_user/Gemfile.lock +25 -0
- data/sub_projects/untyped_user/antitype_test.rb +54 -0
- data/sub_projects/untyped_user/script/test +5 -0
- metadata +392 -9
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
# Stubbing and verifying mocked methods
|
|
2
|
+
|
|
3
|
+
The [test doubles](support/glossary.md#test-double) created by Mocktail can be used to aid in the setup and
|
|
4
|
+
assertion of [isolated unit tests](support/glossary.md#isolated-unit-testing)
|
|
5
|
+
by offering [stub configuration](support/glossary.md#stub) and [spy
|
|
6
|
+
verification](support/glossary.md#spy), respectively.
|
|
7
|
+
|
|
8
|
+
A headline benefit of choosing Mocktail over other mocking libraries is that
|
|
9
|
+
once you know how to stub a method, you also know how to verify a method.
|
|
10
|
+
Because Mocktail's [Mocktail.stubs](support/api.md#mocktailstubs) and
|
|
11
|
+
[Mocktail.verify](support/api.md#mocktailverify) methods are the two most-used
|
|
12
|
+
methods in the library, and because they both enable variations of the same
|
|
13
|
+
behavior—`stubs` anticipates future calls to a dependency whereas `verify`
|
|
14
|
+
ensures past calls occurred—their APIs are similarly symmetrical. That means
|
|
15
|
+
both the `stubs` and `verify` methods share the same basic signature and all the
|
|
16
|
+
same options. Mocking is poorly understood by a lot of developers, so we took a
|
|
17
|
+
lot of care in designing an API that reflected their conceptual similarity
|
|
18
|
+
instead of forcing users to memorize a larger API surface.
|
|
19
|
+
|
|
20
|
+
## Stubbing behavior
|
|
21
|
+
|
|
22
|
+
When you've mocked out a [dependency](support/glossary.md#dependency) of the
|
|
23
|
+
[subject you're testing](support/glossary.md#subject-under-test), you can use
|
|
24
|
+
Mocktail's [stubs](support/api.md#mocktailstubs) DSL method in your tests to
|
|
25
|
+
configure its methods to respond based on the arguments and blocks they're
|
|
26
|
+
passed.
|
|
27
|
+
|
|
28
|
+
In these examples, we'll look at an example dependency object with a few
|
|
29
|
+
instance methods we might want to stub.
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
class Bartop
|
|
33
|
+
def place_coaster(seat_position = 0)
|
|
34
|
+
# …
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def clean_surface(with:)
|
|
38
|
+
# …
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
And we'll work with a mock instance we can create with [Mocktail.of](support/api.md#mocktailof):
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
bartop = Mocktail.of(Bartop)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Initially, `bartop` will return `nil` for any invocation of its faked methods,
|
|
50
|
+
but will still require arguments match their specified signature, raising
|
|
51
|
+
`ArgumentError` if they aren't provided:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
> bartop.clean_surface(with: :rag)
|
|
55
|
+
=> nil
|
|
56
|
+
> bartop.clean_surface
|
|
57
|
+
=> missing keyword: :with [Mocktail call: `clean_surface'] (ArgumentError)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Simple, no-arg stubbing
|
|
61
|
+
|
|
62
|
+
Because `place_coaster`'s only parameter has a default value, the simplest
|
|
63
|
+
stubbing we can create is the no-arg case:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
stubs { bartop.place_coaster }.with { :a_coaster }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
From then onward, calling the method without args will return `:a_coaster`:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
> bartop.place_coaster
|
|
73
|
+
=> :a_coaster
|
|
74
|
+
> bartop.place_coaster(1)
|
|
75
|
+
=> nil
|
|
76
|
+
> bartop.place_coaster()
|
|
77
|
+
=> :a_coaster
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
We can also stub the same method multiple times. Newer stubbings will override
|
|
81
|
+
older ones, as configured stubbings are matched against invocations on a
|
|
82
|
+
"last-in wins" basis:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
> stubs { bartop.place_coaster }.with { :a_napkin }
|
|
86
|
+
=> nil
|
|
87
|
+
> bartop.place_coaster
|
|
88
|
+
=> :a_napkin
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
You can also limit the number of times a stubbing can be satisfied by providing
|
|
92
|
+
a `times` keyword argument to `stubs`:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
> stubs(times: 2) { bartop.place_coaster }.with { :gold_leaf }
|
|
96
|
+
=> nil
|
|
97
|
+
> bartop.place_coaster
|
|
98
|
+
=> :gold_leaf
|
|
99
|
+
> bartop.place_coaster
|
|
100
|
+
=> :gold_leaf
|
|
101
|
+
> bartop.place_coaster
|
|
102
|
+
=> :a_napkin
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
As you can see above, as soon as the `:gold_leaf` stubbing hit its satisfaction
|
|
106
|
+
limit of `2`, `place_coaster` started once again responding with `:a_napkin`.
|
|
107
|
+
|
|
108
|
+
## Stubbing with arguments
|
|
109
|
+
|
|
110
|
+
Of course, you wouldn't need a library if all you were stubbing was no-arg
|
|
111
|
+
methods, so let's start passing some values:
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
stubs { bartop.place_coaster(1) }.with { :coaster_1 }
|
|
115
|
+
stubs { bartop.place_coaster(2) }.with { :coaster_2 }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
And you can probably guess how these will behave:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
> bartop.place_coaster(2)
|
|
122
|
+
=> :coaster_2
|
|
123
|
+
> bartop.place_coaster(1)
|
|
124
|
+
=> :coaster_1
|
|
125
|
+
> bartop.place_coaster(3)
|
|
126
|
+
=> nil
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Keyword arguments work the same way as positional arguments:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
stubs { bartop.clean_surface(with: :bleach) }.with { "👃" }
|
|
133
|
+
stubs { bartop.clean_surface(with: :rag) }.with { "✨" }
|
|
134
|
+
|
|
135
|
+
> bartop.clean_surface(with: :rag)
|
|
136
|
+
=> "✨"
|
|
137
|
+
> bartop.clean_surface(with: :bleach)
|
|
138
|
+
=> "👃"
|
|
139
|
+
> bartop.clean_surface(with: :toothbrush)
|
|
140
|
+
=> nil
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Stubbing with inexact, dynamic arguments
|
|
144
|
+
|
|
145
|
+
When fully-isolated, tests will often provide exactly the
|
|
146
|
+
[values](support/glossary.md#value) that the subject will receive at every step,
|
|
147
|
+
and therefore will be able to provide a demonstration to `stubs` that passes the
|
|
148
|
+
exact arguments passed by the subject, or at least expected arguments that will
|
|
149
|
+
pass an equality check with the actual ones used by the subject.
|
|
150
|
+
|
|
151
|
+
But in more complex cases, you may need to configure a stubbing based on a
|
|
152
|
+
dynamic description of the arguments. Mocktail enables this with [argument
|
|
153
|
+
matchers](support/glossary.md#argument-matcher).
|
|
154
|
+
|
|
155
|
+
Here's a contrived example of Mocktail's built-in [matcher
|
|
156
|
+
API](support/api.md#matching-arguments-dynamically). A subject might pass a
|
|
157
|
+
random value to a dependency, which would definitely make it difficult
|
|
158
|
+
for a test to know the exact value being passed. Matchers could be used to
|
|
159
|
+
configure whether a stubbing or verification is satisfied.
|
|
160
|
+
|
|
161
|
+
Given this subject:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
def leave_bathroom
|
|
165
|
+
@wash_hands.for_seconds(rand(5..10))
|
|
166
|
+
end
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
A stubbing of `for_seconds` could work around the randomness by just matching
|
|
170
|
+
any value using [m.any](support/api.md#many):
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
stubs { |m| @wash_hands.for_seconds(m.any) }.with { :small_suds }
|
|
174
|
+
|
|
175
|
+
> @wash_hands.for_seconds(3)
|
|
176
|
+
=> :small_suds
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Or it could enforce the type with [m.numeric](support/api.md#mnumeric):
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
stubs { |m| @wash_hands.for_seconds(m.numeric) }.with { :medium_suds }
|
|
183
|
+
|
|
184
|
+
> @wash_hands.for_seconds(30)
|
|
185
|
+
=> :medium_suds
|
|
186
|
+
> @wash_hands.for_seconds("some time")
|
|
187
|
+
=> nil
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Or, to be even more precise, a matcher like [m.that](support/api.md#mthat)—which takes a block param
|
|
191
|
+
validate the each
|
|
192
|
+
argument by itself being invoked
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
stubs { |m|
|
|
196
|
+
@wash_hands.for_seconds(m.that {|s| s.between?(5, 10) })
|
|
197
|
+
}.with { :big_suds }
|
|
198
|
+
|
|
199
|
+
> @wash_hands.for_seconds(7)
|
|
200
|
+
=> :big_suds
|
|
201
|
+
> @wash_hands.for_seconds(1)
|
|
202
|
+
=> nil
|
|
203
|
+
> @wash_hands.for_seconds(14)
|
|
204
|
+
=> nil
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
For more on the various matchers that ship with Mocktail as well as how to
|
|
208
|
+
create your own custom matchers, check out their [API
|
|
209
|
+
documentation](support/api.md#matching-arguments-dynamically).
|
|
210
|
+
|
|
211
|
+
There is a _lot_ more you can do with the
|
|
212
|
+
[Mocktail.stubs](support/api.md#mocktailstubs) method, but the basics shown
|
|
213
|
+
abouve should cover the vast majority of usage.
|
|
214
|
+
|
|
215
|
+
## Verifying behavior
|
|
216
|
+
|
|
217
|
+
As mentioned at the top, Mocktail's mocks work as
|
|
218
|
+
[spies](support/glossary.md#spy), allowing users to verify that the
|
|
219
|
+
[subject](support/glossary.md#subject) invoked a method as expected. Mocktail
|
|
220
|
+
exposes this behavior through its [verify](support/api.md#mocktailverify) DSL
|
|
221
|
+
method. This section assumes you read and understand the
|
|
222
|
+
[stubs](#stubbing-behavior) section above, as the API is largely the same.
|
|
223
|
+
|
|
224
|
+
Before we dive in, there's a worthwhile discussion to be had comparing the
|
|
225
|
+
merits of using `stubs` and `verify`, because they weren't created equal.
|
|
226
|
+
|
|
227
|
+
[Pure functions](https://en.wikipedia.org/wiki/Pure_function), those who return
|
|
228
|
+
the same value for the same inputs and have no side effects, confer a lot of
|
|
229
|
+
benefits to developers: easier to comprehend, easier to compose, and easier to
|
|
230
|
+
test. It's generally worth striving to minimize the number of side effects
|
|
231
|
+
scattered throughout a codebase, but modern programming languages and frameworks
|
|
232
|
+
often make it very easy to write side-effect heavy code by failing to provide
|
|
233
|
+
meaningful return values, especially when I/O is concerned. Practicing
|
|
234
|
+
test-driven development with mocks, however, shines a bright light on side
|
|
235
|
+
effects in your [dependencies](support/glossary.md#dependency): each time you
|
|
236
|
+
call `verify`, you're introducing a side effect into your code.
|
|
237
|
+
|
|
238
|
+
As a result, it's possible (and in a sense, laudable) to only occasionally reach
|
|
239
|
+
for Mocktail's `verify` method. That said, Ruby doesn't lend itself especially
|
|
240
|
+
well to purely functional designs and, regardless, some number of side effects
|
|
241
|
+
are unavoidable for systems that interact with the outside world. And because
|
|
242
|
+
side effects are often very difficult to test (given the lack of a return
|
|
243
|
+
value), mocking libraries can make it very easy to test an interaction happens
|
|
244
|
+
as intended.
|
|
245
|
+
|
|
246
|
+
Suppose you have a subject that needs to call a dependency that has a side
|
|
247
|
+
effect and no return value (be wary of APIs that do both, violating
|
|
248
|
+
[command-query separation](support/glossary.md#command-query-separation)).
|
|
249
|
+
|
|
250
|
+
Let's make up an example of such a dependency:
|
|
251
|
+
|
|
252
|
+
```ruby
|
|
253
|
+
class OrdersLimes
|
|
254
|
+
def order!(lime_count = 1, shipping: :overnight)
|
|
255
|
+
# …
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
orders_limes = Mocktail.of(OrdersLimes)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Verifying a no-arg interaction
|
|
263
|
+
|
|
264
|
+
The simplest verification a test can make is of a dependent method with no
|
|
265
|
+
arguments. We can verify that `order!` was invoked like this:
|
|
266
|
+
|
|
267
|
+
```ruby
|
|
268
|
+
verify { orders_limes.order! }
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
But it hasn't been called yet! So `verify` will raise a
|
|
272
|
+
`Mocktail::VerificationError`:
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
Expected mocktail of `OrdersLimes#order!' to be called like: (Mocktail::VerificationError)
|
|
276
|
+
|
|
277
|
+
order!
|
|
278
|
+
|
|
279
|
+
But it was never called.
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
What if we try again? This time calling `order!` first:
|
|
283
|
+
|
|
284
|
+
```ruby
|
|
285
|
+
> orders_limes.order!
|
|
286
|
+
=> nil
|
|
287
|
+
> verify { orders_limes.order! }
|
|
288
|
+
=> nil
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Nothing happened! Just as you'd expect. The verification passed so no action is
|
|
292
|
+
necessary and the test can proceed.
|
|
293
|
+
|
|
294
|
+
We can call `order!` an arbitrary number of times and verify it as many times as
|
|
295
|
+
we like. By default, `verify` only cares that the specified interaction occurred
|
|
296
|
+
at least once.
|
|
297
|
+
|
|
298
|
+
### Verifying methods with arguments
|
|
299
|
+
|
|
300
|
+
When verifying an invocation with arguments, the same rules apply as for
|
|
301
|
+
stubbing: each actual positional and keyword argument is compared with those
|
|
302
|
+
specified in the `verify` [demonstration](support/glossary.md#demonstration)
|
|
303
|
+
using `==` or, optionally, an [argument
|
|
304
|
+
matchers](support/api.md#matching-arguments-dynamically).
|
|
305
|
+
|
|
306
|
+
Let's call `order!` a few times in different ways:
|
|
307
|
+
|
|
308
|
+
```ruby
|
|
309
|
+
orders_limes.order!(3)
|
|
310
|
+
orders_limes.order!(50, shipping: :two_day)
|
|
311
|
+
orders_limes.order!(shipping: :ground)
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Now let's try a verification that we know will fail:
|
|
315
|
+
|
|
316
|
+
```ruby
|
|
317
|
+
verify { orders_limes.order!(4, shipping: :ground) }
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
This will fail as we'd expect, as well as printing out summaries of the prior
|
|
321
|
+
invocations:
|
|
322
|
+
|
|
323
|
+
```ruby
|
|
324
|
+
Expected mocktail of `OrdersLimes#order!' to be called like: (Mocktail::VerificationError)
|
|
325
|
+
|
|
326
|
+
order!(4, shipping: :ground)
|
|
327
|
+
|
|
328
|
+
It was called differently 3 times:
|
|
329
|
+
|
|
330
|
+
order!(3)
|
|
331
|
+
|
|
332
|
+
order!(50, shipping: :two_day)
|
|
333
|
+
|
|
334
|
+
order!(shipping: :ground)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Mocktail does its best to reconstruct a scrutible string for each invocation to
|
|
338
|
+
ease in debugging unexpected failures, but if that's enough, you can also
|
|
339
|
+
leverage its [Mocktail.calls](support/api.md#mocktailcalls) method to inspect
|
|
340
|
+
each invocation to `order!`, replete with references to each argument passed:
|
|
341
|
+
|
|
342
|
+
```ruby
|
|
343
|
+
> Mocktail.calls(orders_limes, :order!)
|
|
344
|
+
=>
|
|
345
|
+
[#<Mocktail::Call:0x0000000104631af0
|
|
346
|
+
@args=[3],
|
|
347
|
+
@block=nil,
|
|
348
|
+
@double=#<Mocktail of OrdersLimes:0x00000001044974b0>,
|
|
349
|
+
@dry_type=#<Class for mocktail of OrdersLimes:0x000000010465e758>,
|
|
350
|
+
@kwargs={},
|
|
351
|
+
@method=:order!,
|
|
352
|
+
@original_method=#<UnboundMethod: OrdersLimes#order!(lime_count=..., shipping: ...),
|
|
353
|
+
@original_type=OrdersLimes,
|
|
354
|
+
@singleton=false>,
|
|
355
|
+
#<Mocktail::Call:0x0000000104652318
|
|
356
|
+
@args=[50],
|
|
357
|
+
@block=nil,
|
|
358
|
+
@double=#<Mocktail of OrdersLimes:0x00000001044974b0>,
|
|
359
|
+
@dry_type=#<Class for mocktail of OrdersLimes:0x000000010465e758>,
|
|
360
|
+
@kwargs={:shipping=>:two_day},
|
|
361
|
+
@method=:order!,
|
|
362
|
+
@original_method=#<UnboundMethod: OrdersLimes#order!(lime_count=..., shipping: ...),
|
|
363
|
+
@original_type=OrdersLimes,
|
|
364
|
+
@singleton=false>,
|
|
365
|
+
#<Mocktail::Call:0x00000001046512d8
|
|
366
|
+
@args=[],
|
|
367
|
+
@block=nil,
|
|
368
|
+
@double=#<Mocktail of OrdersLimes:0x00000001044974b0>,
|
|
369
|
+
@dry_type=#<Class for mocktail of OrdersLimes:0x000000010465e758>,
|
|
370
|
+
@kwargs={:shipping=>:ground},
|
|
371
|
+
@method=:order!,
|
|
372
|
+
@original_method=#<UnboundMethod: OrdersLimes#order!(lime_count=..., shipping: ...),
|
|
373
|
+
@original_type=OrdersLimes,
|
|
374
|
+
@singleton=false>]
|
|
375
|
+
|
|
376
|
+
# Inspecting the most recent call's keyword arguments:
|
|
377
|
+
> Mocktail.calls(orders_limes, :order!).last.kwargs
|
|
378
|
+
=> {:shipping=>:ground}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
This is, hopefully, all you'd need to figure out why an expected invocation
|
|
382
|
+
failed a `verify` check unexpectedly.
|
|
383
|
+
|
|
384
|
+
### Verifying a call happened a certain number of times
|
|
385
|
+
|
|
386
|
+
Just like `stubs`, `verify` has a `times` keyword argument. But, where `stubs`
|
|
387
|
+
will limit a stubbing to the number of `times` specified, `verify` will enforce
|
|
388
|
+
that exactly that numer of matching invocations took place.
|
|
389
|
+
|
|
390
|
+
This isn't something you'll need every day, but if you're paranoid about
|
|
391
|
+
erroneously making multiple lime orders, then you could ensure it was just
|
|
392
|
+
called once:
|
|
393
|
+
|
|
394
|
+
```ruby
|
|
395
|
+
> orders_limes.order!(5, shipping: :two_day)
|
|
396
|
+
=> nil
|
|
397
|
+
> orders_limes.order!(5, shipping: :two_day)
|
|
398
|
+
=> nil
|
|
399
|
+
> verify(times: 1) { orders_limes.order!(5, shipping: :two_day) }
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
As you might expect, this will raise a `VerificationError` because the method
|
|
403
|
+
was called twice in the specified way instead of once. The error message tries
|
|
404
|
+
to make this clear:
|
|
405
|
+
|
|
406
|
+
```ruby
|
|
407
|
+
Expected mocktail of `OrdersLimes#order!' to be called like: (Mocktail::VerificationError)
|
|
408
|
+
|
|
409
|
+
order!(5, shipping: :two_day) [1 time]
|
|
410
|
+
|
|
411
|
+
But it was actually called this way 2 times.
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Adding matchers to a verification
|
|
415
|
+
|
|
416
|
+
Continuing the thread above, let's say you don't know or don't care what the
|
|
417
|
+
`shipping:` keyword argument was set to. For the purposes of the test, if that
|
|
418
|
+
doesn't matter and you just want to express that only a single order for `5`
|
|
419
|
+
limes was made, regardless of shipping method, you can use the
|
|
420
|
+
[m.any](support/api.md#many) just like we did in [the stubbing section
|
|
421
|
+
above](#stubbing-with-inexact-dynamic-arguments).
|
|
422
|
+
|
|
423
|
+
To make this point, let's call `order!` one more time with a different shipping
|
|
424
|
+
method:
|
|
425
|
+
|
|
426
|
+
```ruby
|
|
427
|
+
> orders_limes.order!(5, shipping: :carrier_pigeon)
|
|
428
|
+
=> nil
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Now we can adjust our `verify` call by using `m.any` for the `shipping` kwarg:
|
|
432
|
+
|
|
433
|
+
```ruby
|
|
434
|
+
verify(times: 1) { |m| orders_limes.order!(5, shipping: m.any) }
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Because we'd called the method twice in the [immediately
|
|
438
|
+
previous](#verifying-a-call-happened-a-certain-number-of-times) and once more
|
|
439
|
+
just now. So we should expect Mocktail's error to find all _three_ matching
|
|
440
|
+
invocations:
|
|
441
|
+
|
|
442
|
+
```ruby
|
|
443
|
+
Expected mocktail of `OrdersLimes#order!' to be called like: (Mocktail::VerificationError)
|
|
444
|
+
|
|
445
|
+
order!(5, shipping: any) [1 time]
|
|
446
|
+
|
|
447
|
+
But it was actually called this way 3 times.
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
There it is! The expectation sees `shipping: any` and correctly counts that it
|
|
451
|
+
was invoked `3 times`.
|
|
452
|
+
|
|
453
|
+
### Ignoring extraneous arguments entirely
|
|
454
|
+
|
|
455
|
+
Let's keep pulling the thread and continue the example above.
|
|
456
|
+
|
|
457
|
+
Suppose this isn't paranoid _enough_ for our tastes. Maybe the method supports
|
|
458
|
+
lots of additional optional arguments. And maybe we just _really really_ care
|
|
459
|
+
that the method was called once no matter what. We could do this in two ways:
|
|
460
|
+
|
|
461
|
+
1. Verify that the method was called once, regardless of argument
|
|
462
|
+
2. Split the verification in two: verify the call exactly as we expect, and assert
|
|
463
|
+
the call count is as we expect
|
|
464
|
+
|
|
465
|
+
In general, approach #2 is better: it expresses the two intentions separately,
|
|
466
|
+
which allows both to be made precisely.
|
|
467
|
+
|
|
468
|
+
If we'd been expecting `:carrier_pigeon` shipping all along, we could verify it
|
|
469
|
+
and then check `Mocktail.calls` to have the right number of invocations on
|
|
470
|
+
`:order!`:
|
|
471
|
+
|
|
472
|
+
```ruby
|
|
473
|
+
> verify { orders_limes.order!(5, shipping: :carrier_pigeon) }
|
|
474
|
+
=> nil
|
|
475
|
+
> assert_equal 1, Mocktail.calls(orders_limes, :order!).size
|
|
476
|
+
=> 💥 asertion failed! Expected 1 but got 3
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
If this is what you're trying to accomplish, this approach is not only more
|
|
480
|
+
precise in what it asserts, it expresses the test's intent more clearly to
|
|
481
|
+
future readers.
|
|
482
|
+
|
|
483
|
+
If, however, extraneous arguments are truly irrelevant from the perspective of
|
|
484
|
+
the test, approach #1 may be preferable. To enable this, you can pass
|
|
485
|
+
`ignore_extra_args: true`.
|
|
486
|
+
|
|
487
|
+
In our running example, we can omit all or some of the arguments and
|
|
488
|
+
`ignore_extra_args` will match every invocation, ignoring the value of their
|
|
489
|
+
other arguments. This way, we could specify that we wanted exactly one
|
|
490
|
+
invocation of `order!` via carrier pigeon, no matter how many limes were
|
|
491
|
+
ordered:
|
|
492
|
+
|
|
493
|
+
```ruby
|
|
494
|
+
> verify(times: 1, ignore_extra_args: true) { orders_limes.order!(shipping: :carrier_pigeon) }
|
|
495
|
+
=> nil
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
For more options and complications, check out the full documentation of the
|
|
499
|
+
[verify](support/api.md#mocktailverify) API.
|
|
500
|
+
|
|
501
|
+
## Pulling it all together
|
|
502
|
+
|
|
503
|
+
At this point, we've covered either Mocktail's [sorbet
|
|
504
|
+
setup](./installation_sorbet.md) or [untyped
|
|
505
|
+
install](./installation_untyped.md). You've learned how to instantiate mocks by
|
|
506
|
+
[dependency injection](tdd/poro/dependency_injection.md), [dependency
|
|
507
|
+
inception](tdd/poro/dependency_inception.md), or [class/module method
|
|
508
|
+
replacement](tdd/class_methods.md). And now you've been through the basics of
|
|
509
|
+
stubbing and verifying interactions with mocked methods. You've also
|
|
510
|
+
probably referenced the full [API documentation](support/api.md) and visited the [glossary of terms](support/glossary.md) a few times.
|
|
511
|
+
|
|
512
|
+
All that's left is to put it all together and write a test!
|
|
513
|
+
|
|
514
|
+
**When you're ready, let's [walk through a complete example test](example_test.md), guided by Mocktail.**
|
|
515
|
+
|