mocktail 1.0.0 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 05ec28e8027b6d73ee5b3f513ce4d5e23a60ea0266643e344e20e8f995410ce2
4
- data.tar.gz: 7baf1cff682de031f3f25cebad265e59e598c4f4103968ea2dda61b6c7605c9c
3
+ metadata.gz: 6a9c74cd3ff5167b99c38892ee6411a1ac3f1379158c062076bfbe6d8479c7b7
4
+ data.tar.gz: 81dd2b80a78c162a9c4030fa8bc9be42741c3a8c053fa33c319dc13c404234a8
5
5
  SHA512:
6
- metadata.gz: 4c5eef4fe5c68abd7d1e506bc59db58daa931fb484118e0ee4d6d286944d2834d0bb3107183102ac65110e12f32992b73fadbfcc26ed750bff060535e4eba5a2
7
- data.tar.gz: cff11562ac6bc79475719f17f912ffc2fe8af6315ab90dbdc838143c237542ad788cba2d6c05ee15417799b2f597634244edf3c9f50dac5121adbac551602bdb
6
+ metadata.gz: bd1fbc482135e89dea00de9249d63b5e851d64ecb2f4afc898b4bb77abf5afc96303cfed6b59665d0e51ab4b2c8dd4c438dc2a7ab93d1c69e1d12d723797cf9a
7
+ data.tar.gz: 3af137dd6d1488b1ef41ac0b47178a224e160f11dce0ed0cbeb24418ae4a33c86a6e4fa8e3cabb20c8421c8ac1ac8c166045051d996d4aa1dc709e4fcc97ec3a
@@ -7,7 +7,7 @@ jobs:
7
7
  strategy:
8
8
  matrix:
9
9
  os: [ ubuntu-latest ]
10
- ruby-version: [3.0.1]
10
+ ruby-version: ['3.0', '3.1']
11
11
 
12
12
  runs-on: ${{ matrix.os }}
13
13
 
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 1.1.0
2
+
3
+ * Feature: add support for passing methods to `Mocktail.explain()`
4
+ * Fix 3.1 support by bypassing highlight_error for custom NoMethodError objects
5
+ raised by Mocktail [error_highlight#20](https://github.com/ruby/error_highlight/issues/20)
6
+
1
7
  # 1.0.0
2
8
 
3
9
  * First breaking change! 🎉
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mocktail (1.0.0)
4
+ mocktail (1.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -12,27 +12,27 @@ GEM
12
12
  method_source (1.0.0)
13
13
  minitest (5.15.0)
14
14
  parallel (1.21.0)
15
- parser (3.0.3.2)
15
+ parser (3.1.0.0)
16
16
  ast (~> 2.4.1)
17
17
  pry (0.14.1)
18
18
  coderay (~> 1.1)
19
19
  method_source (~> 1.0)
20
- rainbow (3.0.0)
20
+ rainbow (3.1.1)
21
21
  rake (13.0.6)
22
22
  regexp_parser (2.2.0)
23
23
  rexml (3.2.5)
24
- rubocop (1.23.0)
24
+ rubocop (1.25.0)
25
25
  parallel (~> 1.10)
26
- parser (>= 3.0.0.0)
26
+ parser (>= 3.1.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.12.0, < 2.0)
30
+ rubocop-ast (>= 1.15.1, < 2.0)
31
31
  ruby-progressbar (~> 1.7)
32
32
  unicode-display_width (>= 1.4.0, < 3.0)
33
- rubocop-ast (1.15.0)
33
+ rubocop-ast (1.15.1)
34
34
  parser (>= 3.0.1.1)
35
- rubocop-performance (1.12.0)
35
+ rubocop-performance (1.13.2)
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.5.0)
46
- rubocop (= 1.23.0)
47
- rubocop-performance (= 1.12.0)
45
+ standard (1.7.0)
46
+ rubocop (= 1.25.0)
47
+ rubocop-performance (= 1.13.2)
48
48
  unicode-display_width (2.1.0)
49
49
 
50
50
  PLATFORMS
@@ -60,4 +60,4 @@ DEPENDENCIES
60
60
  standard
61
61
 
62
62
  BUNDLED WITH
63
- 2.2.30
63
+ 2.3.6
data/README.md CHANGED
@@ -553,14 +553,160 @@ them!)]
553
553
  ### Mocktail.explain
554
554
 
555
555
  Test debugging is hard enough when there _aren't_ fake objects flying every
