mocktail 0.0.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67768c396af3e99d7ef7649d2afecf023f1dbdc0e361d960bebbf6befe908d3b
4
- data.tar.gz: 196930b31afa82713bf68469c6d3cb434ce67f22f6cc4c7393c7c438201550c9
3
+ metadata.gz: 05ec28e8027b6d73ee5b3f513ce4d5e23a60ea0266643e344e20e8f995410ce2
4
+ data.tar.gz: 7baf1cff682de031f3f25cebad265e59e598c4f4103968ea2dda61b6c7605c9c
5
5
  SHA512:
6
- metadata.gz: e8b1974f7c8068044c95b4113937aa5cd39972722244ff81bd60038cfe56ff2148a87c8b89bf5487e5b195fa25df1e23f31f667c618e2c375f27615e487d77f7
7
- data.tar.gz: b0cf6ae9e2e1b7b2ce0d23144753d7dde66af999c110da1ac8815b2a95b4bdd6ad0ac1e7555fd4a1333bbc983811fd5fa32a052ea22bda01395a24dfb96eb6da
6
+ metadata.gz: 4c5eef4fe5c68abd7d1e506bc59db58daa931fb484118e0ee4d6d286944d2834d0bb3107183102ac65110e12f32992b73fadbfcc26ed750bff060535e4eba5a2
7
+ data.tar.gz: cff11562ac6bc79475719f17f912ffc2fe8af6315ab90dbdc838143c237542ad788cba2d6c05ee15417799b2f597634244edf3c9f50dac5121adbac551602bdb
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ # 1.0.0
2
+
3
+ * First breaking change! 🎉
4
+ * Remove support for `Mocktail.explain(nil)` because fake nil values cannot be
5
+ made falsey. Pretty big mistake
6
+ * Add `Mocktail.explain_nils` which will return explanation objects of every
7
+ call that didn't satisfy a stubbing since the last reset, including the call
8
+ site where it happened and the backtrace to try to tease out which one you're
9
+ looking for
10
+
1
11
  # 0.0.6
2
12
 
