mocktail 1.2.3 → 2.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.
Files changed (183) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +6 -5
  3. data/.gitignore +3 -0
  4. data/.standard.yml +8 -0
  5. data/CHANGELOG.md +14 -0
  6. data/Gemfile +6 -1
  7. data/Gemfile.lock +98 -25
  8. data/README.md +18 -922
  9. data/Rakefile +0 -1
  10. data/bin/console +1 -2
  11. data/bin/tapioca +29 -0
  12. data/lib/mocktail/collects_calls.rb +2 -0
  13. data/lib/mocktail/debug.rb +13 -10
  14. data/lib/mocktail/dsl.rb +2 -0
  15. data/lib/mocktail/errors.rb +2 -0
  16. data/lib/mocktail/explains_nils.rb +2 -0
  17. data/lib/mocktail/explains_thing.rb +7 -4
  18. data/lib/mocktail/grabs_original_method_parameters.rb +30 -0
  19. data/lib/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +3 -1
  20. data/lib/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +5 -1
  21. data/lib/mocktail/handles_dry_call/fulfills_stubbing.rb +2 -0
  22. data/lib/mocktail/handles_dry_call/logs_call.rb +2 -0
  23. data/lib/mocktail/handles_dry_call/validates_arguments.rb +6 -4
  24. data/lib/mocktail/handles_dry_call.rb +2 -0
  25. data/lib/mocktail/handles_dry_new_call.rb +2 -0
  26. data/lib/mocktail/imitates_type/ensures_imitation_support.rb +2 -0
  27. data/lib/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +4 -1
  28. data/lib/mocktail/imitates_type/makes_double/declares_dry_class.rb +32 -20
  29. data/lib/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +2 -0
  30. data/lib/mocktail/imitates_type/makes_double.rb +3 -0
  31. data/lib/mocktail/imitates_type.rb +3 -1
  32. data/lib/mocktail/initialize_based_on_type_system_mode_switching.rb +9 -0
  33. data/lib/mocktail/initializes_mocktail.rb +5 -0
  34. data/lib/mocktail/matcher_presentation.rb +4 -2
  35. data/lib/mocktail/matchers/any.rb +4 -3
  36. data/lib/mocktail/matchers/base.rb +10 -2
  37. data/lib/mocktail/matchers/captor.rb +9 -0
  38. data/lib/mocktail/matchers/includes.rb +2 -0
  39. data/lib/mocktail/matchers/includes_hash.rb +9 -0
  40. data/lib/mocktail/matchers/includes_key.rb +9 -0
  41. data/lib/mocktail/matchers/includes_string.rb +9 -0
  42. data/lib/mocktail/matchers/is_a.rb +2 -0
  43. data/lib/mocktail/matchers/matches.rb +2 -0
  44. data/lib/mocktail/matchers/not.rb +2 -0
  45. data/lib/mocktail/matchers/numeric.rb +5 -4
  46. data/lib/mocktail/matchers/that.rb +2 -0
  47. data/lib/mocktail/matchers.rb +3 -0
  48. data/lib/mocktail/raises_neato_no_method_error.rb +2 -0
  49. data/lib/mocktail/records_demonstration.rb +2 -0
  50. data/lib/mocktail/registers_matcher.rb +8 -3
  51. data/lib/mocktail/registers_stubbing.rb +2 -0
  52. data/lib/mocktail/replaces_next.rb +7 -1
  53. data/lib/mocktail/replaces_type/redefines_new.rb +3 -1
  54. data/lib/mocktail/replaces_type/redefines_singleton_methods.rb +14 -2
  55. data/lib/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +37 -0
  56. data/lib/mocktail/replaces_type.rb +6 -0
  57. data/lib/mocktail/resets_state.rb +2 -0
  58. data/lib/mocktail/share/bind.rb +7 -5
  59. data/lib/mocktail/share/cleans_backtrace.rb +3 -5
  60. data/lib/mocktail/share/creates_identifier.rb +16 -9
  61. data/lib/mocktail/share/determines_matching_calls.rb +4 -2
  62. data/lib/mocktail/share/stringifies_call.rb +6 -2
  63. data/lib/mocktail/share/stringifies_method_name.rb +3 -1
  64. data/lib/mocktail/simulates_argument_error/reconciles_args_with_params.rb +2 -0
  65. data/lib/mocktail/simulates_argument_error/recreates_message.rb +2 -0
  66. data/lib/mocktail/simulates_argument_error/transforms_params.rb +15 -8
  67. data/lib/mocktail/simulates_argument_error.rb +2 -0
  68. data/lib/mocktail/sorbet/mocktail/collects_calls.rb +18 -0
  69. data/lib/mocktail/sorbet/mocktail/debug.rb +54 -0
  70. data/lib/mocktail/sorbet/mocktail/dsl.rb +46 -0
  71. data/lib/mocktail/sorbet/mocktail/errors.rb +19 -0
  72. data/lib/mocktail/sorbet/mocktail/explains_nils.rb +41 -0
  73. data/lib/mocktail/sorbet/mocktail/explains_thing.rb +137 -0
  74. data/lib/mocktail/sorbet/mocktail/grabs_original_method_parameters.rb +33 -0
  75. data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +27 -0
  76. data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +24 -0
  77. data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing.rb +45 -0
  78. data/lib/mocktail/sorbet/mocktail/handles_dry_call/logs_call.rb +12 -0
  79. data/lib/mocktail/sorbet/mocktail/handles_dry_call/validates_arguments.rb +45 -0
  80. data/lib/mocktail/sorbet/mocktail/handles_dry_call.rb +25 -0
  81. data/lib/mocktail/sorbet/mocktail/handles_dry_new_call.rb +42 -0
  82. data/lib/mocktail/sorbet/mocktail/imitates_type/ensures_imitation_support.rb +16 -0
  83. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +73 -0
  84. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/declares_dry_class.rb +136 -0
  85. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +28 -0
  86. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double.rb +29 -0
  87. data/lib/mocktail/sorbet/mocktail/imitates_type.rb +29 -0
  88. data/lib/mocktail/sorbet/mocktail/initialize_based_on_type_system_mode_switching.rb +11 -0
  89. data/lib/mocktail/sorbet/mocktail/initializes_mocktail.rb +25 -0
  90. data/lib/mocktail/sorbet/mocktail/matcher_presentation.rb +21 -0
  91. data/lib/mocktail/sorbet/mocktail/matchers/any.rb +27 -0
  92. data/lib/mocktail/sorbet/mocktail/matchers/base.rb +39 -0
  93. data/lib/mocktail/sorbet/mocktail/matchers/captor.rb +76 -0
  94. data/lib/mocktail/sorbet/mocktail/matchers/includes.rb +32 -0
  95. data/lib/mocktail/sorbet/mocktail/matchers/includes_hash.rb +12 -0
  96. data/lib/mocktail/sorbet/mocktail/matchers/includes_key.rb +12 -0
  97. data/lib/mocktail/sorbet/mocktail/matchers/includes_string.rb +12 -0
  98. data/lib/mocktail/sorbet/mocktail/matchers/is_a.rb +17 -0
  99. data/lib/mocktail/sorbet/mocktail/matchers/matches.rb +19 -0
  100. data/lib/mocktail/sorbet/mocktail/matchers/not.rb +17 -0
  101. data/lib/mocktail/sorbet/mocktail/matchers/numeric.rb +27 -0
  102. data/lib/mocktail/sorbet/mocktail/matchers/that.rb +32 -0
  103. data/lib/mocktail/sorbet/mocktail/matchers.rb +19 -0
  104. data/lib/mocktail/sorbet/mocktail/raises_neato_no_method_error.rb +93 -0
  105. data/lib/mocktail/sorbet/mocktail/records_demonstration.rb +43 -0
  106. data/lib/mocktail/sorbet/mocktail/registers_matcher.rb +65 -0
  107. data/lib/mocktail/sorbet/mocktail/registers_stubbing.rb +31 -0
  108. data/lib/mocktail/sorbet/mocktail/replaces_next.rb +55 -0
  109. data/lib/mocktail/sorbet/mocktail/replaces_type/redefines_new.rb +32 -0
  110. data/lib/mocktail/sorbet/mocktail/replaces_type/redefines_singleton_methods.rb +80 -0
  111. data/lib/mocktail/sorbet/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +39 -0
  112. data/lib/mocktail/sorbet/mocktail/replaces_type.rb +36 -0
  113. data/lib/mocktail/sorbet/mocktail/resets_state.rb +14 -0
  114. data/lib/mocktail/sorbet/mocktail/share/bind.rb +18 -0
  115. data/lib/mocktail/sorbet/mocktail/share/cleans_backtrace.rb +22 -0
  116. data/lib/mocktail/sorbet/mocktail/share/creates_identifier.rb +39 -0
  117. data/lib/mocktail/sorbet/mocktail/share/determines_matching_calls.rb +72 -0
  118. data/lib/mocktail/sorbet/mocktail/share/stringifies_call.rb +85 -0
  119. data/lib/mocktail/sorbet/mocktail/share/stringifies_method_name.rb +16 -0
  120. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/reconciles_args_with_params.rb +27 -0
  121. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/recreates_message.rb +34 -0
  122. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/transforms_params.rb +58 -0
  123. data/lib/mocktail/sorbet/mocktail/simulates_argument_error.rb +36 -0
  124. data/lib/mocktail/sorbet/mocktail/sorbet.rb +3 -0
  125. data/lib/mocktail/sorbet/mocktail/stringifies_method_signature.rb +53 -0
  126. data/lib/mocktail/sorbet/mocktail/typed.rb +5 -0
  127. data/lib/mocktail/sorbet/mocktail/value/cabinet.rb +91 -0
  128. data/lib/mocktail/sorbet/mocktail/value/call.rb +51 -0
  129. data/lib/mocktail/sorbet/mocktail/value/demo_config.rb +10 -0
  130. data/lib/mocktail/sorbet/mocktail/value/double.rb +10 -0
  131. data/lib/mocktail/sorbet/mocktail/value/double_data.rb +15 -0
  132. data/lib/mocktail/sorbet/mocktail/value/explanation.rb +68 -0
  133. data/lib/mocktail/sorbet/mocktail/value/explanation_data.rb +19 -0
  134. data/lib/mocktail/sorbet/mocktail/value/fake_method_data.rb +11 -0
  135. data/lib/mocktail/sorbet/mocktail/value/matcher_registry.rb +27 -0
  136. data/lib/mocktail/sorbet/mocktail/value/no_explanation_data.rb +20 -0
  137. data/lib/mocktail/sorbet/mocktail/value/signature.rb +35 -0
  138. data/lib/mocktail/sorbet/mocktail/value/stubbing.rb +26 -0
  139. data/lib/mocktail/sorbet/mocktail/value/top_shelf.rb +79 -0
  140. data/lib/mocktail/sorbet/mocktail/value/type_replacement.rb +11 -0
  141. data/lib/mocktail/sorbet/mocktail/value/type_replacement_data.rb +19 -0
  142. data/lib/mocktail/sorbet/mocktail/value/unsatisfying_call.rb +9 -0
  143. data/lib/mocktail/sorbet/mocktail/value/unsatisfying_call_explanation.rb +24 -0
  144. data/lib/mocktail/sorbet/mocktail/value.rb +19 -0
  145. data/lib/mocktail/sorbet/mocktail/verifies_call/finds_verifiable_calls.rb +21 -0
  146. data/lib/mocktail/sorbet/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +15 -0
  147. data/lib/mocktail/sorbet/mocktail/verifies_call/raises_verification_error.rb +74 -0
  148. data/lib/mocktail/sorbet/mocktail/verifies_call.rb +37 -0
  149. data/lib/mocktail/sorbet/mocktail/version.rb +12 -0
  150. data/lib/mocktail/sorbet/mocktail.rb +154 -0
  151. data/lib/mocktail/sorbet.rb +1 -0
  152. data/lib/mocktail/stringifies_method_signature.rb +2 -0
  153. data/lib/mocktail/typed.rb +3 -0
  154. data/lib/mocktail/value/cabinet.rb +8 -1
  155. data/lib/mocktail/value/call.rb +44 -12
  156. data/lib/mocktail/value/demo_config.rb +6 -7
  157. data/lib/mocktail/value/double.rb +6 -7
  158. data/lib/mocktail/value/double_data.rb +11 -7
  159. data/lib/mocktail/value/explanation.rb +28 -3
  160. data/lib/mocktail/value/explanation_data.rb +14 -0
  161. data/lib/mocktail/value/fake_method_data.rb +7 -6
  162. data/lib/mocktail/value/matcher_registry.rb +2 -0
  163. data/lib/mocktail/value/no_explanation_data.rb +16 -0
  164. data/lib/mocktail/value/signature.rb +19 -27
  165. data/lib/mocktail/value/stubbing.rb +11 -12
  166. data/lib/mocktail/value/top_shelf.rb +5 -0
  167. data/lib/mocktail/value/type_replacement.rb +7 -8
  168. data/lib/mocktail/value/type_replacement_data.rb +10 -7
  169. data/lib/mocktail/value/unsatisfying_call.rb +5 -6
  170. data/lib/mocktail/value/unsatisfying_call_explanation.rb +18 -0
  171. data/lib/mocktail/value.rb +5 -2
  172. data/lib/mocktail/verifies_call/finds_verifiable_calls.rb +2 -0
  173. data/lib/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +2 -0
  174. data/lib/mocktail/verifies_call/raises_verification_error.rb +2 -0
  175. data/lib/mocktail/verifies_call.rb +3 -0
  176. data/lib/mocktail/version.rb +8 -1
  177. data/lib/mocktail.rb +46 -5
  178. data/mocktail.gemspec +8 -4
  179. data/rbi/mocktail-pregenerated.rbi +1865 -0
  180. data/rbi/mocktail.rbi +77 -0
  181. data/rbi/sorbet-runtime.rbi +29 -0
  182. data/spoom_report.html +1248 -0
  183. metadata +130 -3
