mocktail 2.0.0 → 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 +2 -2
- data/CHANGELOG.md +5 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +76 -82
- data/README.md +2 -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/debug.rb +4 -3
- data/lib/mocktail/share/stringifies_call.rb +0 -1
- data/lib/mocktail/simulates_argument_error/transforms_params.rb +2 -2
- data/lib/mocktail/sorbet/mocktail/debug.rb +4 -3
- data/lib/mocktail/sorbet/mocktail/share/stringifies_call.rb +0 -1
- data/lib/mocktail/sorbet/mocktail/simulates_argument_error/transforms_params.rb +2 -2
- data/lib/mocktail/sorbet/mocktail/value/call.rb +1 -1
- data/lib/mocktail/sorbet/mocktail/version.rb +1 -1
- data/lib/mocktail/value/call.rb +1 -1
- data/lib/mocktail/version.rb +1 -1
- data/mocktail.gemspec +2 -2
- data/rbi/mocktail-pregenerated.rbi +58 -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/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 +264 -8
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require "mocktail"; require "minitest/autorun" # standard:disable Style/Semicolon
|
|
2
|
+
|
|
3
|
+
class PrepsFruitsTest < Minitest::Test
|
|
4
|
+
def setup
|
|
5
|
+
@fetches_fruits = Mocktail.of_next(FetchesFruits)
|
|
6
|
+
@slices_fruit = Mocktail.of_next(SlicesFruit)
|
|
7
|
+
@stores_fruit = Mocktail.of_next(StoresFruit)
|
|
8
|
+
|
|
9
|
+
@subject = PrepsFruits.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_prep # standard:disable Layout/IndentationConsistency, Layout/IndentationWidth
|
|
13
|
+
fruits = [Lime.new, Mango.new, Pineapple.new]
|
|
14
|
+
stubs { @fetches_fruits.fetch([:lime, :mango, :pineapple]) }.with { fruits }
|
|
15
|
+
stubs { |m| @slices_fruit.slice(m.is_a(Fruit)) }.with { |call|
|
|
16
|
+
SlicedFruit.new(call.args.first)
|
|
17
|
+
}
|
|
18
|
+
stubs { |m| @stores_fruit.store(m.is_a(SlicedFruit)) }.with { |call|
|
|
19
|
+
StoredFruit.new("ID for #{call.args.first.type}", call.args.first)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
result = @subject.prep([:lime, :mango, :pineapple])
|
|
23
|
+
|
|
24
|
+
assert_equal 3, result.size
|
|
25
|
+
assert_equal "ID for Lime", result[0].id
|
|
26
|
+
assert_equal Lime, result[0].fruit.type
|
|
27
|
+
assert_equal "ID for Mango", result[1].id
|
|
28
|
+
assert_equal Mango, result[1].fruit.type
|
|
29
|
+
assert_equal "ID for Zebras", result[2].id
|
|
30
|
+
assert_equal Pineapple, result[2].fruit.type
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class PrepsFruits
|
|
35
|
+
def initialize
|
|
36
|
+
@fetches_fruits = FetchesFruits.new
|
|
37
|
+
@slices_fruit = SlicesFruit.new
|
|
38
|
+
@stores_fruit = StoresFruit.new
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def prep(fruit_types)
|
|
42
|
+
@fetches_fruits.fetch(fruit_types).map { |fruit|
|
|
43
|
+
fruit = @slices_fruit.slice(fruit)
|
|
44
|
+
@stores_fruit.store(fruit)
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class FetchesFruits
|
|
50
|
+
def fetch(types)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class SlicesFruit
|
|
55
|
+
def slice(fruit)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class StoresFruit
|
|
60
|
+
def store(fruit)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class Fruit
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class Lime < Fruit
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class Mango < Fruit
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class Pineapple < Fruit
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class SlicedFruit
|
|
77
|
+
def initialize(fruit)
|
|
78
|
+
@fruit = fruit
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def type
|
|
82
|
+
@fruit.class
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
StoredFruit = Struct.new(:id, :fruit)
|
|
87
|
+
|
|
88
|
+
class Minitest::Test
|
|
89
|
+
include Mocktail::DSL
|
|
90
|
+
|
|
91
|
+
def teardown
|
|
92
|
+
Mocktail.reset
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# Glossary of terms
|
|
2
|
+
|
|
3
|
+
## Argument matcher
|
|
4
|
+
|
|
5
|
+
When an invocation of a mocked method is compared against its configured
|
|
6
|
+
[stubbings](#stub) or [spy verifications](#spy), every argument must pass an
|
|
7
|
+
equality check to be said to satisfy the stubbing or verification. In cases
|
|
8
|
+
where a test either can't provide arguments that would `==` their actual values
|
|
9
|
+
or where the configuration should match a broader range of values, argument
|
|
10
|
+
matchers can be used to relax or otherwise augment the process of determining
|
|
11
|
+
if a call matches a [demonstration](#demonstration).
|
|
12
|
+
|
|
13
|
+
Put more explicitly, an argument matcher is a utility method that returns an
|
|
14
|
+
object that provides a means of determining if an expected and actual argument
|
|
15
|
+
"match" and which the mocking library prioritizes over a default equality
|
|
16
|
+
comparison ([source](/src/mocktail/share/determines_matching_calls.rb#L63-L70)).
|
|
17
|
+
|
|
18
|
+
In Mocktail, argument matchers are provided by its [matcher
|
|
19
|
+
API](api.md#matching-arguments-dynamically) invoked via an optional block
|
|
20
|
+
parameter passed to the demonstration.
|
|
21
|
+
|
|
22
|
+
## Arrange-Act-Assert
|
|
23
|
+
|
|
24
|
+
The three phases of a unit test are said to be "arrange", "act", and "assert".
|
|
25
|
+
(In more plainspoken parlance, these are often translated to "given", "when",
|
|
26
|
+
and "then".) They refer to the three necessary activities that make a test a
|
|
27
|
+
test: setting things up ("arrange"), invoking the [subject under
|
|
28
|
+
test](#subject-under-test) ("act"), and verifying the results ("assert").
|
|
29
|
+
|
|
30
|
+
Because nearly every test does all three things, tools and conventions are often
|
|
31
|
+
associated with a particular phase. For example, test fixtures and factories
|
|
32
|
+
prepare the prerequisite state of database, so they're normally configured in
|
|
33
|
+
the assert phase. Additionally, tests are sometimes made clearer by demarcating
|
|
34
|
+
the phases with DSLs (like `let` and `it` RSpec) or a line of whitespace. In
|
|
35
|
+
fact, the [rspec-given](https://github.com/rspec-given/rspec-given) library
|
|
36
|
+
exists to explicitly map each activity of a test to one of the three phases,
|
|
37
|
+
both to better express intention and to take advantage of commonality between
|
|
38
|
+
the phases (like memoizing reused setup code).
|
|
39
|
+
|
|
40
|
+
In the context of Mocktail, mocks are typically created and
|
|
41
|
+
[stubbings](#stub) are configured during the arrange phase, while
|
|
42
|
+
verifications take place during the assert phase. A notable advantage of
|
|
43
|
+
[spies](#spy) over [formal mocks](#mock) is that spies allow for assertion after
|
|
44
|
+
the act phase has completed, whereas mocks require assertions to be set up in
|
|
45
|
+
the arrange phase (which violates the natural "arrange-act-assert" phase
|
|
46
|
+
ordering).
|
|
47
|
+
|
|
48
|
+
## Command-query separation
|
|
49
|
+
|
|
50
|
+
Command-query separation refers to a design practice of avoiding methods and
|
|
51
|
+
functions that _both_ return a value and have a meaningful side effect. Quoting
|
|
52
|
+
its [Wikipedia entry](https://en.wikipedia.org/wiki/Command–query_separation),
|
|
53
|
+
"asking a question should not change the answer". Discussing this principle in
|
|
54
|
+
the context of mocking is often necessary because there is a tendency for
|
|
55
|
+
developers to see a method they know has a necessary return value _and_ an
|
|
56
|
+
important side effect and feel an urge to _both [stub](#stub) and
|
|
57
|
+
[verify](#spy)_ the same interaction. Whenever that seems like a good idea,
|
|
58
|
+
there's a good chance the [dependent method](#dependency) is violating
|
|
59
|
+
command-query separation and its design should be revisited.
|
|
60
|
+
|
|
61
|
+
(Stubbing and verifying the same interaction is never necessary from a test
|
|
62
|
+
specification perspective: if the stubbing is necessary for the
|
|
63
|
+
[subject](#subject-under-test) to do its work, an additional verification of the
|
|
64
|
+
same interaction is redundant.)
|
|
65
|
+
|
|
66
|
+
## Delegator
|
|
67
|
+
|
|
68
|
+
A delegator is a unit of code (typically a class in Ruby) that performs its work
|
|
69
|
+
primarily by calling through to its [dependencies](#dependency) as opposed to
|
|
70
|
+
implementing domain logic itself. In the broader world of software, the vast
|
|
71
|
+
majority of domain objects mix delegation and domain logic without much concern
|
|
72
|
+
for separating them. When practicing [isolated TDD](#isolated-unit-testing)
|
|
73
|
+
rigorously, however, mixing delegation with domain logic makes for especially
|
|
74
|
+
painful orchestration of [test doubles](#test-double). As a result, delegators
|
|
75
|
+
tend to emerge as a distinct type of unit in a codebase.
|
|
76
|
+
|
|
77
|
+
Delegators confer a few benefits to a broader codebase:
|
|
78
|
+
|
|
79
|
+
* They encourage developers to imagine a greater number
|
|
80
|
+
of single-purpose units to implement domain logic, making it easier to adhere to
|
|
81
|
+
the [single-responsibility
|
|
82
|
+
principle](https://en.wikipedia.org/wiki/Single-responsibility_principle)
|
|
83
|
+
* Clearly distinguishing between delegators and units that implement domain
|
|
84
|
+
logic results in easy-to-navigate, tree-shaped dependency graphs with fewer
|
|
85
|
+
cycles (when visualizing dependency graphs, delegators are never leaf nodes in the tree, but implementors of domain logic almost always are)
|
|
86
|
+
* Because delegators essentially _only_ interact with other application-defined
|
|
87
|
+
units, they operate at a single [level of abstraction](#level-of-abstraction)
|
|
88
|
+
and make it easier for each unit to which they delegate to also operate at
|
|
89
|
+
a single level of abstraction
|
|
90
|
+
|
|
91
|
+
To illustrate, a dependency graph resulting from outside-in isolated TDD will
|
|
92
|
+
often look like this, with only as many layers of delegators as are necessary to
|
|
93
|
+
identify single-purpose units to implement domain logic:
|
|
94
|
+
|
|
95
|
+
<p align="center" width="100%">
|
|
96
|
+
<img src="../img/delegator_tree.png" width="320" alt="a tree of dependencies with logic as the leaf nodes">
|
|
97
|
+
</p>
|
|
98
|
+
|
|
99
|
+
## Demonstration
|
|
100
|
+
|
|
101
|
+
Mocktail's API was designed so that you could configure a [stubbing](#stub) or
|
|
102
|
+
[spy verification](#spy) by invoking a mocked [dependency](#dependency)'s method
|
|
103
|
+
_exactly_ as the subject would. This has two benefits: reducing the mental
|
|
104
|
+
overhead of keeping a larger mocking API straight and increasing the
|
|
105
|
+
find-and-replaceability of your code. We call that invocation used to configure
|
|
106
|
+
the mock a "demonstration", because the test is demonstrating how it expects
|
|
107
|
+
the mocked method to be called.
|
|
108
|
+
|
|
109
|
+
For example, in the following stubbing:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
stubs { @highball.stir(times: 3) }.with { :bubbles }
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The mock `@highball` has its `stir` method stubbed with the return value
|
|
116
|
+
`:bubbles` when it is invoked as demonstrated above.
|
|
117
|
+
|
|
118
|
+
Or this verification:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
verify { @highball.drink!(speed: SIPPING) }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
In this case, `@highball.drink!(speed: SIPPING)` is a demonstration of the call
|
|
125
|
+
we expect to the subject to invoke.
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
## Dependency
|
|
129
|
+
|
|
130
|
+
In [isolated unit testing](#isolated-unit-testing), a "dependency" almost always
|
|
131
|
+
refers to a plain ol' Ruby class for which one or more instances are depended on
|
|
132
|
+
by a [subject under test](#subject-under-test).
|
|
133
|
+
|
|
134
|
+
This usage of the word "dependency" in the context of unit testing with mocking
|
|
135
|
+
libraries stands in contrast to most others, where the word most often refers to
|
|
136
|
+
third-party libraries and frameworks (usually distributed as Ruby gems) or to
|
|
137
|
+
networked services (e.g. an HTTP API). In this use, an integrated application or
|
|
138
|
+
project is implied as the thing depending on the dependency.
|
|
139
|
+
|
|
140
|
+
In Gerard Mezsaros' XUnit Patterns, he referred to dependencies less ambiguously
|
|
141
|
+
as [depended-on components (DOC)](http://xunitpatterns.com/DOC.html).
|
|
142
|
+
|
|
143
|
+
## Isolated unit testing
|
|
144
|
+
|
|
145
|
+
Isolated unit testing (also known as "mockist", "London-school" test-driven
|
|
146
|
+
development, or discovery testing) was most thoroughly defined in Steve Freeman
|
|
147
|
+
and Nat Pryce's book [Growing Object-Oriented Software, Guided by
|
|
148
|
+
Tests](https://www.amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/0321503627).
|
|
149
|
+
Whereas traditional test-driven development often builds systems "bottom-up" by
|
|
150
|
+
starting with units that implement domain logic (leaving the ultimate
|
|
151
|
+
composition of dependencies up to the individual to compose or extract
|
|
152
|
+
manually), isolated TDD starts "outside-in" and it results in the decomposition
|
|
153
|
+
of a big problem into small units as a matter of course.
|
|
154
|
+
|
|
155
|
+
In simplest terms, an isolated unit test exercises the behavior of the
|
|
156
|
+
[subject](#subject-under-test) but not of any of its dependencies, instead
|
|
157
|
+
replacing all of them at runtime with alternatives controlled by the test. This
|
|
158
|
+
puts the subject under extreme isolation, allowing the tester to:
|
|
159
|
+
|
|
160
|
+
* Test the subject's behavior without invoking the behavior of its
|
|
161
|
+
[dependencies](#dependency) and thereby introducing a transitive dependency on
|
|
162
|
+
them
|
|
163
|
+
* Assert or observe the subject's behavior directly, as opposed to measuring
|
|
164
|
+
the a return value or a side effect of a dependency
|
|
165
|
+
* validate the design of the contracts between the subject and its dependencies
|
|
166
|
+
by responding to the pain of stubbing and verifying any interactions with the
|
|
167
|
+
dependencies, because (assuming an expressive mocking library) API contracts
|
|
168
|
+
that are hard to fake are generally also hard to use
|
|
169
|
+
|
|
170
|
+
Code that results from isolated test-driven development tends to result in
|
|
171
|
+
separate classes of units: one that breaks down the work
|
|
172
|
+
([delegators](#delegator)) and another that implements a single aspect of the
|
|
173
|
+
work work as single-purpose units. This approach is typified by tree-shaped
|
|
174
|
+
designs that branch from the program's entry point into a set of delegators and
|
|
175
|
+
a larger number of implementation objects as leaf nods—many of them behaving as
|
|
176
|
+
pure functions.
|
|
177
|
+
|
|
178
|
+
## Level of Abstraction
|
|
179
|
+
|
|
180
|
+
The phrase "level of abstraction" is both ironically and inherently amorphous,
|
|
181
|
+
because it refers to a conceptual [layer of
|
|
182
|
+
concepts](https://en.wikipedia.org/wiki/Abstraction_layer). The term is most
|
|
183
|
+
often used in the context of the exhortation "don't mix levels of abstraction",
|
|
184
|
+
which is even harder to pin down a meaning to.
|
|
185
|
+
|
|
186
|
+
To clarify the term for the purpose of a discussion of [isolated unit
|
|
187
|
+
testing](#isolated-unit-testing), consider a level of abstraction as "the
|
|
188
|
+
classification of things a unit of code interacts with". For example, imagine
|
|
189
|
+
units of code as interacting with one or more of these "levels" of abstraction:
|
|
190
|
+
|
|
191
|
+
1. Primitive booleans, strings, numbers
|
|
192
|
+
2. Collections of multiple primitives
|
|
193
|
+
3. Value objects containing primitives and collections
|
|
194
|
+
4. Implementers of domain logic that operate on [value objects](#value)
|
|
195
|
+
5. [Delegators](#delegator) of implementers of domain logic
|
|
196
|
+
|
|
197
|
+
When practicing outside-in test-driven development, testing
|
|
198
|
+
[subjects](#subject-under-test) operating on that 5th level of abstraction is
|
|
199
|
+
the only layer for which mocking out dependencies has a clear, valuable purpose.
|
|
200
|
+
Moreover, if such a subject _also_ interacts significantly with layers 1-4, the
|
|
201
|
+
test will generally be painful to write, because the [test double](#test-double)
|
|
202
|
+
configuration will be more complicated. As a result, practicing outside-in TDD
|
|
203
|
+
and responding to testing pain by adjusting the design of the production code
|
|
204
|
+
will often result in delegator objects that do nothing but delegate their work
|
|
205
|
+
up to other units that implement domain logic as opposed to _mixing levels
|
|
206
|
+
of abstraction_ in their implementation.
|
|
207
|
+
|
|
208
|
+
## Mock
|
|
209
|
+
|
|
210
|
+
A mock is a specific type of [test double](#test-double) that is typified by
|
|
211
|
+
having any expected invocations specified _in advance_ of invoking the
|
|
212
|
+
[subject](#subject-under-test) (that is, during the [arrange
|
|
213
|
+
phase](#arrange-act-assert), as opposed to the assertion phase). Typically, a
|
|
214
|
+
mock would be configured, the subject invoked, and then the mock would be called
|
|
215
|
+
to verify that its expectations were met.
|
|
216
|
+
|
|
217
|
+
Mocktail creates test doubles that perform validation as [spies](#spy), but
|
|
218
|
+
Minitest ships with `Minitest::Mock`, which implements traditional mocking
|
|
219
|
+
behavior:
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
mock = Minitest::Mock.new
|
|
223
|
+
mock.expect(:some_method, :some_arg)
|
|
224
|
+
|
|
225
|
+
subject.do_stuff(mock)
|
|
226
|
+
|
|
227
|
+
mock.verify
|
|
228
|
+
# => if `mock.some_method(:some_arg)` is not called, will raise:
|
|
229
|
+
# MockExpectationError: "expected some_method() => :some_arg"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Mock objects were popularized in the [Extreme
|
|
233
|
+
Programming](https://en.wikipedia.org/wiki/Extreme_programming) community by
|
|
234
|
+
Steve Freeman and others, with their paper on what they termed
|
|
235
|
+
"[Endotesting](https://www2.ccs.neu.edu/research/demeter/related-work/extreme-programming/MockObjectsFinal.PDF)" at the XP 2000 conference.
|
|
236
|
+
Mocking was the dominant method of verifying behavior with test doubles until
|
|
237
|
+
2010, as [test spies](#spy) began to pick up more steam, thanks to the
|
|
238
|
+
popularity of Java's [Mockito](https://site.mockito.org) and
|
|
239
|
+
[Jasmine](https://jasmine.github.io/api/edge/global.html#spyOn) in JavaScript.
|
|
240
|
+
|
|
241
|
+
## Partial mock
|
|
242
|
+
|
|
243
|
+
A partial mock is a [test double](#test-double) that replaces some, but not all,
|
|
244
|
+
of its real functionality with fake functionality. This could refer to its
|
|
245
|
+
state, behavior, or some combination of both. Partial mocks are generally
|
|
246
|
+
considered an antipattern, for several reasons:
|
|
247
|
+
|
|
248
|
+
* [Isolated tests](#isolated-unit-testing) are designed to establish clear boundaries between a
|
|
249
|
+
[subject](#subject-under-test) and its [dependencies](#dependency), so drawing
|
|
250
|
+
that border somewhere in the middle of one of the dependencies represents an
|
|
251
|
+
inherently unclear boundary
|
|
252
|
+
* Whenever a real method on a partial mock calls a fake method on itself, the
|
|
253
|
+
test author needs to concern themselves with any stubbing and verifying
|
|
254
|
+
happening within the internals of a dependency (as opposed to the subject being
|
|
255
|
+
tested itself), and because those implementation details can change at any time,
|
|
256
|
+
the test of an unrelated subject that is facially isolated from the partial mock
|
|
257
|
+
could very likely fail
|
|
258
|
+
* It is perhaps not-so-surprisingly very easy for internal state held by partial
|
|
259
|
+
mocks to enter undefined states, leading to behavior that's completley unlike
|
|
260
|
+
how their "real" methods will behave in production, so their perceived realness
|
|
261
|
+
is often illusory
|
|
262
|
+
|
|
263
|
+
Even when things are kept very simple, mocking is poorly understood and leads to
|
|
264
|
+
a lot of confusion, so the increase in complexity represented by partial mocks
|
|
265
|
+
results in test-scoped code that's very difficult to read and understand how it
|
|
266
|
+
is behaving and what that behavior _means_ in terms of what assurances are being
|
|
267
|
+
provided by the test.
|
|
268
|
+
|
|
269
|
+
## Proxy
|
|
270
|
+
|
|
271
|
+
In the context of mocking, the word proxy most often describes a [test
|
|
272
|
+
doubles](#test-double) that records all interactions made against its methods
|
|
273
|
+
(like a [spy](#spy)), but unlike every other kind of test double, proceeds to
|
|
274
|
+
call through to the _actual_ implementation of the dependency. This can be seen
|
|
275
|
+
as having the best of both worlds (verifying interactions without violating
|
|
276
|
+
their veracity), but more often results in tests make unnecessarily many
|
|
277
|
+
assertions and promotes the design of code that overly relies on side effects
|
|
278
|
+
over pure functions.
|
|
279
|
+
|
|
280
|
+
Proxies aren't especially common in mocking libraries, but can be found in rr's
|
|
281
|
+
[mock.proxy](https://github.com/rr/rr/blob/master/doc/03_api_overview.md#mockproxy)
|
|
282
|
+
API and, in JavaScript, with Jasmine spies'
|
|
283
|
+
[callThrough()](https://jasmine.github.io/api/edge/SpyStrategy.html) function.
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
## Spy
|
|
287
|
+
|
|
288
|
+
A spy is a special sub-type of a [test double](#test-double) that describes a
|
|
289
|
+
fake object that silently records all invocations made against it and provides a
|
|
290
|
+
way for a test to interrogate those interactions in the
|
|
291
|
+
[assert](#arrange-act-assert) phase. The term "[test
|
|
292
|
+
spy](http://xunitpatterns.com/Test%20Spy.html)" was first coined by Gerard
|
|
293
|
+
Mezsaros for his book [XUnit
|
|
294
|
+
Patterns](https://www.amazon.com/xUnit-Test-Patterns-Refactoring-Code/dp/0131495054/).
|
|
295
|
+
|
|
296
|
+
The "mock" methods created by Mocktail can behave as both spies and
|
|
297
|
+
[stubs](#stub), as they allow after-the-fact assertion and introspection via the
|
|
298
|
+
[verify](../support/api.md#mocktailverify) DSL method. (This approach also
|
|
299
|
+
enabled debugging utilities like
|
|
300
|
+
[Mocktail.explain](../support/api.md#mocktailexplain) and
|
|
301
|
+
[Mocktail.calls](../support/api.md#mocktailcalls).)
|
|
302
|
+
|
|
303
|
+
In general, interactions should only be verified explicitly when the
|
|
304
|
+
[dependency](#dependency) triggers a side effect that doesn't return a
|
|
305
|
+
meaningful value that is able to be observed by the
|
|
306
|
+
[subject](#subject-under-test)'s ultimate result. In such a case, a spy
|
|
307
|
+
is used like this:
|
|
308
|
+
|
|
309
|
+
```ruby
|
|
310
|
+
copy_machine = Mocktail.of(CopyMachine)
|
|
311
|
+
subject = Accountant.new
|
|
312
|
+
|
|
313
|
+
subject.record(:secret_stuff, with: copy_machine)
|
|
314
|
+
|
|
315
|
+
verify { copy_machine.copy(:secret_stuff) }
|
|
316
|
+
# => if `copy_machine.copy` was invoked with `:secret_stuff`, nothing happens.
|
|
317
|
+
# Otherwise, it raises Mocktail::VerificationError:
|
|
318
|
+
#
|
|
319
|
+
# Expected mocktail of `CopyMachine#copy' to be called like:
|
|
320
|
+
#
|
|
321
|
+
# copy(:secret_stuff)
|
|
322
|
+
#
|
|
323
|
+
# But it was never called.
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Stub
|
|
327
|
+
|
|
328
|
+
In mocking parlance, a "stub" is a specific type of a [test
|
|
329
|
+
double](#test-double) wherein a function or method is configured in the
|
|
330
|
+
[arrange](#arrange-act-assert) phase of an [isolated
|
|
331
|
+
test](#isolated-unit-testing) to respond with a particular value (or raise a
|
|
332
|
+
particular error). A particular stubbing (that is, a preconfigured response)
|
|
333
|
+
might be applied to all invocations of the stubbed method, or made to depend on
|
|
334
|
+
the arguments provided.
|
|
335
|
+
|
|
336
|
+
The purpose of stubbing is to facilitate downstream behavior in the
|
|
337
|
+
[subject](#subject-under-test) by configuring an artificial response (return
|
|
338
|
+
values or errors) in a test double of a [dependency](#dependency). Mocktail's
|
|
339
|
+
"mocks" can be used as both stubs and [spies](#spy), so here's an example
|
|
340
|
+
using its [stubs](api.md#mocktailstubs) DSL method:
|
|
341
|
+
|
|
342
|
+
```ruby
|
|
343
|
+
peeler = Mocktail.of(Peeler)
|
|
344
|
+
subject = Bartender.new(peeler)
|
|
345
|
+
stubs { peeler.peel(:orange) }.with { :peeled_orange }
|
|
346
|
+
stubs { peeler.peel(:lemon) }.with { :peeled_lemon }
|
|
347
|
+
|
|
348
|
+
result = subject.prep([:orange, :lemon])
|
|
349
|
+
|
|
350
|
+
assert_equal [:peeled_orange, :peeled_lemon], result
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
In the example above, the two `stubs` merely enable the `Bartender` subject to
|
|
354
|
+
do its job (of invoking `peel` on each element passed to it). Because the stubs'
|
|
355
|
+
configuration will only return the `:peeled_` symbols if passed the correct
|
|
356
|
+
argument, no explicit assertion that the calls occurred is necessary. Since
|
|
357
|
+
calling `peeler.peel(:grape)` would return nil, the above stubbing is sufficient
|
|
358
|
+
to verify the dependency was invoked appropriately.
|
|
359
|
+
|
|
360
|
+
## Subject under test
|
|
361
|
+
|
|
362
|
+
The [subject under test](http://xunitpatterns.com/SUT.html) (or "subject") was
|
|
363
|
+
coined by Gerard Meszaros in his book [XUnit
|
|
364
|
+
Patterns](https://www.amazon.com/xUnit-Test-Patterns-Refactoring-Code/dp/0131495054/)
|
|
365
|
+
to refer to the _thing being tested_. That's all. Nothing too fancy!
|
|
366
|
+
|
|
367
|
+
In order to promote easier extract refactors, some developers like to assign the
|
|
368
|
+
subject under test to a variable name like `@subject` in every test so that test
|
|
369
|
+
cases can be moved between file listings with less effort. It has the added
|
|
370
|
+
benefit of always clarifying the thing being tested from any other
|
|
371
|
+
[dependencies](#dependency) referenced in the test.
|
|
372
|
+
|
|
373
|
+
## Test double
|
|
374
|
+
|
|
375
|
+
A test double is a catch-all term for a fake object meant to stand-in for a real
|
|
376
|
+
thing. The name is meant to evoke the image of a stunt double who stands in for
|
|
377
|
+
the real actor in your tests. It was coined by Gerard Meszaros in his [book
|
|
378
|
+
XUnit patterns](http://xunitpatterns.com/Test Double.html). Technically, the
|
|
379
|
+
mocks generated by Mocktail would be most correctly described not as mocks at
|
|
380
|
+
all, but as "combination stubs and spies" in proper parlance, but outside a very
|
|
381
|
+
tiny group of people who write mocking libraries, the distinctions have turned
|
|
382
|
+
out to not be sufficiently meaningful to teach people half a dozen special words
|
|
383
|
+
for what everyone colloquially prefers to call a "mock".
|
|
384
|
+
|
|
385
|
+
([Test Double](https://testdouble.com) is also the name of a
|
|
386
|
+
pretty great software consultancy with strong ties to the Ruby community and
|
|
387
|
+
which incidentally created and maintains Mocktail.)
|
|
388
|
+
|
|
389
|
+
## Value
|
|
390
|
+
|
|
391
|
+
Similar to how [isolated TDD](#isolated-unit-testing) tends to lead to a sharp
|
|
392
|
+
distinction between [delegators](#delegator) that break up the work and small
|
|
393
|
+
units to implement it, struct-like objects to represent values also emerge as
|
|
394
|
+
distinct from objects that contain domain logic that implements features.
|
|
395
|
+
|
|
396
|
+
Because delegators typically only hold references to their
|
|
397
|
+
[dependencies](#dependency) and are otherwise stateless, their public methods
|
|
398
|
+
tend to receive value objects. Values essentially become the
|
|
399
|
+
"unit of work", and are passed elsewhere, wrapped, transformed, or mutated, and
|
|
400
|
+
returned. Values typically contain primitive data (e.g. booleans, strings, etc.),
|
|
401
|
+
other values, and methods that merely _elucidate_ the data they hold as opposed
|
|
402
|
+
to implementing feature logic.
|
|
403
|
+
|
|
404
|
+
In Ruby, values are most often implemented as
|
|
405
|
+
[Struct](https://docs.ruby-lang.org/en/master/Struct.html),
|
|
406
|
+
[Data](https://docs.ruby-lang.org/en/master/Data.html), or (in Sorbet)
|
|
407
|
+
[T::Struct](https://sorbet.org/docs/tstruct).
|
|
408
|
+
|
|
409
|
+
## Wrapper object
|
|
410
|
+
|
|
411
|
+
A wrapper object, sometimes referred to as an adapter (or even "[scar
|
|
412
|
+
tissue](https://www.destroyallsoftware.com/talks/boundaries)") is often
|
|
413
|
+
introduced to wrap code whose API can't be readily changed in response to being
|
|
414
|
+
difficult to mock out in a test (e.g. a third-party library, a utility
|
|
415
|
+
maintained by another team, etc). Wrappers typically act as a solitary
|
|
416
|
+
chokepoint for an application's use of a third-party API, which can serve a
|
|
417
|
+
couple of key benefits:
|
|
418
|
+
|
|
419
|
+
* Wrappers self-document the extent to which a codebase uses a particular
|
|
420
|
+
dependency and make it easy to assess swapping it for an alternative without
|
|
421
|
+
requiring changes to be made throughout the codebase
|
|
422
|
+
* If a wrapper is written around a hard-to-mock (and therefore hard-to-use)
|
|
423
|
+
third-party API, then the wrapper can effectively serve as a rug under which the
|
|
424
|
+
complexity of that API can be swept by exposing its behavior through
|
|
425
|
+
easier-to-mock (and therefore easier-to-use) method signatures and return values
|
|
426
|
+
that look and feel similar to those found in the rest of the codebase
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Mocking class and module methods
|
|
2
|
+
|
|
3
|
+
Usually, Mocktail is used to create mock _instances_ of classes and modules, but
|
|
4
|
+
you may occassionally want or need to mock out the methods defined on a class or
|
|
5
|
+
module.
|
|
6
|
+
|
|
7
|
+
To fake out all the methods on a type, you can simply pass it to
|
|
8
|
+
[Mocktail.replace](/docs/support/api.md#mocktailreplace). Here is a little
|
|
9
|
+
example:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
module Substitution
|
|
13
|
+
def self.for(ingredient)
|
|
14
|
+
ingredient.alternatives.first
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Somewhere in a test
|
|
19
|
+
Mocktail.replace(Substitution)
|
|
20
|
+
|
|
21
|
+
stubs { Substitution.for(:peychauds_bitters) }.with { :angosutra_bitters }
|
|
22
|
+
|
|
23
|
+
Substitution.for(:peychauds_bitters)
|
|
24
|
+
=> :angosutra_bitters
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Heads up!
|
|
28
|
+
|
|
29
|
+
`Mocktail.replace` is a spicy operation because it globally mutates that class
|
|
30
|
+
or module by overwriting its methods. When called, Mocktail does its best to
|
|
31
|
+
dispatch the real or the fake method based on whether the currently-running
|
|
32
|
+
thread has faked the type, but this isn't 100% fool-proof, so be wary of
|
|
33
|
+
potential pollution if you use [thread-based test
|
|
34
|
+
parallelization](https://edgeguides.rubyonrails.org/testing.html#parallel-testing-with-threads),
|
|
35
|
+
and remember to configure an after-each hook to call
|
|
36
|
+
[Mocktail.reset](/docs/support/api.md#mocktailreset) to restore state.
|
|
37
|
+
|
|
38
|
+
## Way to stay classy
|
|
39
|
+
|
|
40
|
+
Where to go from here?
|
|
41
|
+
|
|
42
|
+
**Head back onto the golden path and use Mocktail to create [fake instances of Ruby classes](./poro.md).**
|
|
43
|
+
|
|
44
|
+
**Put these overridden classes and modules to good use by [stubbing and verifying their methods](../stubbing_and_verifying.md).**
|