3
13
  * Require pathname, which I missed because `bundle exec` loads it. Wups!
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mocktail (0.0.6)
4
+ mocktail (1.0.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -10,29 +10,29 @@ GEM
10
10
  coderay (1.1.3)
11
11
  docile (1.4.0)
12
12
  method_source (1.0.0)
13
- minitest (5.14.4)
13
+ minitest (5.15.0)
14
14
  parallel (1.21.0)
15
- parser (3.0.2.0)
15
+ parser (3.0.3.2)
16
16
  ast (~> 2.4.1)
17
17
  pry (0.14.1)
18
18
  coderay (~> 1.1)
19
19
  method_source (~> 1.0)
20
20
  rainbow (3.0.0)
21
21
  rake (13.0.6)
22
- regexp_parser (2.1.1)
22
+ regexp_parser (2.2.0)
23
23
  rexml (3.2.5)
24
- rubocop (1.20.0)
24
+ rubocop (1.23.0)
25
25
  parallel (~> 1.10)
26
26
  parser (>= 3.0.0.0)
27
27
  rainbow (>= 2.2.2, < 4.0)
28
28
  regexp_parser (>= 1.8, < 3.0)
29
29
  rexml
30
- rubocop-ast (>= 1.9.1, < 2.0)
30
+ rubocop-ast (>= 1.12.0, < 2.0)
31
31
  ruby-progressbar (~> 1.7)
32
32
  unicode-display_width (>= 1.4.0, < 3.0)
33
- rubocop-ast (1.11.0)
33
+ rubocop-ast (1.15.0)
34
34
  parser (>= 3.0.1.1)
35
- rubocop-performance (1.11.5)
35
+ rubocop-performance (1.12.0)
36
36
  rubocop (>= 1.7.0, < 2.0)
37
37
  rubocop-ast (>= 0.4.0)
38
38
  ruby-progressbar (1.11.0)
@@ -42,9 +42,9 @@ GEM
42
42
  simplecov_json_formatter (~> 0.1)
43
43
  simplecov-html (0.12.3)
44
44
  simplecov_json_formatter (0.1.3)
45
- standard (1.3.0)
46
- rubocop (= 1.20.0)
47
- rubocop-performance (= 1.11.5)
45
+ standard (1.5.0)
46
+ rubocop (= 1.23.0)
47
+ rubocop-performance (= 1.12.0)
48
48
  unicode-display_width (2.1.0)
49
49
 
50
50
  PLATFORMS
data/README.md CHANGED
@@ -580,12 +580,27 @@ ice_tray.fill(:water_type, 30)
580
580
  ```
581
581
 
582
582
  From there, you can just copy-paste the provided method stub as a starting point
583
- for your new method.
583
+ for your new method:
584
584
 
585
- #### `nil` values returned by faked methods
585
+ ```ruby
586
+ class IceTray
587
+ def fill(water_type, amount)
588
+ end
589
+ end
590
+ ```
591
+
592
+ ### Unexpected nils with Mocktail.explain_nils
593
+
594
+ Is a faked method returning `nil` and you don't understand why?
595
+
596
+ By default, methods faked by Mocktail will return `nil` when no stubbing is
597
+ satisfied. A frequent frustration, therefore, is when the way `stubs {}.with {}`
598
+ is configured does not satisfy a call the way you expected. To try to make
599
+ debugging this a little bit easier, the gem provides a top-level
600
+ `Mocktail.explain_nils` method that will return an array of summaries of every
601
+ call to a faked method that failed to satisfy any stubbings.
586
602
 
587
- Suppose you go ahead and implement the `fill` method above and configure a
588
- stubbing:
603
+ For example, suppose you stub this `fill` method like so:
589
604
 
590
605
  ```ruby
591
606
  ice_tray = Mocktail.of(IceTray)
@@ -598,20 +613,22 @@ you don't understand why:
598
613
 
599
614
  ```ruby
600
615
  def prep
601
- ice = ice_tray.fill(:tap_water, 50) # => nil
602
- glass.add(ice)
616
+ ice = ice_tray.fill(:tap_water, 50)
617
+ glass.add(ice) # => why is `ice` nil?!
603
618
  end
604
619
  ```
605
620
 
606
- You can pass that `nil` value to `Mocktail.explain` and get an
607
- `UnsatisfiedStubExplanation` that will include both a `reference` object to explore
608
- as well a summary message:
621
+ Whenever you're confused by a nil, you can call `Mocktail.explain_nils` for an
622
+ array containing `UnsatisfyingCallExplanation` objects (one for each call to
623
+ a faked method that did not satisfy any configured stubbings).
624
+
625
+ The returned explanation objects will include both a `reference` object to
626
+ explore as well a summary `message`:
609
627
 
610
628
  ```ruby
611
629
  def prep
612
- ice = ice_tray.fill(:tap_water, 50).tap do |wat|
613
- puts Mocktail.explain(wat).message
614
- end
630
+ ice = ice_tray.fill(:tap_water, 50)
631
+ puts Mocktail.explain_nils.first.message
615
632
  glass.add(ice)
616
633
  end
617
634
  ```
@@ -626,16 +643,24 @@ The actual call:
626
643
 
627
644
  fill(:tap_water, 50)
628
645
 
646
+ The call site:
647
+
648
+ /path/to/your/code.rb:42:in `prep'
649
+
629
650
  Stubbings configured prior to this call but not satisfied by it:
630
651
 
631
652
  fill(:tap_water, 30)
632
653
  ```
633
654
 
655
+ The `reference` object will have details of the `call` itself, an array of
656
+ `other_stubbings` defined on the faked method, and a `backtrace` to determine
657
+ which call site produced the unexpected `nil` value.
658
+
634
659
  #### Fake instances created by Mocktail
635
660
 
636
- Any instances created by `Mocktail.of` or `Mocktail.of_next` can also be passed
637
- to `Mocktail.explain`, and they will list out all the calls and stubbings made
638
- for each of their fake methods.
661
+ Any instances created by `Mocktail.of` or `Mocktail.of_next` can be passed to
662
+ `Mocktail.explain`, and they will list out all the calls and stubbings made for
663
+ each of their fake methods.
639
664
 
640
665
  Calling `Mocktail.explain(ice_tray).message` following the example above will
641
666
  yield:
@@ -662,6 +687,8 @@ passed to `Mocktail.explain()` for a summary of all the stubbing configurations
662
687
  and calls made against its faked singleton methods for the currently running
663
688
  thread.
664
689
 
690
+ Imagine a `Shop` class with `self.open!` and `self.close!` singleton methods:
691
+
665
692
  ```ruby
666
693
  Mocktail.replace(Shop)
667
694
 
@@ -705,6 +732,10 @@ but you _definitely_ want to call it if you're using `Mocktail.replace` or
705
732
  `Mocktail.of_next` anywhere, since those will affect state that is shared across
706
733
  tests.
707
734
 
735
+ Calling reset in a `teardown` or `after(:each)` hook will also improve the
736
+ usefulness of messages returned by `Mocktail.explain` and
737
+ `Mocktail.explain_nils`.
738
+
708
739
  ## Acknowledgements
709
740
 
710
741
  Mocktail is created & maintained by the software agency [Test
@@ -0,0 +1,35 @@
1
+ require_relative "share/stringifies_method_name"
2
+ require_relative "share/stringifies_call"
3
+
4
+ module Mocktail
5
+ class ExplainsNils
6
+ def initialize
7
+ @stringifies_method_name = StringifiesMethodName.new
8
+ @stringifies_call = StringifiesCall.new
9
+ end
10
+
11
+ def explain
12
+ Mocktail.cabinet.unsatisfying_calls.map { |unsatisfying_call|
13
+ dry_call = unsatisfying_call.call
14
+ other_stubbings = unsatisfying_call.other_stubbings
15
+
16
+ UnsatisfyingCallExplanation.new(unsatisfying_call, <<~MSG)
17
+ `nil' was returned by a mocked `#{@stringifies_method_name.stringify(dry_call)}' method
18
+ because none of its configured stubbings were satisfied.
19
+
20
+ The actual call:
21
+
22
+ #{@stringifies_call.stringify(dry_call, always_parens: true)}
23
+
24
+ The call site:
25
+
26
+ #{unsatisfying_call.backtrace.first}
27
+
28
+ #{@stringifies_call.stringify_multiple(other_stubbings.map(&:recording),
29
+ nonzero_message: "Stubbings configured prior to this call but not satisfied by it",
30
+ zero_message: "No stubbings were configured on this method")}
31
+ MSG
32
+ }
33
+ end
34
+ end
35
+ end
@@ -9,9 +9,7 @@ module Mocktail
9
9
  end