data/Rakefile CHANGED
@@ -4,7 +4,6 @@ require "standard/rake"
4
4
 
5
5
  Rake::TestTask.new(:test) do |t|
6
6
  t.libs << "test"
7
- t.libs << "lib"
8
7
  t.test_files = FileList["test/**/*_test.rb"]
9
8
  end
10
9
 
data/bin/console CHANGED
@@ -77,5 +77,4 @@ Shop.open!(42)
77
77
 
78
78
  Shop.close!(42)
79
79
 
80
- require "pry"
81
- Pry.start
80
+ binding.irb
data/bin/tapioca ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'tapioca' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("tapioca", "tapioca")
@@ -1,5 +1,7 @@
1
1
  module Mocktail
2
2
  class CollectsCalls
3
+ extend T::Sig
4
+
3
5
  def collect(double, method_name)
4
6
  calls = ExplainsThing.new.explain(double).reference.calls
5
7
 
@@ -1,5 +1,7 @@
1
1
  module Mocktail
2
2
  module Debug
3
+ extend T::Sig
4
+
3
5
  # It would be easy and bad for the mocktail lib to call something like
4
6
  #
5
7
  # double == other_double
@@ -12,26 +14,27 @@ module Mocktail
12
14
  # happens unintentionally. This works in conjunction with the test
