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 +4 -4
- data/.github/workflows/main.yml +1 -1
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +12 -12
- data/README.md +152 -75
- data/lib/mocktail/explains_thing.rb +34 -4
- data/lib/mocktail/raises_neato_no_method_error.rb +1 -1
- data/lib/mocktail/value/explanation.rb +3 -0
- data/lib/mocktail/value/fake_method_data.rb +9 -0
- data/lib/mocktail/value.rb +1 -0
- data/lib/mocktail/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a9c74cd3ff5167b99c38892ee6411a1ac3f1379158c062076bfbe6d8479c7b7
|
4
|
+
data.tar.gz: 81dd2b80a78c162a9c4030fa8bc9be42741c3a8c053fa33c319dc13c404234a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd1fbc482135e89dea00de9249d63b5e851d64ecb2f4afc898b4bb77abf5afc96303cfed6b59665d0e51ab4b2c8dd4c438dc2a7ab93d1c69e1d12d723797cf9a
|
7
|
+
data.tar.gz: 3af137dd6d1488b1ef41ac0b47178a224e160f11dce0ed0cbeb24418ae4a33c86a6e4fa8e3cabb20c8421c8ac1ac8c166045051d996d4aa1dc709e4fcc97ec3a
|
data/.github/workflows/main.yml
CHANGED
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.
|
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.
|
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.
|
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.
|
24
|
+
rubocop (1.25.0)
|
25
25
|
parallel (~> 1.10)
|
26
|
-
parser (>= 3.
|
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.
|
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.
|
33
|
+
rubocop-ast (1.15.1)
|
34
34
|
parser (>= 3.0.1.1)
|
35
|
-
rubocop-performance (1.
|
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.
|
46
|
-
rubocop (= 1.
|
47
|
-
rubocop-performance (= 1.
|
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.
|
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
|
557
|
-
messages throughout the
|
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
|
-
|
562
|
-
that
|
563
|
-
|
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
|
-
###
|
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
|
24
|
-
|
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
|
42
|
-
|
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
|
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)}
|
data/lib/mocktail/value.rb
CHANGED
@@ -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"
|
data/lib/mocktail/version.rb
CHANGED
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.
|
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:
|
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.
|
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
|