10
10
 
11
11
  def explain(thing)
12
- if is_stub_returned_nil?(thing)
13
- unsatisfied_stub_explanation(thing)
14
- elsif (double = Mocktail.cabinet.double_for_instance(thing))
12
+ if (double = Mocktail.cabinet.double_for_instance(thing))
15
13
  double_explanation(double)
16
14
  elsif (type_replacement = TopShelf.instance.type_replacement_if_exists_for(thing))
17
15
  replaced_type_explanation(type_replacement)
@@ -22,31 +20,6 @@ module Mocktail
22
20
 
23
21
  private
24
22
 
25
- # Our fake nil doesn't even implement respond_to?, instead quacking like nil
26
- def is_stub_returned_nil?(thing)
27
- thing.was_returned_by_unsatisfied_stub?
28
- rescue NoMethodError
29
- end
30
-
31
- def unsatisfied_stub_explanation(stub_returned_nil)
32
- unsatisfied_stubbing = stub_returned_nil.unsatisfied_stubbing
33
- dry_call = unsatisfied_stubbing.call
34
- other_stubbings = unsatisfied_stubbing.other_stubbings
35
-
36
- UnsatisfiedStubExplanation.new(unsatisfied_stubbing, <<~MSG)
37
- This `nil' was returned by a mocked `#{@stringifies_method_name.stringify(dry_call)}' method
38
- because none of its configured stubbings were satisfied.
39
-
40
- The actual call:
41
-
42
- #{@stringifies_call.stringify(dry_call, always_parens: true)}
43
-
44
- #{describe_multiple_calls(other_stubbings.map(&:recording),
45
- "Stubbings configured prior to this call but not satisfied by it",
46
- "No stubbings were configured on this method")}
47
- MSG
48
- end
49
-
50
23
  def double_explanation(double)