13
15
  # MockingMethodfulClassesTest, because it mocks every defined method on the
14
16
  # mocked BasicObject
17
+
15
18
  def self.guard_against_mocktail_accidentally_calling_mocks_if_debugging!
16
19
  return unless ENV["MOCKTAIL_DEBUG_ACCIDENTAL_INTERNAL_MOCK_CALLS"]
17
- raise
18
- rescue => e
20
+ raise Mocktail::Error
21
+ rescue Mocktail::Error => e
19
22
  base_path = Pathname.new(__FILE__).dirname.to_s
20
- backtrace_minus_this_and_whoever_called_this = e.backtrace[2..]
21
- internal_call_sites = backtrace_minus_this_and_whoever_called_this.take_while { |call_site|
23
+ backtrace_minus_this_and_whoever_called_this = e.backtrace&.[](2..)
24
+ internal_call_sites = backtrace_minus_this_and_whoever_called_this&.take_while { |call_site|
22
25
  # the "in `block" is very confusing but necessary to include lines after
23
26
  # a stubs { blah.foo }.with { … } call, since that's when most of the
24
27
  # good stuff happens
25
28
  call_site.start_with?(base_path) || call_site.include?("in `block")
26
- }.reject { |call_site| call_site.include?("in `block") }
29
+ }&.reject { |call_site| call_site.include?("in `block") } || []
27
30
 