556
- which way, so Mocktail tries to make it a little easier by way of better
557
- messages throughout the library.
556
+ which way, so Mocktail tries to make it a little easier on you. In addition to
557
+ returning useful messages throughout the API, the gem also includes an
558
+ introspection method `Mocktail.explain(thing)`, which returns a human-readable
559
+ `message` and a `reference` object with useful attributes (that vary depending
560
+ on the type of fake `thing` you pass in. Below are some things `explain()` can
561
+ do.
562
+
563
+ #### Fake instances created by Mocktail
564
+
565
+ Any instances created by `Mocktail.of` or `Mocktail.of_next` can be passed to
566
+ `Mocktail.explain`, and they will list out all the calls and stubbings made for
567
+ each of their fake methods.
568
+
569
+ Suppose these interactions have occurred:
570
+
571
+ ```ruby
572
+ ice_tray = Mocktail.of(IceTray)
573
+
574
+ Mocktail.stubs { ice_tray.fill(:tap_water, 30) }.with { :some_ice }
575
+
576
+ ice_tray.fill(:tap_water, 50)
577
+ ```
578
+
579
+ You can interrogate what's going on with the fake instance by passing it to
580
+ `explain`:
581
+
582
+ ```ruby
583
+ explanation = Mocktail.explain(ice_tray)
584
+
585
+ explanation.reference.type #=> IceTray
586
+ explanation.reference.double #=> The ice_tray instance
587
+ explanation.reference.calls #=> details on each invocation of each method
588
+ explanation.reference.stubbings #=> all stubbings configured for each method
589
+ ```
590
+
591
+ Calling `explanation.message` will return:
592
+
593
+ ```
594
+ This is a fake `IceTray' instance.
595
+
596
+ It has these mocked methods:
597
+ - fill
598
+
599
+ `IceTray#fill' stubbings:
600
+
601
+ fill(:tap_water, 30)
602
+
603
+ `IceTray#fill' calls:
604
+
605
+ fill(:tap_water, 50)
606
+
607
+ ```
608
+
609
+ #### Modules and classes with singleton methods replaced
610
+
611
+ If you've called `Mocktail.replace()` on a class or module, it can also be
612
+ passed to `Mocktail.explain()` for a summary of all the stubbing configurations
613
+ and calls made against its faked singleton methods for the currently running
614
+ thread.
615
+
616
+ Imagine a `Shop` class with `self.open!` and `self.close!` singleton methods:
617
+
618
+ ```ruby
619
+ Mocktail.replace(Shop)
620
+
621
+ stubs { |m| Shop.open!(m.numeric) }.with { :a_bar }
622
+
623
+ Shop.open!(42)
624
+
625
+ Shop.close!(42)
626
+
627
+ explanation = Mocktail.explain(Shop)
628
+
629
+ explanation.reference.type #=> Shop
630
+ explanation.reference.replaced_method_names #=> [:close!, :open!]
631
+ explanation.reference.calls #=> details on each invocation of each method
632
+ explanation.reference.stubbings #=> all stubbings configured for each method
633
+ ```
634
+
635
+ And `explanation.message` will return:
636
+
637
+ ```ruby
638
+ `Shop' is a class that has had its singleton methods faked.
639
+
640
+ It has these mocked methods:
641
+ - close!
642
+ - open!
643
+
644
+ `Shop.close!' has no stubbings.
645
+
646
+ `Shop.close!' calls:
647
+
648
+ close!(42)
649
+
650
+ close!(42)
651
+
652
+ `Shop.open!' stubbings:
653
+
654
+ open!(numeric)
655
+
656
+ open!(numeric)
657
+
658
+ `Shop.open!' calls:
659
+
660
+ open!(42)
661
+
662
+ open!(42)
663
+ ```
664
+
665
+ #### Methods on faked instances and replaced types
666
+
667
+ In addition to passing the test double, you can also pass a reference to any
668
+ fake method created by Mocktail to `Mocktail.explain`:
669
+
670
+ ```ruby
671
+ ice_tray = Mocktail.of(IceTray)
672
+
673
+ ice_tray.fill(:chilled, 50)
674
+
675
+ explanation = Mocktail.explain(ice_tray.method(:fill))
676
+
677
+ explanation.reference.receiver #=> a reference to the `ice_tray` instance
678
+ explanation.reference.calls #=> details on each invocation of the method
679
+ explanation.reference.stubbings #=> all stubbings configured for the method
680
+ ```
681
+
682
+ The above may be handy in cases where you want to assert the number of calls of
683
+ a method outside the `Mocktail.verify` API:
684
+
685
+ ```ruby
686
+ assert_equal 1, explanation.reference.calls.size
687
+ ```
688
+
689
+ The explanation will also contain a `message` like this:
690
+
691
+ ```
692
+ `IceTray#fill' has no stubbings.
693
+
694
+ `IceTray#fill' calls:
695
+
696
+ fill(:chilled, 50)
697
+ ```
698
+
699
+ Replaced singleton methods can also be passed to `explain()`, so something like
700
+ `Mocktail.explain(Shop.method(:open!))` from the earlier example would also work
701
+ (with `Shop` being the `receiver` on the explanation's `reference`).
558
702
 
559
703
  #### Undefined methods
560
704
 
561
- One message you'll see automatically if you try to call a method
562
- that doesn't exist is this one, which gives a sample definition of the method
563
- you had attempted to call:
705
+ There's no API for this one, but Mocktail also offers explanations for methods
706
+ that don't exist yet. You'll see this error message whenever you try to call a
707
+ method that doesn't exist on a test double. The message is designed to
708
+ facilitate "paint-by-numbers" TDD, by including a sample definition of the
709
+ method you had attempted to call that can be copy-pasted into a source listing:
564
710
 
565
711
  ```ruby
566
712
  class IceTray
@@ -589,7 +735,7 @@ class IceTray
589
735
  end
590
736
  ```
591
737
 
592
- ### Unexpected nils with Mocktail.explain_nils
738
+ ### Mocktail.explain_nils
593
739
 
594
740
  Is a faked method returning `nil` and you don't understand why?
595
741
 
@@ -656,75 +802,6 @@ The `reference` object will have details of the `call` itself, an array of
656
802
  `other_stubbings` defined on the faked method, and a `backtrace` to determine
657
803
  which call site produced the unexpected `nil` value.
658
804
 
659
- #### Fake instances created by Mocktail
660
-
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.
664
-
665
- Calling `Mocktail.explain(ice_tray).message` following the example above will
666
- yield:
667
-
668
- ```
669
- This is a fake `IceTray' instance.
670
-
671
- It has these mocked methods:
672
- - fill
673
-
674
- `IceTray#fill' stubbings:
675
-
676
- fill(:tap_water, 30)
677
-
678
- `IceTray#fill' calls:
679
-
680
- fill(:tap_water, 50)
681
- ```
682
-
683
- #### Modules and classes with singleton methods replaced
684
-
685
- If you've called `Mocktail.replace()` on a class or module, it can also be
686
- passed to `Mocktail.explain()` for a summary of all the stubbing configurations
687
- and calls made against its faked singleton methods for the currently running
688
- thread.
689
-
690
- Imagine a `Shop` class with `self.open!` and `self.close!` singleton methods:
691
-
692
- ```ruby
693
- Mocktail.replace(Shop)
694
-
695
- stubs { |m| Shop.open!(m.numeric) }.with { :a_bar }
696
-
697
- Shop.open!(42)
698
-
699
- Shop.close!(42)
700
-
701
- puts Mocktail.explain(Shop).message
702
- ```
703
-
704
- Will print:
705
-
706
- ```ruby
707
- `Shop' is a class that has had its singleton methods faked.
708
-
709
- It has these mocked methods:
710
- - close!
711
- - open!
712
-
713
- `Shop.close!' has no stubbings.
714
-
715
- `Shop.close!' calls:
716
-
717
- close!(42)
718
-
719
- `Shop.open!' stubbings:
720
-
721
- open!(numeric)
722
-
723
- `Shop.open!' calls:
724
-
725
- open!(42)
726
- ```
727
-
728
805
  ### Mocktail.reset
729
806
 
730
807
  This one's simple: you probably want to call `Mocktail.reset` after each test,
@@ -13,6 +13,8 @@ module Mocktail
13
13
  double_explanation(double)
14
14
  elsif (type_replacement = TopShelf.instance.type_replacement_if_exists_for(thing))
15
15
  replaced_type_explanation(type_replacement)
16
+ elsif (fake_method_explanation = fake_method_explanation_for(thing))
17
+ fake_method_explanation
16
18
  else
17
19
  no_explanation(thing)
18
20
  end
@@ -20,13 +22,37 @@ module Mocktail
20
22
 
21
23
  private
22
24
 
23
- def double_explanation(double)
24
- double_data = DoubleData.new(
25
+ def fake_method_explanation_for(thing)
26
+ return unless thing.is_a?(Method)
27
+ method = thing
28
+ receiver = thing.receiver
29
+
30
+ receiver_data = if (double = Mocktail.cabinet.double_for_instance(receiver))
31
+ data_for_double(double)
32
+ elsif (type_replacement = TopShelf.instance.type_replacement_if_exists_for(receiver))
33
+ data_for_type_replacement(type_replacement)
34
+ end
35
+
36
+ if receiver_data
37
+ FakeMethodExplanation.new(FakeMethodData.new(
38
+ receiver: receiver,
39
+ calls: receiver_data.calls,
40
+ stubbings: receiver_data.stubbings
41
+ ), describe_dry_method(receiver_data, method.name))
42
+ end
43
+ end
44
+
45
+ def data_for_double(double)
46
+ DoubleData.new(
25
47
  type: double.original_type,
26
48
  double: double.dry_instance,
27
49
  calls: Mocktail.cabinet.calls_for_double(double),
28
50
  stubbings: Mocktail.cabinet.stubbings_for_double(double)
29
51
  )
52
+ end
53
+
54
+ def double_explanation(double)
55
+ double_data = data_for_double(double)
30
56
 
31
57
  DoubleExplanation.new(double_data, <<~MSG)
32
58
  This is a fake `#{double.original_type.name}' instance.
@@ -38,8 +64,8 @@ module Mocktail
38
64
  MSG
39
65
  end
40
66
 
41
- def replaced_type_explanation(type_replacement)
42
- type_replacement_data = TypeReplacementData.new(
67
+ def data_for_type_replacement(type_replacement)
68
+ TypeReplacementData.new(
43
69
  type: type_replacement.type,
44
70
  replaced_method_names: type_replacement.replacement_methods.map(&:name).sort,
45
71
  calls: Mocktail.cabinet.calls.select { |call|
@@ -49,6 +75,10 @@ module Mocktail
49
75
  stubbing.recording.double == type_replacement.type
50
76
  }
51
77
  )
78
+ end
79
+
80
+ def replaced_type_explanation(type_replacement)
81
+ type_replacement_data = data_for_type_replacement(type_replacement)
52
82
 
53
83
  ReplacedTypeExplanation.new(type_replacement_data, <<~MSG)
54
84
  `#{type_replacement.type}' is a #{type_replacement.type.class.to_s.downcase} that has had its singleton methods faked.
@@ -11,7 +11,7 @@ module Mocktail
11
11
  end
12
12
 
13
13
  def call(call)
14
- raise NoMethodError.new <<~MSG
14
+ raise NoMethodError, <<~MSG, caller[1..]
15
15
  No method `#{@stringifies_method_name.stringify(call)}' exists for call:
16
16
 
17
17
  #{@stringifies_call.stringify(call, anonymous_blocks: true, always_parens: true)}
@@ -23,4 +23,7 @@ module Mocktail
23
23
 
24
24
  class ReplacedTypeExplanation < Explanation
25
25
  end
26
+
27
+ class FakeMethodExplanation < Explanation
28
+ end
26
29
  end
@@ -0,0 +1,9 @@
1
+ module Mocktail
2
+ class FakeMethodData < Struct.new(
3
+ :receiver,
4
+ :calls,
5
+ :stubbings,
6
+ keyword_init: true
7
+ )
8
+ end
9
+ end
@@ -4,6 +4,7 @@ require_relative "value/demo_config"
4
4
  require_relative "value/double"
5
5
  require_relative "value/double_data"
6
6
  require_relative "value/explanation"
7
+ require_relative "value/fake_method_data"
7
8
  require_relative "value/matcher_registry"
8
9
  require_relative "value/signature"
9
10
  require_relative "value/stubbing"
@@ -1,3 +1,3 @@
1
1
  module Mocktail
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
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: 1.0.0
4
+ version: 1.1.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-12-20 00:00:00.000000000 Z
11
+ date: 2022-01-27 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -82,6 +82,7 @@ files:
82
82
  - lib/mocktail/value/double.rb
83
83
  - lib/mocktail/value/double_data.rb
84
84
  - lib/mocktail/value/explanation.rb
85
+ - lib/mocktail/value/fake_method_data.rb
85
86
  - lib/mocktail/value/matcher_registry.rb
86
87
  - lib/mocktail/value/signature.rb
87
88
  - lib/mocktail/value/stubbing.rb
@@ -116,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
117
  - !ruby/object:Gem::Version
117
118
  version: '0'
118
119
  requirements: []
119
- rubygems_version: 3.2.22
120
+ rubygems_version: 3.3.6
120
121
  signing_key:
121
122
  specification_version: 4
122
123
  summary: Take your objects, and make them a double