51
24
  double_data = DoubleData.new(
52
25
  type: double.original_type,
@@ -95,35 +68,23 @@ module Mocktail
95
68
  ))
96
69
 
97
70
  [
98
- describe_multiple_calls(
71
+ @stringifies_call.stringify_multiple(
99
72
  double_data.stubbings.map(&:recording).select { |call|
100
73
  call.method == method
101
74
  },
102
- "`#{method_name}' stubbings",
103
- "`#{method_name}' has no stubbings"
75
+ nonzero_message: "`#{method_name}' stubbings",
76
+ zero_message: "`#{method_name}' has no stubbings"
104
77
  ),
105
- describe_multiple_calls(
78
+ @stringifies_call.stringify_multiple(
106
79
  double_data.calls.select { |call|
107
80
  call.method == method
108
81
  },
109
- "`#{method_name}' calls",
110
- "`#{method_name}' has no calls"
82
+ nonzero_message: "`#{method_name}' calls",
83
+ zero_message: "`#{method_name}' has no calls"
111
84
  )
112
85
  ].join("\n")
113
86
  end
114
87
 
115
- def describe_multiple_calls(calls, nonzero_message, zero_message)
116
- if calls.empty?
117
- "#{zero_message}.\n"
118
- else
119
- <<~MSG
120
- #{nonzero_message}:
121
-
122
- #{calls.map { |call| " " + @stringifies_call.stringify(call) }.join("\n\n")}
123
- MSG
124
- end
125
- end
126
-
127
88
  def no_explanation(thing)
128
89
  NoExplanation.new(thing,
129
90
  "Unfortunately, Mocktail doesn't know what this thing is: #{thing.inspect}")
@@ -1,12 +1,19 @@
1
+ require_relative "../../share/cleans_backtrace"
2
+
1
3
  module Mocktail
2
4
  class DescribesUnsatisfiedStubbing
5
+ def initialize
6
+ @cleans_backtrace = CleansBacktrace.new
7
+ end
8
+
3
9
  def describe(dry_call)
4
- UnsatisfiedStubbing.new(
10
+ UnsatisfyingCall.new(
5
11
  call: dry_call,
6
12
  other_stubbings: Mocktail.cabinet.stubbings.select { |stubbing|
7
- dry_call.double == stubbing.recording.double &&
8
- dry_call.method == stubbing.recording.method
9
- }
13
+ dry_call.double == stubbing.recording.double &&
14
+ dry_call.method == stubbing.recording.method
15
+ },
16
+ backtrace: @cleans_backtrace.clean(Error.new).backtrace
10
17
  )
11
18
  end
12
19
  end
@@ -13,13 +13,25 @@ module Mocktail
13
13
  stubbing.satisfied!
14
14
  stubbing.effect&.call(dry_call)
15
15
  else
16
- StubReturnedNil.new(@describes_unsatisfied_stubbing.describe(dry_call))
16
+ store_unsatisfying_call!(dry_call)
17
+ nil
17
18
  end
18
19
  end
19
20
 
20
21
  def satisfaction(dry_call)
21
22
  return if Mocktail.cabinet.demonstration_in_progress?
23
+
22
24
  @finds_satisfaction.find(dry_call)
23
25
  end
26
+
27
+ private
28
+
29
+ def store_unsatisfying_call!(dry_call)
30
+ return if Mocktail.cabinet.demonstration_in_progress?
31
+
32
+ Mocktail.cabinet.store_unsatisfying_call(
33
+ @describes_unsatisfied_stubbing.describe(dry_call)
34
+ )
35
+ end
24
36
  end
25
37
  end
@@ -34,6 +34,10 @@ module Mocktail::Matchers
34
34
  def captured?
35
35
  @captured
36
36
  end
37
+
38
+ def inspect
39
+ "capture"
40
+ end
37
41
  end
38
42
 
39
43
  attr_reader :capture
@@ -6,7 +6,7 @@ module Mocktail::Matchers
6
6
 
7
7
  def initialize(&blk)
8
8
  if blk.nil?
9
- raise "The `that` matcher must be passed a block (e.g. `that { |arg| … }`)"
9
+ raise ArgumentError.new("The `that` matcher must be passed a block (e.g. `that { |arg| … }`)")
10
10
  end
11
11
  @blk = blk
12
12
  end
@@ -4,6 +4,20 @@ module Mocktail
4
4
  "#{call.method}#{args_to_s(call, parens: always_parens)}#{blockify(call.block, anonymous: anonymous_blocks)}"
5
5
  end
6
6
 
7
+ def stringify_multiple(calls, nonzero_message:, zero_message:,
8
+ anonymous_blocks: false, always_parens: false)
9
+
10
+ if calls.empty?
11
+ "#{zero_message}.\n"
12
+ else
13
+ <<~MSG
14
+ #{nonzero_message}:
15
+
16
+ #{calls.map { |call| " " + stringify(call) }.join("\n\n")}
17
+ MSG
18
+ end
19
+ end
20
+
7
21
  private
8
22
 
9
23
  def args_to_s(call, parens: true)
@@ -1,7 +1,7 @@
1
1
  require_relative "simulates_argument_error/transforms_params"
2
2
  require_relative "simulates_argument_error/reconciles_args_with_params"
3
3
  require_relative "simulates_argument_error/recreates_message"
4
- require_relative "simulates_argument_error/cleans_backtrace"
4
+ require_relative "share/cleans_backtrace"
5
5
  require_relative "share/stringifies_call"
6
6
 
7
7
  module Mocktail
@@ -3,18 +3,20 @@
3
3
  module Mocktail
4
4
  class Cabinet
5
5
  attr_writer :demonstration_in_progress
6
- attr_reader :calls, :stubbings
6
+ attr_reader :calls, :stubbings, :unsatisfying_calls
7
7
 
8
8
  def initialize
9
9
  @doubles = []
10
10
  @calls = []
11
11
  @stubbings = []
12
+ @unsatisfying_calls = []
12
13
  @demonstration_in_progress = false
13
14
  end
14
15
 
15
16
  def reset!
16
17
  @calls = []
17
18
  @stubbings = []
19
+ @unsatisfying_calls = []
18
20
  # Could cause an exception or prevent pollution—you decide!
19
21
  @demonstration_in_progress = false
20
22
  # note we don't reset doubles as they don't carry any
@@ -34,6 +36,10 @@ module Mocktail
34
36
  @stubbings << stubbing
35
37
  end
36
38
 
39
+ def store_unsatisfying_call(unsatisfying_call)
40
+ @unsatisfying_calls << unsatisfying_call
41
+ end
42
+
37
43
  def demonstration_in_progress?
38
44
  @demonstration_in_progress
39
45
  end
@@ -15,7 +15,7 @@ module Mocktail
15
15
  class NoExplanation < Explanation
16
16
  end
17
17
 
18
- class UnsatisfiedStubExplanation < Explanation
18
+ class UnsatisfyingCallExplanation < Explanation
19
19
  end
20
20
 
21
21
  class DoubleExplanation < Explanation
@@ -1,7 +1,8 @@
1
1
  module Mocktail
2
- class UnsatisfiedStubbing < Struct.new(
2
+ class UnsatisfyingCall < Struct.new(
3
3
  :call,
4
4
  :other_stubbings,
5
+ :backtrace,
5
6
  keyword_init: true
6
7
  )
7
8
  end
@@ -7,8 +7,7 @@ require_relative "value/explanation"
7
7
  require_relative "value/matcher_registry"
8
8
  require_relative "value/signature"
9
9
  require_relative "value/stubbing"
10
- require_relative "value/stub_returned_nil"
11
10
  require_relative "value/top_shelf"
12
11
  require_relative "value/type_replacement"
13
12
  require_relative "value/type_replacement_data"
14
- require_relative "value/unsatisfied_stubbing"
13
+ require_relative "value/unsatisfying_call"
@@ -1,3 +1,3 @@
1
1
  module Mocktail
2
- VERSION = "0.0.6"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/mocktail.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require_relative "mocktail/dsl"
2
2
  require_relative "mocktail/errors"
3
3
  require_relative "mocktail/explains_thing"
4
+ require_relative "mocktail/explains_nils"
4
5
  require_relative "mocktail/handles_dry_call"
5
6
  require_relative "mocktail/handles_dry_new_call"
6
7
  require_relative "mocktail/imitates_type"
@@ -61,6 +62,10 @@ module Mocktail
61
62
  ExplainsThing.new.explain(thing)
62
63
  end
63
64
 
65
+ def self.explain_nils
66
+ ExplainsNils.new.explain
67
+ end
68
+
64
69
  # Stores most transactional state about calls & stubbing configurations
65
70
  # Anything returned by this is undocumented and could change at any time, so
66
71
  # don't commit code that relies on it!
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mocktail
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Searls
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-04 00:00:00.000000000 Z
11
+ date: 2021-12-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -31,6 +31,7 @@ files:
31
31
  - lib/mocktail.rb
32
32
  - lib/mocktail/dsl.rb
33
33
  - lib/mocktail/errors.rb
34
+ - lib/mocktail/explains_nils.rb
34
35
  - lib/mocktail/explains_thing.rb
35
36
  - lib/mocktail/handles_dry_call.rb
36
37
  - lib/mocktail/handles_dry_call/fulfills_stubbing.rb
@@ -65,12 +66,12 @@ files:
65
66
  - lib/mocktail/replaces_type/redefines_new.rb
66
67
  - lib/mocktail/replaces_type/redefines_singleton_methods.rb
67
68
  - lib/mocktail/resets_state.rb
69
+ - lib/mocktail/share/cleans_backtrace.rb
68
70
  - lib/mocktail/share/creates_identifier.rb
69
71
  - lib/mocktail/share/determines_matching_calls.rb
70
72
  - lib/mocktail/share/stringifies_call.rb
71
73
  - lib/mocktail/share/stringifies_method_name.rb
72
74
  - lib/mocktail/simulates_argument_error.rb
73
- - lib/mocktail/simulates_argument_error/cleans_backtrace.rb
74
75
  - lib/mocktail/simulates_argument_error/reconciles_args_with_params.rb
75
76
  - lib/mocktail/simulates_argument_error/recreates_message.rb
76
77
  - lib/mocktail/simulates_argument_error/transforms_params.rb
@@ -83,12 +84,11 @@ files:
83
84
  - lib/mocktail/value/explanation.rb
84
85
  - lib/mocktail/value/matcher_registry.rb
85
86
  - lib/mocktail/value/signature.rb
86
- - lib/mocktail/value/stub_returned_nil.rb
87
87
  - lib/mocktail/value/stubbing.rb
88
88
  - lib/mocktail/value/top_shelf.rb
89
89
  - lib/mocktail/value/type_replacement.rb
90
90
  - lib/mocktail/value/type_replacement_data.rb
91
- - lib/mocktail/value/unsatisfied_stubbing.rb
91
+ - lib/mocktail/value/unsatisfying_call.rb
92
92
  - lib/mocktail/verifies_call.rb
93
93
  - lib/mocktail/verifies_call/finds_verifiable_calls.rb
94
94
  - lib/mocktail/verifies_call/raises_verification_error.rb
@@ -1,26 +0,0 @@
1
- module Mocktail
2
- class StubReturnedNil < BasicObject
3
- attr_reader :unsatisfied_stubbing
4
-
5
- def initialize(unsatisfied_stubbing)
6
- @unsatisfied_stubbing = unsatisfied_stubbing
7
- end
8
-
9
- def was_returned_by_unsatisfied_stub?
10
- true
11
- end
12
-
13
- def tap
14
- yield self
15
- self
16
- end
17
-
18
- def method_missing(name, *args, **kwargs, &blk)
19
- nil.send(name, *args, **kwargs, &blk)
20
- end
21
-
22
- def respond_to_missing?(name, include_all = false)
23
- nil.respond_to?(name, include_all)
24
- end
25
- end
26
- end