28
31
  approved_call_sites = [
29
- "fulfills_stubbing.rb:14",
30
- "validates_arguments.rb:16",
31
- "validates_arguments.rb:19"
32
+ /fulfills_stubbing.rb:(16|20)/,
33
+ /validates_arguments.rb:(18|23)/,
34
+ /validates_arguments.rb:(21|26)/
32
35
  ]
33
36
  if internal_call_sites.any? && approved_call_sites.none? { |approved_call_site|
34
- internal_call_sites.first.include?(approved_call_site)
37
+ internal_call_sites.first&.match?(approved_call_site)
35
38
  }
36
39
  raise Error.new <<~MSG
37
40
  Unauthorized internal call of a mock internally by Mocktail itself:
@@ -40,7 +43,7 @@ module Mocktail
40
43
 
41
44
  Offending call's complete stack trace:
42
45
 
43
- #{backtrace_minus_this_and_whoever_called_this.join("\n")}
46
+ #{backtrace_minus_this_and_whoever_called_this&.join("\n")}
44
47
  ==END OFFENDING TRACE==
45
48
  MSG
46
49
  end
data/lib/mocktail/dsl.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  module Mocktail
2
2
  module DSL
3
+ extend T::Sig
4
+
3
5
  def stubs(ignore_block: false, ignore_extra_args: false, ignore_arity: false, times: nil, &demo)
