mocktail 0.0.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/Gemfile.lock +12 -12
- data/README.md +209 -18
- data/bin/console +21 -1
- data/lib/mocktail/explains_nils.rb +35 -0
- data/lib/mocktail/explains_thing.rb +93 -0
- data/lib/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +20 -0
- data/lib/mocktail/handles_dry_call/fulfills_stubbing.rb +16 -0
- data/lib/mocktail/imitates_type/makes_double/declares_dry_class.rb +1 -20
- data/lib/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +21 -0
- data/lib/mocktail/imitates_type/makes_double.rb +8 -4
- data/lib/mocktail/matchers/captor.rb +4 -0
- data/lib/mocktail/matchers/that.rb +1 -1
- data/lib/mocktail/raises_neato_no_method_error.rb +3 -1
- data/lib/mocktail/{simulates_argument_error → share}/cleans_backtrace.rb +2 -0
- data/lib/mocktail/share/creates_identifier.rb +17 -2
- data/lib/mocktail/share/stringifies_call.rb +14 -0
- data/lib/mocktail/share/stringifies_method_name.rb +11 -0
- data/lib/mocktail/simulates_argument_error.rb +1 -1
- data/lib/mocktail/value/cabinet.rb +19 -1
- data/lib/mocktail/value/double.rb +7 -8
- data/lib/mocktail/value/double_data.rb +10 -0
- data/lib/mocktail/value/explanation.rb +26 -0
- data/lib/mocktail/value/top_shelf.rb +24 -25
- data/lib/mocktail/value/type_replacement_data.rb +13 -0
- data/lib/mocktail/value/unsatisfying_call.rb +9 -0
- data/lib/mocktail/value.rb +4 -0
- data/lib/mocktail/verifies_call/raises_verification_error.rb +3 -1
- data/lib/mocktail/version.rb +1 -1
- data/lib/mocktail.rb +12 -0
- data/mocktail.gemspec +1 -1
- metadata +14 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 05ec28e8027b6d73ee5b3f513ce4d5e23a60ea0266643e344e20e8f995410ce2
|
4
|
+
data.tar.gz: 7baf1cff682de031f3f25cebad265e59e598c4f4103968ea2dda61b6c7605c9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4c5eef4fe5c68abd7d1e506bc59db58daa931fb484118e0ee4d6d286944d2834d0bb3107183102ac65110e12f32992b73fadbfcc26ed750bff060535e4eba5a2
|
7
|
+
data.tar.gz: cff11562ac6bc79475719f17f912ffc2fe8af6315ab90dbdc838143c237542ad788cba2d6c05ee15417799b2f597634244edf3c9f50dac5121adbac551602bdb
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,32 @@
|
|
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
|
+
|
11
|
+
# 0.0.6
|
12
|
+
|
13
|
+
* Require pathname, which I missed because `bundle exec` loads it. Wups!
|
14
|
+
|
15
|
+
# 0.0.5
|
16
|
+
|
17
|
+
* Fix concurrency [#6](https://github.com/testdouble/mocktail/pull/6)
|
18
|
+
|
19
|
+
# 0.0.4
|
20
|
+
|
21
|
+
* Introduce Mocktail.explain(), which will return a message & reference object
|
22
|
+
for any of:
|
23
|
+
* A class that has been passed to Mocktail.replace()
|
24
|
+
* An instance created by Mocktail.of() or of_next()
|
25
|
+
* A nil value returned by an unsatisfied stubbing invocation
|
26
|
+
* Fix some minor printing issue with the improved NoMethodError released in
|
27
|
+
0.0.3
|
28
|
+
|
29
|
+
|
1
30
|
# 0.0.3
|
2
31
|
|
3
32
|
* Implement method_missing on all mocked instance methods to print out useful
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
mocktail (0.0
|
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.
|
13
|
+
minitest (5.15.0)
|
14
14
|
parallel (1.21.0)
|
15
|
-
parser (3.0.2
|
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.
|
22
|
+
regexp_parser (2.2.0)
|
23
23
|
rexml (3.2.5)
|
24
|
-
rubocop (1.
|
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.
|
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.
|
33
|
+
rubocop-ast (1.15.0)
|
34
34
|
parser (>= 3.0.1.1)
|
35
|
-
rubocop-performance (1.
|
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.
|
46
|
-
rubocop (= 1.
|
47
|
-
rubocop-performance (= 1.
|
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
|
@@ -60,4 +60,4 @@ DEPENDENCIES
|
|
60
60
|
standard
|
61
61
|
|
62
62
|
BUNDLED WITH
|
63
|
-
2.2.
|
63
|
+
2.2.30
|
data/README.md
CHANGED
@@ -50,26 +50,37 @@ assert_equal "🎉", result
|
|
50
50
|
verify { glass.pour!(:a_drink) }
|
51
51
|
```
|
52
52
|
|
53
|
-
## Why
|
54
|
-
|
55
|
-
Besides
|
56
|
-
mocking libraries:
|
57
|
-
|
58
|
-
* **
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
* **
|
66
|
-
|
67
|
-
|
68
|
-
|
53
|
+
## Why Mocktail?
|
54
|
+
|
55
|
+
Besides helping you avoid a hangover, Mocktail offers several advantages over
|
56
|
+
Ruby's other mocking libraries:
|
57
|
+
|
58
|
+
* **Simpler test recipes**: [Mocktail.of_next(type)](#mocktailof_next) both
|
59
|
+
creates your mock and supplies to your subject under test in a single
|
60
|
+
one-liner. No more forcing dependency injection for the sake of your tests
|
61
|
+
* **WYSIWYG API**: Want to know how to stub a call to `phone.dial(911)`? You
|
62
|
+
just demonstrate the call with `stubs { phone.dial(911) }.with { :operator }`.
|
63
|
+
Because stubbing & verifying looks just like the actual call, your tests
|
64
|
+
becomes a sounding board for your APIs as you invent them
|
65
|
+
* **Argument validation**: Ever see a test pass after a change to a mocked
|
66
|
+
method should have broken it? Not with Mocktail, you haven't
|
67
|
+
* **Mocked class methods**: Singleton methods on modules and classes can be
|
68
|
+
faked out using [`Mocktail.replace(type)`](#mocktailreplace) without
|
69
|
+
sacrificing thread safety
|
70
|
+
* **Super-duper detailed error messages** A good mocking library should make
|
71
|
+
coding feel like
|
72
|
+
[paint-by-number](https://en.wikipedia.org/wiki/Paint_by_number), thoughtfully
|
73
|
+
guiding you from one step to the next. Calling a method that doesn't exist
|
74
|
+
will print a sample definition you can copy-paste. Verification failures will
|
75
|
+
print every call that _did_ occur. And [Mocktail.explain()](#mocktailexplain)
|
76
|
+
provides even more introspection
|
69
77
|
* **Expressive**: Built-in [argument matchers](#mocktailmatchers) and a simple
|
70
|
-
API for adding [custom matchers](#custom-matchers)
|
78
|
+
API for adding [custom matchers](#custom-matchers) allow you to tune your
|
79
|
+
stubbing configuration and call verification to match _exactly_ what your test
|
80
|
+
intends
|
71
81
|
* **Powerful**: [Argument captors](#mocktailcaptor) for assertions of very
|
72
|
-
complex arguments
|
82
|
+
complex arguments, as well as advanced configuration options for stubbing &
|
83
|
+
verification
|
73
84
|
|
74
85
|
## Ready to order?
|
75
86
|
|
@@ -539,6 +550,181 @@ down bugs. (If this concerns you, then the fact that class methods are
|
|
539
550
|
effectively global state may be a great reason not to rely too heavily on
|
540
551
|
them!)]
|
541
552
|
|
553
|
+
### Mocktail.explain
|
554
|
+
|
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.
|
558
|
+
|
559
|
+
#### Undefined methods
|
560
|
+
|
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:
|
564
|
+
|
565
|
+
```ruby
|
566
|
+
class IceTray
|
567
|
+
end
|
568
|
+
|
569
|
+
ice_tray = Mocktail.of(IceTray)
|
570
|
+
|
571
|
+
ice_tray.fill(:water_type, 30)
|
572
|
+
# => No method `IceTray#fill' exists for call: (NoMethodError)
|
573
|
+
#
|
574
|
+
# fill(:water_type, 30)
|
575
|
+
#
|
576
|
+
# Need to define the method? Here's a sample definition:
|
577
|
+
#
|
578
|
+
# def fill(water_type, arg)
|
579
|
+
# end
|
580
|
+
```
|
581
|
+
|
582
|
+
From there, you can just copy-paste the provided method stub as a starting point
|
583
|
+
for your new method:
|
584
|
+
|
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.
|
602
|
+
|
603
|
+
For example, suppose you stub this `fill` method like so:
|
604
|
+
|
605
|
+
```ruby
|
606
|
+
ice_tray = Mocktail.of(IceTray)
|
607
|
+
|
608
|
+
stubs { ice_tray.fill(:tap_water, 30) }.with { :normal_ice }
|
609
|
+
```
|
610
|
+
|
611
|
+
But then you find that your subject under test is just getting `nil` back and
|
612
|
+
you don't understand why:
|
613
|
+
|
614
|
+
```ruby
|
615
|
+
def prep
|
616
|
+
ice = ice_tray.fill(:tap_water, 50)
|
617
|
+
glass.add(ice) # => why is `ice` nil?!
|
618
|
+
end
|
619
|
+
```
|
620
|
+
|
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`:
|
627
|
+
|
628
|
+
```ruby
|
629
|
+
def prep
|
630
|
+
ice = ice_tray.fill(:tap_water, 50)
|
631
|
+
puts Mocktail.explain_nils.first.message
|
632
|
+
glass.add(ice)
|
633
|
+
end
|
634
|
+
```
|
635
|
+
|
636
|
+
Which will print:
|
637
|
+
|
638
|
+
```
|
639
|
+
This `nil' was returned by a mocked `IceTray#fill' method
|
640
|
+
because none of its configured stubbings were satisfied.
|
641
|
+
|
642
|
+
The actual call:
|
643
|
+
|
644
|
+
fill(:tap_water, 50)
|
645
|
+
|
646
|
+
The call site:
|
647
|
+
|
648
|
+
/path/to/your/code.rb:42:in `prep'
|
649
|
+
|
650
|
+
Stubbings configured prior to this call but not satisfied by it:
|
651
|
+
|
652
|
+
fill(:tap_water, 30)
|
653
|
+
```
|
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
|
+
|
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
|
+
|
542
728
|
### Mocktail.reset
|
543
729
|
|
544
730
|
This one's simple: you probably want to call `Mocktail.reset` after each test,
|
@@ -546,6 +732,10 @@ but you _definitely_ want to call it if you're using `Mocktail.replace` or
|
|
546
732
|
`Mocktail.of_next` anywhere, since those will affect state that is shared across
|
547
733
|
tests.
|
548
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
|
+
|
549
739
|
## Acknowledgements
|
550
740
|
|
551
741
|
Mocktail is created & maintained by the software agency [Test
|
@@ -572,3 +762,4 @@ including (but not limited to) one-on-one communications, public posts/comments,
|
|
572
762
|
code reviews, pull requests, and GitHub issues. If violations occur, Test Double
|
573
763
|
will take any action they deem appropriate for the infraction, up to and
|
574
764
|
including blocking a user from the organization's repositories.
|
765
|
+
|
data/bin/console
CHANGED
@@ -56,6 +56,26 @@ class Bartender
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
|
59
|
+
class IceTray
|
60
|
+
def fill(water_type, amount)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Shop
|
65
|
+
def self.open!(bar_id)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.close!(bar_id)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Mocktail.replace(Shop)
|
73
|
+
|
74
|
+
stubs { |m| Shop.open!(m.numeric) }.with { :a_bar }
|
75
|
+
|
76
|
+
Shop.open!(42)
|
77
|
+
|
78
|
+
Shop.close!(42)
|
79
|
+
|
60
80
|
require "pry"
|
61
81
|
Pry.start
|
@@ -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
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require_relative "share/stringifies_method_name"
|
2
|
+
require_relative "share/stringifies_call"
|
3
|
+
|
4
|
+
module Mocktail
|
5
|
+
class ExplainsThing
|
6
|
+
def initialize
|
7
|
+
@stringifies_method_name = StringifiesMethodName.new
|
8
|
+
@stringifies_call = StringifiesCall.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def explain(thing)
|
12
|
+
if (double = Mocktail.cabinet.double_for_instance(thing))
|
13
|
+
double_explanation(double)
|
14
|
+
elsif (type_replacement = TopShelf.instance.type_replacement_if_exists_for(thing))
|
15
|
+
replaced_type_explanation(type_replacement)
|
16
|
+
else
|
17
|
+
no_explanation(thing)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def double_explanation(double)
|
24
|
+
double_data = DoubleData.new(
|
25
|
+
type: double.original_type,
|
26
|
+
double: double.dry_instance,
|
27
|
+
calls: Mocktail.cabinet.calls_for_double(double),
|
28
|
+
stubbings: Mocktail.cabinet.stubbings_for_double(double)
|
29
|
+
)
|
30
|
+
|
31
|
+
DoubleExplanation.new(double_data, <<~MSG)
|
32
|
+
This is a fake `#{double.original_type.name}' instance.
|
33
|
+
|
34
|
+
It has these mocked methods:
|
35
|
+
#{double.dry_methods.sort.map { |method| " - #{method}" }.join("\n")}
|
36
|
+
|
37
|
+
#{double.dry_methods.sort.map { |method| describe_dry_method(double_data, method) }.join("\n")}
|
38
|
+
MSG
|
39
|
+
end
|
40
|
+
|
41
|
+
def replaced_type_explanation(type_replacement)
|
42
|
+
type_replacement_data = TypeReplacementData.new(
|
43
|
+
type: type_replacement.type,
|
44
|
+
replaced_method_names: type_replacement.replacement_methods.map(&:name).sort,
|
45
|
+
calls: Mocktail.cabinet.calls.select { |call|
|
46
|
+
call.double == type_replacement.type
|
47
|
+
},
|
48
|
+
stubbings: Mocktail.cabinet.stubbings.select { |stubbing|
|
49
|
+
stubbing.recording.double == type_replacement.type
|
50
|
+
}
|
51
|
+
)
|
52
|
+
|
53
|
+
ReplacedTypeExplanation.new(type_replacement_data, <<~MSG)
|
54
|
+
`#{type_replacement.type}' is a #{type_replacement.type.class.to_s.downcase} that has had its singleton methods faked.
|
55
|
+
|
56
|
+
It has these mocked methods:
|
57
|
+
#{type_replacement_data.replaced_method_names.map { |method| " - #{method}" }.join("\n")}
|
58
|
+
|
59
|
+
#{type_replacement_data.replaced_method_names.map { |method| describe_dry_method(type_replacement_data, method) }.join("\n")}
|
60
|
+
MSG
|
61
|
+
end
|
62
|
+
|
63
|
+
def describe_dry_method(double_data, method)
|
64
|
+
method_name = @stringifies_method_name.stringify(Call.new(
|
65
|
+
original_type: double_data.type,
|
66
|
+
singleton: double_data.type == double_data.double,
|
67
|
+
method: method
|
68
|
+
))
|
69
|
+
|
70
|
+
[
|
71
|
+
@stringifies_call.stringify_multiple(
|
72
|
+
double_data.stubbings.map(&:recording).select { |call|
|
73
|
+
call.method == method
|
74
|
+
},
|
75
|
+
nonzero_message: "`#{method_name}' stubbings",
|
76
|
+
zero_message: "`#{method_name}' has no stubbings"
|
77
|
+
),
|
78
|
+
@stringifies_call.stringify_multiple(
|
79
|
+
double_data.calls.select { |call|
|
80
|
+
call.method == method
|
81
|
+
},
|
82
|
+
nonzero_message: "`#{method_name}' calls",
|
83
|
+
zero_message: "`#{method_name}' has no calls"
|
84
|
+
)
|
85
|
+
].join("\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
def no_explanation(thing)
|
89
|
+
NoExplanation.new(thing,
|
90
|
+
"Unfortunately, Mocktail doesn't know what this thing is: #{thing.inspect}")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative "../../share/cleans_backtrace"
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
class DescribesUnsatisfiedStubbing
|
5
|
+
def initialize
|
6
|
+
@cleans_backtrace = CleansBacktrace.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def describe(dry_call)
|
10
|
+
UnsatisfyingCall.new(
|
11
|
+
call: dry_call,
|
12
|
+
other_stubbings: Mocktail.cabinet.stubbings.select { |stubbing|
|
13
|
+
dry_call.double == stubbing.recording.double &&
|
14
|
+
dry_call.method == stubbing.recording.method
|
15
|
+
},
|
16
|
+
backtrace: @cleans_backtrace.clean(Error.new).backtrace
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,21 +1,37 @@
|
|
1
1
|
require_relative "fulfills_stubbing/finds_satisfaction"
|
2
|
+
require_relative "fulfills_stubbing/describes_unsatisfied_stubbing"
|
2
3
|
|
3
4
|
module Mocktail
|
4
5
|
class FulfillsStubbing
|
5
6
|
def initialize
|
6
7
|
@finds_satisfaction = FindsSatisfaction.new
|
8
|
+
@describes_unsatisfied_stubbing = DescribesUnsatisfiedStubbing.new
|
7
9
|
end
|
8
10
|
|
9
11
|
def fulfill(dry_call)
|
10
12
|
if (stubbing = satisfaction(dry_call))
|
11
13
|
stubbing.satisfied!
|
12
14
|
stubbing.effect&.call(dry_call)
|
15
|
+
else
|
16
|
+
store_unsatisfying_call!(dry_call)
|
17
|
+
nil
|
13
18
|
end
|
14
19
|
end
|
15
20
|
|
16
21
|
def satisfaction(dry_call)
|
17
22
|
return if Mocktail.cabinet.demonstration_in_progress?
|
23
|
+
|
18
24
|
@finds_satisfaction.find(dry_call)
|
19
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
|
20
36
|
end
|
21
37
|
end
|
@@ -5,8 +5,7 @@ module Mocktail
|
|
5
5
|
@raises_neato_no_method_error = RaisesNeatoNoMethodError.new
|
6
6
|
end
|
7
7
|
|
8
|
-
def declare(type)
|
9
|
-
instance_methods = instance_methods_on(type)
|
8
|
+
def declare(type, instance_methods)
|
10
9
|
dry_class = Class.new(Object) {
|
11
10
|
include type if type.instance_of?(Module)
|
12
11
|
|
@@ -98,23 +97,5 @@ module Mocktail
|
|
98
97
|
)
|
99
98
|
}
|
100
99
|
end
|
101
|
-
|
102
|
-
def instance_methods_on(type)
|
103
|
-
methods = type.instance_methods + [
|
104
|
-
(:respond_to_missing? if type.private_method_defined?(:respond_to_missing?))
|
105
|
-
].compact
|
106
|
-
|
107
|
-
methods.reject { |m|
|
108
|
-
ignore?(type, m)
|
109
|
-
}
|
110
|
-
end
|
111
|
-
|
112
|
-
def ignore?(type, method_name)
|
113
|
-
ignored_ancestors.include?(type.instance_method(method_name).owner)
|
114
|
-
end
|
115
|
-
|
116
|
-
def ignored_ancestors
|
117
|
-
Object.ancestors
|
118
|
-
end
|
119
100
|
end
|
120
101
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Mocktail
|
2
|
+
class GathersFakeableInstanceMethods
|
3
|
+
def gather(type)
|
4
|
+
methods = type.instance_methods + [
|
5
|
+
(:respond_to_missing? if type.private_method_defined?(:respond_to_missing?))
|
6
|
+
].compact
|
7
|
+
|
8
|
+
methods.reject { |m|
|
9
|
+
ignore?(type, m)
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def ignore?(type, method_name)
|
14
|
+
ignored_ancestors.include?(type.instance_method(method_name).owner)
|
15
|
+
end
|
16
|
+
|
17
|
+
def ignored_ancestors
|
18
|
+
Object.ancestors
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,17 +1,21 @@
|
|
1
1
|
require_relative "makes_double/declares_dry_class"
|
2
|
+
require_relative "makes_double/gathers_fakeable_instance_methods"
|
2
3
|
|
3
4
|
module Mocktail
|
4
5
|
class MakesDouble
|
5
6
|
def initialize
|
6
7
|
@declares_dry_class = DeclaresDryClass.new
|
8
|
+
@gathers_fakeable_instance_methods = GathersFakeableInstanceMethods.new
|
7
9
|
end
|
8
10
|
|
9
|
-
def make(
|
10
|
-
|
11
|
+
def make(type)
|
12
|
+
dry_methods = @gathers_fakeable_instance_methods.gather(type)
|
13
|
+
dry_type = @declares_dry_class.declare(type, dry_methods)
|
11
14
|
Double.new(
|
12
|
-
original_type:
|
15
|
+
original_type: type,
|
13
16
|
dry_type: dry_type,
|
14
|
-
dry_instance: dry_type.new
|
17
|
+
dry_instance: dry_type.new,
|
18
|
+
dry_methods: dry_methods
|
15
19
|
)
|
16
20
|
end
|
17
21
|
end
|
@@ -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
|
@@ -1,16 +1,18 @@
|
|
1
1
|
require_relative "share/stringifies_call"
|
2
|
+
require_relative "share/stringifies_method_name"
|
2
3
|
require_relative "share/creates_identifier"
|
3
4
|
|
4
5
|
module Mocktail
|
5
6
|
class RaisesNeatoNoMethodError
|
6
7
|
def initialize
|
7
8
|
@stringifies_call = StringifiesCall.new
|
9
|
+
@stringifies_method_name = StringifiesMethodName.new
|
8
10
|
@creates_identifier = CreatesIdentifier.new
|
9
11
|
end
|
10
12
|
|
11
13
|
def call(call)
|
12
14
|
raise NoMethodError.new <<~MSG
|
13
|
-
No method `#{
|
15
|
+
No method `#{@stringifies_method_name.stringify(call)}' exists for call:
|
14
16
|
|
15
17
|
#{@stringifies_call.stringify(call, anonymous_blocks: true, always_parens: true)}
|
16
18
|
|
@@ -1,13 +1,28 @@
|
|
1
1
|
module Mocktail
|
2
2
|
class CreatesIdentifier
|
3
|
+
KEYWORDS = %w[__FILE__ __LINE__ alias and begin BEGIN break case class def defined? do else elsif end END ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield]
|
4
|
+
|
3
5
|
def create(s, default: "identifier", max_length: 24)
|
4
|
-
id = s.to_s.downcase
|
6
|
+
id = s.to_s.downcase
|
7
|
+
.gsub(/:0x[0-9a-f]+/, "") # Lazy attempt to wipe any Object:0x802beef identifiers
|
8
|
+
.gsub(/[^\w\s]/, "")
|
9
|
+
.gsub(/^\d+/, "")[0...max_length]
|
10
|
+
.strip
|
11
|
+
.gsub(/\s+/, "_") # snake_case
|
5
12
|
|
6
13
|
if id.empty?
|
7
14
|
default
|
8
15
|
else
|
9
|
-
id
|
16
|
+
unreserved(id, default)
|
10
17
|
end
|
11
18
|
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def unreserved(id, default)
|
23
|
+
return id unless KEYWORDS.include?(id)
|
24
|
+
|
25
|
+
"#{id}_#{default}"
|
26
|
+
end
|
12
27
|
end
|
13
28
|
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 "
|
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,8 +36,24 @@ 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
|
46
|
+
|
47
|
+
def double_for_instance(thing)
|
48
|
+
@doubles.find { |double| double.dry_instance == thing }
|
49
|
+
end
|
50
|
+
|
51
|
+
def stubbings_for_double(double)
|
52
|
+
@stubbings.select { |stubbing| stubbing.recording.double == double.dry_instance }
|
53
|
+
end
|
54
|
+
|
55
|
+
def calls_for_double(double)
|
56
|
+
@calls.select { |call| call.double == double.dry_instance }
|
57
|
+
end
|
40
58
|
end
|
41
59
|
end
|
@@ -1,11 +1,10 @@
|
|
1
1
|
module Mocktail
|
2
|
-
class Double
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
2
|
+
class Double < Struct.new(
|
3
|
+
:original_type,
|
4
|
+
:dry_type,
|
5
|
+
:dry_instance,
|
6
|
+
:dry_methods,
|
7
|
+
keyword_init: true
|
8
|
+
)
|
10
9
|
end
|
11
10
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Mocktail
|
2
|
+
class Explanation
|
3
|
+
attr_reader :reference, :message
|
4
|
+
|
5
|
+
def initialize(reference, message)
|
6
|
+
@reference = reference
|
7
|
+
@message = message
|
8
|
+
end
|
9
|
+
|
10
|
+
def type
|
11
|
+
self.class
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class NoExplanation < Explanation
|
16
|
+
end
|
17
|
+
|
18
|
+
class UnsatisfyingCallExplanation < Explanation
|
19
|
+
end
|
20
|
+
|
21
|
+
class DoubleExplanation < Explanation
|
22
|
+
end
|
23
|
+
|
24
|
+
class ReplacedTypeExplanation < Explanation
|
25
|
+
end
|
26
|
+
end
|
@@ -1,61 +1,60 @@
|
|
1
|
-
# The top shelf stores all cross-thread & thread-aware state, so anything that
|
2
|
-
# goes here is on its own when it comes to ensuring thread safety.
|
3
1
|
module Mocktail
|
4
2
|
class TopShelf
|
5
3
|
def self.instance
|
6
|
-
|
4
|
+
Thread.current[:mocktail_top_shelf] ||= new
|
7
5
|
end
|
8
6
|
|
7
|
+
@@type_replacements = {}
|
8
|
+
@@type_replacements_mutex = Mutex.new
|
9
|
+
|
9
10
|
def initialize
|
10
|
-
@
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@singleton_method_registrations = {}
|
11
|
+
@new_registrations = []
|
12
|
+
@of_next_registrations = []
|
13
|
+
@singleton_method_registrations = []
|
14
14
|
end
|
15
15
|
|
16
16
|
def type_replacement_for(type)
|
17
|
-
|
17
|
+
@@type_replacements_mutex.synchronize {
|
18
|
+
@@type_replacements[type] ||= TypeReplacement.new(type: type)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def type_replacement_if_exists_for(type)
|
23
|
+
@@type_replacements_mutex.synchronize {
|
24
|
+
@@type_replacements[type]
|
25
|
+
}
|
18
26
|
end
|
19
27
|
|
20
28
|
def reset_current_thread!
|
21
|
-
|
22
|
-
@of_next_registrations[Thread.current] = []
|
23
|
-
@singleton_method_registrations[Thread.current] = []
|
29
|
+
Thread.current[:mocktail_top_shelf] = self.class.new
|
24
30
|
end
|
25
31
|
|
26
32
|
def register_new_replacement!(type)
|
27
|
-
@new_registrations
|
28
|
-
@new_registrations[Thread.current] |= [type]
|
33
|
+
@new_registrations |= [type]
|
29
34
|
end
|
30
35
|
|
31
36
|
def new_replaced?(type)
|
32
|
-
@new_registrations
|
33
|
-
@new_registrations[Thread.current].include?(type)
|
37
|
+
@new_registrations.include?(type)
|
34
38
|
end
|
35
39
|
|
36
40
|
def register_of_next_replacement!(type)
|
37
|
-
@of_next_registrations
|
38
|
-
@of_next_registrations[Thread.current] |= [type]
|
41
|
+
@of_next_registrations |= [type]
|
39
42
|
end
|
40
43
|
|
41
44
|
def of_next_registered?(type)
|
42
|
-
@of_next_registrations
|
43
|
-
@of_next_registrations[Thread.current].include?(type)
|
45
|
+
@of_next_registrations.include?(type)
|
44
46
|
end
|
45
47
|
|
46
48
|
def unregister_of_next_replacement!(type)
|
47
|
-
@of_next_registrations
|
48
|
-
@of_next_registrations[Thread.current] -= [type]
|
49
|
+
@of_next_registrations -= [type]
|
49
50
|
end
|
50
51
|
|
51
52
|
def register_singleton_method_replacement!(type)
|
52
|
-
@singleton_method_registrations
|
53
|
-
@singleton_method_registrations[Thread.current] |= [type]
|
53
|
+
@singleton_method_registrations |= [type]
|
54
54
|
end
|
55
55
|
|
56
56
|
def singleton_methods_replaced?(type)
|
57
|
-
@singleton_method_registrations
|
58
|
-
@singleton_method_registrations[Thread.current].include?(type)
|
57
|
+
@singleton_method_registrations.include?(type)
|
59
58
|
end
|
60
59
|
end
|
61
60
|
end
|
data/lib/mocktail/value.rb
CHANGED
@@ -2,8 +2,12 @@ require_relative "value/cabinet"
|
|
2
2
|
require_relative "value/call"
|
3
3
|
require_relative "value/demo_config"
|
4
4
|
require_relative "value/double"
|
5
|
+
require_relative "value/double_data"
|
6
|
+
require_relative "value/explanation"
|
5
7
|
require_relative "value/matcher_registry"
|
6
8
|
require_relative "value/signature"
|
7
9
|
require_relative "value/stubbing"
|
8
10
|
require_relative "value/top_shelf"
|
9
11
|
require_relative "value/type_replacement"
|
12
|
+
require_relative "value/type_replacement_data"
|
13
|
+
require_relative "value/unsatisfying_call"
|
@@ -1,16 +1,18 @@
|
|
1
1
|
require_relative "raises_verification_error/gathers_calls_of_method"
|
2
|
+
require_relative "../share/stringifies_method_name"
|
2
3
|
require_relative "../share/stringifies_call"
|
3
4
|
|
4
5
|
module Mocktail
|
5
6
|
class RaisesVerificationError
|
6
7
|
def initialize
|
7
8
|
@gathers_calls_of_method = GathersCallsOfMethod.new
|
9
|
+
@stringifies_method_name = StringifiesMethodName.new
|
8
10
|
@stringifies_call = StringifiesCall.new
|
9
11
|
end
|
10
12
|
|
11
13
|
def raise(recording, verifiable_calls, demo_config)
|
12
14
|
Kernel.raise VerificationError.new <<~MSG
|
13
|
-
Expected mocktail of
|
15
|
+
Expected mocktail of `#{@stringifies_method_name.stringify(recording)}' to be called like:
|
14
16
|
|
15
17
|
#{@stringifies_call.stringify(recording)}#{[
|
16
18
|
(" [#{demo_config.times} #{pl("time", demo_config.times)}]" unless demo_config.times.nil?),
|
data/lib/mocktail/version.rb
CHANGED
data/lib/mocktail.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require_relative "mocktail/dsl"
|
2
2
|
require_relative "mocktail/errors"
|
3
|
+
require_relative "mocktail/explains_thing"
|
4
|
+
require_relative "mocktail/explains_nils"
|
3
5
|
require_relative "mocktail/handles_dry_call"
|
4
6
|
require_relative "mocktail/handles_dry_new_call"
|
5
7
|
require_relative "mocktail/imitates_type"
|
@@ -56,7 +58,17 @@ module Mocktail
|
|
56
58
|
ResetsState.new.reset
|
57
59
|
end
|
58
60
|
|
61
|
+
def self.explain(thing)
|
62
|
+
ExplainsThing.new.explain(thing)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.explain_nils
|
66
|
+
ExplainsNils.new.explain
|
67
|
+
end
|
68
|
+
|
59
69
|
# Stores most transactional state about calls & stubbing configurations
|
70
|
+
# Anything returned by this is undocumented and could change at any time, so
|
71
|
+
# don't commit code that relies on it!
|
60
72
|
def self.cabinet
|
61
73
|
Thread.current[:mocktail_store] ||= Cabinet.new
|
62
74
|
end
|
data/mocktail.gemspec
CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.authors = ["Justin Searls"]
|
7
7
|
spec.email = ["searls@gmail.com"]
|
8
8
|
|
9
|
-
spec.summary = "your objects,
|
9
|
+
spec.summary = "Take your objects, and make them a double"
|
10
10
|
spec.homepage = "https://github.com/testdouble/mocktail"
|
11
11
|
spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
|
12
12
|
|
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
|
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
|
+
date: 2021-12-20 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -31,8 +31,11 @@ files:
|
|
31
31
|
- lib/mocktail.rb
|
32
32
|
- lib/mocktail/dsl.rb
|
33
33
|
- lib/mocktail/errors.rb
|
34
|
+
- lib/mocktail/explains_nils.rb
|
35
|
+
- lib/mocktail/explains_thing.rb
|
34
36
|
- lib/mocktail/handles_dry_call.rb
|
35
37
|
- lib/mocktail/handles_dry_call/fulfills_stubbing.rb
|
38
|
+
- lib/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb
|
36
39
|
- lib/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb
|
37
40
|
- lib/mocktail/handles_dry_call/logs_call.rb
|
38
41
|
- lib/mocktail/handles_dry_call/validates_arguments.rb
|
@@ -41,6 +44,7 @@ files:
|
|
41
44
|
- lib/mocktail/imitates_type/ensures_imitation_support.rb
|
42
45
|
- lib/mocktail/imitates_type/makes_double.rb
|
43
46
|
- lib/mocktail/imitates_type/makes_double/declares_dry_class.rb
|
47
|
+
- lib/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb
|
44
48
|
- lib/mocktail/initializes_mocktail.rb
|
45
49
|
- lib/mocktail/matcher_presentation.rb
|
46
50
|
- lib/mocktail/matchers.rb
|
@@ -62,11 +66,12 @@ files:
|
|
62
66
|
- lib/mocktail/replaces_type/redefines_new.rb
|
63
67
|
- lib/mocktail/replaces_type/redefines_singleton_methods.rb
|
64
68
|
- lib/mocktail/resets_state.rb
|
69
|
+
- lib/mocktail/share/cleans_backtrace.rb
|
65
70
|
- lib/mocktail/share/creates_identifier.rb
|
66
71
|
- lib/mocktail/share/determines_matching_calls.rb
|
67
72
|
- lib/mocktail/share/stringifies_call.rb
|
73
|
+
- lib/mocktail/share/stringifies_method_name.rb
|
68
74
|
- lib/mocktail/simulates_argument_error.rb
|
69
|
-
- lib/mocktail/simulates_argument_error/cleans_backtrace.rb
|
70
75
|
- lib/mocktail/simulates_argument_error/reconciles_args_with_params.rb
|
71
76
|
- lib/mocktail/simulates_argument_error/recreates_message.rb
|
72
77
|
- lib/mocktail/simulates_argument_error/transforms_params.rb
|
@@ -75,11 +80,15 @@ files:
|
|
75
80
|
- lib/mocktail/value/call.rb
|
76
81
|
- lib/mocktail/value/demo_config.rb
|
77
82
|
- lib/mocktail/value/double.rb
|
83
|
+
- lib/mocktail/value/double_data.rb
|
84
|
+
- lib/mocktail/value/explanation.rb
|
78
85
|
- lib/mocktail/value/matcher_registry.rb
|
79
86
|
- lib/mocktail/value/signature.rb
|
80
87
|
- lib/mocktail/value/stubbing.rb
|
81
88
|
- lib/mocktail/value/top_shelf.rb
|
82
89
|
- lib/mocktail/value/type_replacement.rb
|
90
|
+
- lib/mocktail/value/type_replacement_data.rb
|
91
|
+
- lib/mocktail/value/unsatisfying_call.rb
|
83
92
|
- lib/mocktail/verifies_call.rb
|
84
93
|
- lib/mocktail/verifies_call/finds_verifiable_calls.rb
|
85
94
|
- lib/mocktail/verifies_call/raises_verification_error.rb
|
@@ -107,8 +116,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
116
|
- !ruby/object:Gem::Version
|
108
117
|
version: '0'
|
109
118
|
requirements: []
|
110
|
-
rubygems_version: 3.2.
|
119
|
+
rubygems_version: 3.2.22
|
111
120
|
signing_key:
|
112
121
|
specification_version: 4
|
113
|
-
summary: your objects,
|
122
|
+
summary: Take your objects, and make them a double
|
114
123
|
test_files: []
|