mocktail 1.0.0 → 1.1.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: 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