4
6
  RegistersStubbing.new.register(demo, DemoConfig.new(
5
7
  ignore_block: ignore_block,
@@ -12,4 +12,6 @@ module Mocktail
12
12
  class InvalidMatcherError < Error; end
13
13
 
14
14
  class VerificationError < Error; end
15
+
16
+ class TypeCheckingError < Error; end
15
17
  end
@@ -3,6 +3,8 @@ require_relative "share/stringifies_call"
3
3
 
4
4
  module Mocktail
5
5
  class ExplainsNils
6
+ extend T::Sig
7
+
6
8
  def initialize
7
9
  @stringifies_method_name = StringifiesMethodName.new
8
10
  @stringifies_call = StringifiesCall.new
@@ -3,6 +3,8 @@ require_relative "share/stringifies_call"
3
3
 
4
4
  module Mocktail
5
5
  class ExplainsThing
6
+ extend T::Sig
7
+
6
8
  def initialize
7
9
  @stringifies_method_name = StringifiesMethodName.new
8
10
  @stringifies_call = StringifiesCall.new
@@ -11,7 +13,8 @@ module Mocktail
11
13
  def explain(thing)
12
14
  if (double = Mocktail.cabinet.double_for_instance(thing))
13
15
  double_explanation(double)
14
- elsif (type_replacement = TopShelf.instance.type_replacement_if_exists_for(thing))
16
+ elsif (thing.is_a?(Module) || thing.is_a?(Class)) &&
17
+ (type_replacement = TopShelf.instance.type_replacement_if_exists_for(thing))
15
18
  replaced_type_explanation(type_replacement)
16
19
  elsif (fake_method_explanation = fake_method_explanation_for(thing))
17
20
  fake_method_explanation
@@ -67,7 +70,7 @@ module Mocktail
67
70
  def data_for_type_replacement(type_replacement)
68
71
  TypeReplacementData.new(
69
72
  type: type_replacement.type,
70
- replaced_method_names: type_replacement.replacement_methods.map(&:name).sort,
73
+ replaced_method_names: type_replacement.replacement_methods&.map(&:name)&.sort || [],
71
74
  calls: Mocktail.cabinet.calls.select { |call|
72
75
  call.double == type_replacement.type
73
76
  },
@@ -81,7 +84,7 @@ module Mocktail
81
84
  type_replacement_data = data_for_type_replacement(type_replacement)
82
85
 
83
86
  ReplacedTypeExplanation.new(type_replacement_data, <<~MSG)
84
- `#{type_replacement.type}' is a #{type_replacement.type.class.to_s.downcase} that has had its singleton methods faked.
87
+ `#{type_replacement.type}' is a #{type_replacement.type.class.to_s.downcase} that has had its methods faked.
85
88
 
86
89
  It has these mocked methods:
87
90
  #{type_replacement_data.replaced_method_names.map { |method| " - #{method}" }.join("\n")}
@@ -116,7 +119,7 @@ module Mocktail
116
119
  end
117
120
 
118
121
  def no_explanation(thing)
119
- NoExplanation.new(thing,
122
+ NoExplanation.new(NoExplanationData.new(thing: thing),
120
123
  "Unfortunately, Mocktail doesn't know what this thing is: #{thing.inspect}")
121
124
  end
122
125
  end
@@ -0,0 +1,30 @@
1
+ module Mocktail
2
+ class GrabsOriginalMethodParameters
3
+ extend T::Sig
4
+
5
+ # Sorbet wraps the original method in a sig wrapper, so we need to unwrap it.
6
+ # The value returned from `owner.instance_method(method_name)` does not have
7
+ # the real parameters values available, as they'll have been erased
8
+ #
9
+ # If the method isn't wrapped by Sorbet, this will return the #instance_method,
10
+ # per usual
11
+
12
+ def grab(method)
13
+ return [] unless method
14
+
15
+ if (wrapped_method = sorbet_wrapped_method(method))
16
+ wrapped_method.parameters
17
+ else
18
+ method.parameters
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def sorbet_wrapped_method(method)
25
+ return unless defined?(::T::Private::Methods)
26
+
27
+ T::Private::Methods.signature_for_method(method)
28
+ end
29
+ end
30
+ end
@@ -3,6 +3,8 @@ require_relative "../../share/bind"
3
3
 
4
4
  module Mocktail
5
5
  class DescribesUnsatisfiedStubbing
6
+ extend T::Sig
7
+
6
8
  def initialize
7
9
  @cleans_backtrace = CleansBacktrace.new
8
10
  end
@@ -14,7 +16,7 @@ module Mocktail
14
16
  Bind.call(dry_call.double, :==, stubbing.recording.double) &&
15
17
  dry_call.method == stubbing.recording.method
16
18
  },
17
- backtrace: @cleans_backtrace.clean(Error.new).backtrace
19
+ backtrace: @cleans_backtrace.clean(Error.new).backtrace || []
18
20
  )
19
21
  end
20
22
  end
@@ -2,14 +2,18 @@ require_relative "../../share/determines_matching_calls"
2
2
 
3
3
  module Mocktail
4
4
  class FindsSatisfaction
5
+ extend T::Sig
6
+
5
7
  def initialize
6
8
  @determines_matching_calls = DeterminesMatchingCalls.new
7
9
  end
8
10
 
9
11
  def find(dry_call)
10
12
  Mocktail.cabinet.stubbings.reverse.find { |stubbing|
13
+ demo_config_times = stubbing.demo_config.times
14
+
11
15
  @determines_matching_calls.determine(dry_call, stubbing.recording, stubbing.demo_config) &&
12
- (stubbing.demo_config.times.nil? || stubbing.demo_config.times > stubbing.satisfaction_count)
16
+ (demo_config_times.nil? || demo_config_times > stubbing.satisfaction_count)
13
17
  }
14
18
  end
15
19
  end
@@ -3,6 +3,8 @@ require_relative "fulfills_stubbing/describes_unsatisfied_stubbing"
3
3
 
4
4
  module Mocktail
5
5
  class FulfillsStubbing
6
+ extend T::Sig
7
+
6
8
  def initialize
7
9
  @finds_satisfaction = FindsSatisfaction.new
8
10
  @describes_unsatisfied_stubbing = DescribesUnsatisfiedStubbing.new
@@ -1,5 +1,7 @@
1
1
  module Mocktail
2
2
  class LogsCall
3
+ extend T::Sig
4
+
3
5
  def log(dry_call)
4
6
  Mocktail.cabinet.store_call(dry_call)
5
7
  end
@@ -1,5 +1,7 @@
1
1
  module Mocktail
2
2
  class ValidatesArguments
3
+ extend T::Sig
4
+
3
5
  def self.disable!
4
6
  Thread.current[:mocktail_arity_validation_disabled] = true
5
7
  end
@@ -9,16 +11,16 @@ module Mocktail
9
11
  end
10
12
 
11
13
  def self.disabled?
12
- Thread.current[:mocktail_arity_validation_disabled]
14
+ !!Thread.current[:mocktail_arity_validation_disabled]
13
15
  end
14
16
 
15
17
  def self.optional(disable, &blk)
16
18
  return blk.call unless disable
17
19
 
18
20
  disable!
19
- blk.call.tap do
20
- enable!
21
- end
21
+ ret = blk.call
22
+ enable!
23
+ ret
22
24
  end
23
25
 
24
26
  def initialize
@@ -4,6 +4,8 @@ require_relative "handles_dry_call/validates_arguments"
4
4
 
5
5
  module Mocktail
6
6
  class HandlesDryCall
7
+ extend T::Sig
8
+
7
9
  def initialize
8
10
  @validates_arguments = ValidatesArguments.new
9
11
  @logs_call = LogsCall.new
@@ -1,5 +1,7 @@
1
1
  module Mocktail
2
2
  class HandlesDryNewCall
3
+ extend T::Sig
4
+
3
5
  def initialize
4
6
  @validates_arguments = ValidatesArguments.new
5
7
  @logs_call = LogsCall.new
@@ -1,5 +1,7 @@
1
1
  module Mocktail
2
2
  class EnsuresImitationSupport
3
+ extend T::Sig
4
+
3
5
  def ensure(type)
4
6
  unless type.is_a?(Class) || type.is_a?(Module)
5
7
  raise UnsupportedMocktail.new <<~MSG.tr("\n", " ")
@@ -1,5 +1,7 @@
1
1
  module Mocktail
2
2
  class ReconstructsCall
3
+ extend T::Sig
4
+
3
5
  def reconstruct(double:, call_binding:, default_args:, dry_class:, type:, method:, original_method:, signature:)
4
6
  Call.new(
5
7
  singleton: false,
@@ -37,7 +39,8 @@ module Mocktail
37
39
  def non_default_args(params, default_args)
38
40
  named_args = params.allowed
39
41
  .reject { |p| default_args&.key?(p) }
40
- rest_arg = if params.rest && !default_args&.key?(params.rest)
42
+ rest_param = params.rest
43
+ rest_arg = if rest_param && !default_args&.key?(rest_param)
41
44
  params.rest
42
45
  end
43
46
 
@@ -2,60 +2,72 @@ require_relative "declares_dry_class/reconstructs_call"
2
2
 
3
3
  module Mocktail
4
4
  class DeclaresDryClass
5
+ extend T::Sig
6
+
7
+ DEFAULT_ANCESTORS = Class.new(Object).ancestors[1..]
8
+
5
9
  def initialize
6
10
  @raises_neato_no_method_error = RaisesNeatoNoMethodError.new
7
11
  @transforms_params = TransformsParams.new
8
12
  @stringifies_method_signature = StringifiesMethodSignature.new
13
+ @grabs_original_method_parameters = GrabsOriginalMethodParameters.new
9
14
  end
10
15
 
11
16
  def declare(type, instance_methods)
12
17
  dry_class = Class.new(Object) {
13
- include type if type.instance_of?(Module)
18
+ include type if type.is_a?(Module) && !type.is_a?(Class)
14
19
 
15
- def initialize(*args, **kwargs, &blk)
20
+ define_method :initialize do |*args, **kwargs, &blk|
16
21
  end
17
22
 
18
- define_method :is_a?, ->(thing) {
19
- type.ancestors.include?(thing)
20
- }
21
- alias_method :kind_of?, :is_a?
23
+ [:is_a?, :kind_of?].each do |method_name|
24
+ define_method method_name, ->(thing) {
25
+ # Mocktails extend from Object, so share the same ancestors, plus the passed type
26
+ [type, *DEFAULT_ANCESTORS].include?(thing)
27
+ }
28
+ end
22
29
 
23
- if type.instance_of?(Class)
30
+ if type.is_a?(Class)
24
31
  define_method :instance_of?, ->(thing) {
25
32
  type == thing
26
33
  }
27
34
  end
28
35
  }
29
36
 
30
- # These have special implementations, but if the user defines
31
- # any of them on the object itself, then they'll be replaced with normal
32
- # mocked methods. YMMV
37
+ add_more_methods!(dry_class, type, instance_methods)
38
+
39
+ dry_class # This is all fake! That's the whole point—it's not a real Foo, it's just some new class that quacks like a Foo
40
+ end
41
+
42
+ private
43
+
44
+ # These have special implementations, but if the user defines
45
+ # any of them on the object itself, then they'll be replaced with normal
46
+ # mocked methods. YMMV
47
+
48
+ def add_more_methods!(dry_class, type, instance_methods)
33
49
  add_stringify_methods!(dry_class, :to_s, type, instance_methods)
34
50
  add_stringify_methods!(dry_class, :inspect, type, instance_methods)
35
51
  define_method_missing_errors!(dry_class, type, instance_methods)
36
52
 
37
53
  define_double_methods!(dry_class, type, instance_methods)
38
-
39
- dry_class
40
54
  end
41
55
 
42
- private
43
-
44
56
  def define_double_methods!(dry_class, type, instance_methods)
45
- instance_methods.each do |method|
46
- dry_class.undef_method(method) if dry_class.method_defined?(method)
47
- parameters = type.instance_method(method).parameters
57
+ instance_methods.each do |method_name|
58
+ dry_class.undef_method(method_name) if dry_class.method_defined?(method_name)
59
+ parameters = @grabs_original_method_parameters.grab(type.instance_method(method_name))
48
60
  signature = @transforms_params.transform(Call.new, params: parameters)
49
61
  method_signature = @stringifies_method_signature.stringify(signature)
50
62
  __mocktail_closure = {
51
63
  dry_class: dry_class,
52
64
  type: type,
53
- method: method,
54
- original_method: type.instance_method(method),
65
+ method: method_name,
66
+ original_method: type.instance_method(method_name),
55
67
  signature: signature
56
68
  }
57
69
 
58
- dry_class.define_method method,
70
+ dry_class.define_method method_name,
59
71
  eval(<<-RUBBY, binding, __FILE__, __LINE__ + 1) # standard:disable Security/Eval
60
72
  ->#{method_signature} do
61
73
  ::Mocktail::Debug.guard_against_mocktail_accidentally_calling_mocks_if_debugging!
@@ -1,5 +1,7 @@
1
1
  module Mocktail
2
2
  class GathersFakeableInstanceMethods
3
+ extend T::Sig
4
+
3
5
  def gather(type)
4
6
  methods = type.instance_methods + [
5
7
  (:respond_to_missing? if type.private_method_defined?(:respond_to_missing?))
@@ -3,6 +3,8 @@ require_relative "makes_double/gathers_fakeable_instance_methods"
3
3
 
4
4
  module Mocktail
5
5
  class MakesDouble
6
+ extend T::Sig
7
+
6
8
  def initialize
7
9
  @declares_dry_class = DeclaresDryClass.new
8
10
  @gathers_fakeable_instance_methods = GathersFakeableInstanceMethods.new
@@ -11,6 +13,7 @@ module Mocktail
11
13
  def make(type)
12
14
  dry_methods = @gathers_fakeable_instance_methods.gather(type)
13
15
  dry_type = @declares_dry_class.declare(type, dry_methods)
16
+
14
17
  Double.new(
15
18
  original_type: type,
16
19
  dry_type: dry_type,
@@ -3,8 +3,10 @@ require_relative "imitates_type/makes_double"
3
3
 
4
4
  module Mocktail
5
5
  class ImitatesType
6
+ extend T::Sig
7
+ extend T::Generic
8
+
6
9
  def initialize
7
- @top_shelf = TopShelf.instance
8
10
  @ensures_imitation_support = EnsuresImitationSupport.new
9
11
  @makes_double = MakesDouble.new
10
12
  end
@@ -0,0 +1,9 @@
1
+ require_relative "typed"
2
+
3
+ # Constant boolean, so won't statically type-check, but `T.unsafe` can't be used
4
+ # because we haven't required sorbet-runtime yet
5
+ if eval("Mocktail::TYPED", binding, __FILE__, __LINE__)
6
+ require "sorbet-runtime"
7
+ else
8
+ require "#{Gem.loaded_specs["sorbet-eraser"].gem_dir}/lib/t"
9
+ end
@@ -1,9 +1,14 @@
1
1
  module Mocktail
2
2
  class InitializesMocktail
3
+ extend T::Sig
4
+
3
5
  def init
4
6
  [
5
7
  Mocktail::Matchers::Any,
6
8
  Mocktail::Matchers::Includes,
9
+ Mocktail::Matchers::IncludesString,
10
+ Mocktail::Matchers::IncludesKey,
11
+ Mocktail::Matchers::IncludesHash,
7
12
  Mocktail::Matchers::IsA,
8
13
  Mocktail::Matchers::Matches,
9
14
  Mocktail::Matchers::Not,
@@ -1,12 +1,14 @@
1
1
  module Mocktail
2
2
  class MatcherPresentation
3
+ extend T::Sig
4
+
3
5
  def respond_to_missing?(name, include_private = false)
4
6
  !!MatcherRegistry.instance.get(name) || super
5
7
  end
6
8
 
7
- def method_missing(name, *args, **kwargs, &blk)
9
+ def method_missing(name, *args, **kwargs, &blk) # standard:disable Style/ArgumentsForwarding
8
10
  if (matcher = MatcherRegistry.instance.get(name))
9
- matcher.new(*args, **kwargs, &blk)
11
+ matcher.new(*args, **kwargs, &blk) # standard:disable Style/ArgumentsForwarding
10
12
  else
11
13
  super
12
14
  end
@@ -1,12 +1,13 @@
1
1
  module Mocktail::Matchers
2
2
  class Any < Base
3
+ extend T::Sig
4
+
3
5
  def self.matcher_name
4
6
  :any
5
7
  end
6
8
 
7
- # Change this comment to a descriptive one once this is merged:
8
- # https://github.com/rubocop/rubocop/pull/10551
9
- def initialize # standard:disable Style/RedundantInitialize
9
+ def initialize
10
+ # Empty initialize is necessary b/c Base default expects an argument
10
11
  end
11
12
 
12
13
  def match?(actual)
@@ -1,17 +1,25 @@
1
1
  module Mocktail::Matchers
2
2
  class Base
3
+ extend T::Sig
4
+ extend T::Helpers
5
+
6
+ if Mocktail::TYPED && T::Private::RuntimeLevels.default_checked_level != :never
7
+
8
+ end
9
+
3
10
  # Custom matchers can receive any args, kwargs, or block they want. Usually
4
11
  # single-argument, though, so that's defaulted here and in #insepct
12
+
5
13
  def initialize(expected)
6
14
  @expected = expected
7
15
  end
8
16
 
9
17
  def self.matcher_name
10
- raise Mocktail::Error.new("The `matcher_name` class method must return a valid method name")
18
+ raise Mocktail::InvalidMatcherError.new("The `matcher_name` class method must return a valid method name")
11
19
  end
12
20
 
13
21
  def match?(actual)
14
- raise Mocktail::Error.new("Matchers must implement `match?(argument)`")
22
+ raise Mocktail::InvalidMatcherError.new("Matchers must implement `match?(argument)`")
15
23
  end
16
24
 
17
25
  def inspect
@@ -13,7 +13,11 @@ module Mocktail::Matchers
13
13
  # See Mockito, which is the earliest implementation I know of:
14
14
  # https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Captor.html
15
15
  class Captor
16
+ extend T::Sig
17
+
16
18
  class Capture < Mocktail::Matchers::Base
19
+ extend T::Sig
20
+
17
21
  def self.matcher_name
18
22
  :capture
19
23
  end
@@ -40,7 +44,12 @@ module Mocktail::Matchers
40
44
  end
41
45
  end
42
46
 
47
+ # This T.untyped is intentional. Even though a Capture is surely returned,
48
+ # in order for a verification demonstration to pass its own type check,
49
+ # it needs to think it's being returned whatever parameter is expected
50
+
43
51
  attr_reader :capture
52
+
44
53
  def initialize
45
54
  @capture = Capture.new
46
55
  end
@@ -1,5 +1,7 @@
1
1
  module Mocktail::Matchers
2
2
  class Includes < Base
3
+ extend T::Sig
4
+
3
5
  def self.matcher_name
4
6
  :includes
5
7
  end
@@ -0,0 +1,9 @@
1
+ module Mocktail::Matchers
2
+ class IncludesHash < Includes
3
+ extend T::Sig
4
+
5
+ def self.matcher_name
6
+ :includes_hash
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Mocktail::Matchers
2
+ class IncludesKey < Includes
3
+ extend T::Sig
4
+
5
+ def self.matcher_name
6
+ :includes_key
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Mocktail::Matchers
2
+ class IncludesString < Includes
3
+ extend T::Sig
4
+
5
+ def self.matcher_name
6
+ :includes_string
7
+ end
8
+ end
9
+ end
@@ -1,5 +1,7 @@
1
1
  module Mocktail::Matchers
2
2
  class IsA < Base
3
+ extend T::Sig
4
+
3
5
  def self.matcher_name
4
6
  :is_a
5
7
  end
@@ -1,5 +1,7 @@
1
1
  module Mocktail::Matchers
2
2
  class Matches < Base
3
+ extend T::Sig
4
+
3
5
  def self.matcher_name
4
6
  :matches
5
7
  end