mocktail 0.0.1 → 0.0.2
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 +9 -3
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +2 -1
- data/README.md +82 -65
- data/bin/console +26 -0
- data/lib/mocktail/version.rb +1 -1
- data/mocktail.gemspec +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5a4729eb601d9f3ed3ece213a460dd0881105a505a812c4d035c14cec252223
|
4
|
+
data.tar.gz: f4b3a3352f63de7de812ce8db616b570de740f1ce7550f8677eb47ecb99305fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 469b39914d0d887b7cd8d25006f5dc6f3ca8a1a3b5594f39bfae5e3c21f36f2983a30ec2849c916129e216dd9fb731f476abbdc5a41ad0f80c740d7106793a7d
|
7
|
+
data.tar.gz: d7c24487a15c7ddc9b540d6c9d5c2b3f07da3599eba61eee128a79f5faa3fd5f6c4e54b7ea4318119a86737ead855f30d814a7f39e30c1e20a6f3880f0c21ce2
|
data/.github/workflows/main.yml
CHANGED
@@ -4,15 +4,21 @@ on: [push,pull_request]
|
|
4
4
|
|
5
5
|
jobs:
|
6
6
|
build:
|
7
|
-
|
7
|
+
strategy:
|
8
|
+
matrix:
|
9
|
+
os: [ ubuntu-latest ]
|
10
|
+
ruby-version: [3.0.1]
|
11
|
+
|
12
|
+
runs-on: ${{ matrix.os }}
|
13
|
+
|
8
14
|
steps:
|
9
15
|
- uses: actions/checkout@v2
|
10
16
|
- name: Set up Ruby
|
11
17
|
uses: ruby/setup-ruby@v1
|
12
18
|
with:
|
13
|
-
ruby-version:
|
19
|
+
ruby-version: ${{ matrix.ruby-version }}
|
14
20
|
- name: Run the default task
|
15
21
|
run: |
|
16
|
-
gem install bundler
|
22
|
+
gem install bundler
|
17
23
|
bundle install
|
18
24
|
bundle exec rake
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
# 0.0.2
|
2
|
+
|
3
|
+
* Drop Ruby 2.7 support. Unbeknownst to me (since I developed mocktail using
|
4
|
+
ruby 3.0), the entire approach to using `define_method` with `*args` and
|
5
|
+
`**kwargs` splats only further confuses the [arg
|
6
|
+
splitting](https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/)
|
7
|
+
behavior in Ruby 2.x. So in the event that someone calls a method with an
|
8
|
+
ambiguous hash-as-last arg (either in a mock demonstration or call), it will
|
9
|
+
be functionally impossible to either (a) validate the args against the
|
10
|
+
parameters or (b) compare two calls as being a match for one another. These
|
11
|
+
problems could be overcome (by using `eval` instead of `define_method` for
|
12
|
+
mocked methods and by expanding the call-matching logic dramatically), but
|
13
|
+
who's got the time. Upgrade to 3.0!
|
14
|
+
|
1
15
|
# 0.0.1
|
2
16
|
|
3
17
|
Initial release
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -6,73 +6,74 @@ width="90%"/>
|
|
6
6
|
|
7
7
|
Mocktail is a [test
|
8
8
|
double](https://github.com/testdouble/contributing-tests/wiki/Test-Double)
|
9
|
-
library for Ruby
|
9
|
+
library for Ruby that provides a terse and robust API for creating mocks,
|
10
|
+
getting them in the hands of the code you're testing, stub & verify behavior,
|
11
|
+
and even safely override class methods.
|
10
12
|
|
11
|
-
##
|
13
|
+
## An aperitif
|
12
14
|
|
13
15
|
Before getting into the details, let's demonstrate what Mocktail's API looks
|
14
|
-
like. Suppose you
|
16
|
+
like. Suppose you want to test a `Bartender` class:
|
15
17
|
|
16
18
|
```ruby
|
17
|
-
class
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
def shake!(shaker)
|
23
|
-
shaker.mix(self.class.ingredients)
|
19
|
+
class Bartender
|
20
|
+
def initialize
|
21
|
+
@shaker = Shaker.new
|
22
|
+
@glass = Glass.new
|
23
|
+
@bar = Bar.new
|
24
24
|
end
|
25
25
|
|
26
|
-
def
|
27
|
-
|
26
|
+
def make_drink(name, customer:)
|
27
|
+
if name == :negroni
|
28
|
+
drink = @shaker.combine(:gin, :campari, :sweet_vermouth)
|
29
|
+
@glass.pour!(drink)
|
30
|
+
@bar.pass(@glass, to: customer)
|
31
|
+
end
|
28
32
|
end
|
29
33
|
end
|
30
34
|
```
|
31
35
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
36
|
+
You could write an isolated unit test with Mocktail like this:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
shaker = Mocktail.of_next(Shaker)
|
40
|
+
glass = Mocktail.of_next(Glass)
|
41
|
+
bar = Mocktail.of_next(Bar)
|
42
|
+
subject = Bartender.new
|
43
|
+
stubs { shaker.combine(:gin, :campari, :sweet_vermouth) }.with { :a_drink }
|
44
|
+
stubs { bar.pass(glass, to: "Eileen") }.with { "🎉" }
|
45
|
+
|
46
|
+
result = subject.make_drink(:negroni, customer: "Eileen")
|
47
|
+
|
48
|
+
assert_equal "🎉", result
|
49
|
+
# Oh yeah, and make sure the drink got poured! Silly side effects!
|
50
|
+
verify { glass.pour!(:a_drink) }
|
51
|
+
```
|
52
|
+
|
53
|
+
## Why order?
|
54
|
+
|
55
|
+
Besides a lack of hangover, Mocktail offers several advantages over other
|
56
|
+
mocking libraries:
|
57
|
+
|
58
|
+
* **Fewer hoops to jump through**: [`Mocktail.of_next(type)`] avoids the need
|
59
|
+
for dependency injection by returning a Mocktail of the type the next time
|
60
|
+
`Type.new` is called. You can inject a fake into production code in one
|
61
|
+
line.
|
62
|
+
* **Fewer false test passes**: Arity of arguments and keyword arguments of faked
|
63
|
+
methods is enforced—no more tests that keep passing after an API changes
|
64
|
+
* **Super-duper detailed error messages when verifications fail**
|
65
|
+
* **Fake class methods**: Singleton methods on classes and modules can be
|
66
|
+
replaced with [`Mocktail.replace(type)`](#mocktailreplace) while still
|
57
67
|
preserving thread safety
|
58
|
-
*
|
59
|
-
|
60
|
-
|
61
|
-
*
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
`times` a stub can be satisfied or a call should be verified, allowing tests
|
68
|
-
to forego specifying arguments and blocks, and temporarily disabling arity
|
69
|
-
validation
|
70
|
-
* Built-in matchers as well as custom matcher support
|
71
|
-
* Argument captors for complex, multi-step call verifications
|
72
|
-
|
73
|
-
## Getting started
|
74
|
-
|
75
|
-
### Install
|
68
|
+
* **Less test setup**: Dynamic stubbings based on the arguments passed to the actual call
|
69
|
+
* **Expressive**: Built-in [argument matchers](#mocktailmatchers) and a simple
|
70
|
+
API for adding [custom matchers](#custom-matchers)
|
71
|
+
* **Powerful**: [Argument captors](#mocktailcaptor) for assertions of very
|
72
|
+
complex arguments
|
73
|
+
|
74
|
+
## Ready to order?
|
75
|
+
|
76
|
+
### Install the gem
|
76
77
|
|
77
78
|
The main ingredient to add to your Gemfile:
|
78
79
|
|
@@ -80,7 +81,7 @@ The main ingredient to add to your Gemfile:
|
|
80
81
|
gem "mocktail", group: :test
|
81
82
|
```
|
82
83
|
|
83
|
-
###
|
84
|
+
### Sprinkle in the DSL
|
84
85
|
|
85
86
|
Then, in each of your tests or in a test helper, you'll probably want to include
|
86
87
|
Mocktail's DSL. (This is optional, however, as every method in the DSL is also
|
@@ -102,11 +103,10 @@ RSpec.configure do |config|
|
|
102
103
|
end
|
103
104
|
```
|
104
105
|
|
105
|
-
### Clean up
|
106
|
+
### Clean up when you're done
|
106
107
|
|
107
|
-
|
108
|
-
|
109
|
-
also call `Mocktail.reset` after each test:
|
108
|
+
To reset Mocktail's internal state between tests and avoid test pollution, you
|
109
|
+
should also call `Mocktail.reset` after each test:
|
110
110
|
|
111
111
|
In Minitest:
|
112
112
|
|
@@ -131,8 +131,8 @@ end
|
|
131
131
|
|
132
132
|
## API
|
133
133
|
|
134
|
-
The public API is
|
135
|
-
source](lib/mocktail.rb).
|
134
|
+
The entire public API is listed in the [top-level module's
|
135
|
+
source](lib/mocktail.rb). Below is a longer menu to explain what goes into each
|
136
136
|
feature.
|
137
137
|
|
138
138
|
### Mocktail.of
|
@@ -284,7 +284,7 @@ user_repository.find(1) # => :not_found
|
|
284
284
|
`ignore_extra_args` will allow a demonstration to be considered satisfied even
|
285
285
|
if it fails to specify arguments and keyword arguments made by the actual call:
|
286
286
|
|
287
|
-
```
|
287
|
+
```ruby
|
288
288
|
stubs { user_repository.find(4) }.with { :a_person }
|
289
289
|
user_repository.find(4, debug: true) # => nil
|
290
290
|
|
@@ -490,7 +490,7 @@ verify { big_api.send(payload_captor.capture) } # => nil!
|
|
490
490
|
The `verify` above will pass because _a_ call did happen, but we haven't
|
491
491
|
asserted anything beyond that yet. What really happened is that
|
492
492
|
`payload_captor.capture` actually returned a matcher that will return true for
|
493
|
-
any argument _while also sneakily storing a copy of the argument value_.
|
493
|
+
any argument _while also sneakily storing a copy of the argument value_.
|
494
494
|
|
495
495
|
That's why we instantiated `payload_captor` with `Mocktail.captor` outside the
|
496
496
|
demonstration block, so we can inspect its `value` after the `verify` call:
|
@@ -513,6 +513,24 @@ When you call `Mocktail.replace(type)`, all of the singleton methods on the
|
|
513
513
|
provided type are replaced with fake methods available for stubbing and
|
514
514
|
verification. It's really that simple.
|
515
515
|
|
516
|
+
For example, if our `Bartender` class has a class method:
|
517
|
+
|
518
|
+
```ruby
|
519
|
+
class Bartender
|
520
|
+
def self.cliche_greeting
|
521
|
+
["It's 5 o'clock somewhere!", "Norm!"].sample
|
522
|
+
end
|
523
|
+
end
|
524
|
+
```
|
525
|
+
|
526
|
+
We can replace the behavior of the overall class, and then stub how we'd like it
|
527
|
+
to respond, in our test:
|
528
|
+
|
529
|
+
```ruby
|
530
|
+
Mocktail.replace(Bartender)
|
531
|
+
stubs { Bartender.cliche_greeting }.with { "Norm!" }
|
532
|
+
```
|
533
|
+
|
516
534
|
[**Obligatory warning:** Mocktail does its best to ensure that other threads
|
517
535
|
won't be affected when you replace the singleton methods on a type, but your
|
518
536
|
mileage may very! Singleton methods are global and code that introspects or
|
@@ -554,4 +572,3 @@ including (but not limited to) one-on-one communications, public posts/comments,
|
|
554
572
|
code reviews, pull requests, and GitHub issues. If violations occur, Test Double
|
555
573
|
will take any action they deem appropriate for the infraction, up to and
|
556
574
|
including blocking a user from the organization's repositories.
|
557
|
-
|
data/bin/console
CHANGED
@@ -29,6 +29,32 @@ class Auditor
|
|
29
29
|
def record!(message, user:, action: nil); end
|
30
30
|
end
|
31
31
|
|
32
|
+
class Shaker
|
33
|
+
def combine(*args); end
|
34
|
+
end
|
35
|
+
class Glass
|
36
|
+
def pour!(drink); end
|
37
|
+
end
|
38
|
+
class Bar
|
39
|
+
def pass(glass, to:)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Bartender
|
44
|
+
def initialize
|
45
|
+
@shaker = Shaker.new
|
46
|
+
@glass = Glass.new
|
47
|
+
@bar = Bar.new
|
48
|
+
end
|
49
|
+
|
50
|
+
def make_drink(name, customer:)
|
51
|
+
if name == :negroni
|
52
|
+
drink = @shaker.combine(:gin, :campari, :sweet_vermouth)
|
53
|
+
@glass.pour!(drink)
|
54
|
+
@bar.pass(@glass, to: customer)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
32
58
|
|
33
59
|
# (If you use this, don't forget to add pry to your Gemfile!)
|
34
60
|
require "pry"
|
data/lib/mocktail/version.rb
CHANGED
data/mocktail.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
|
9
9
|
spec.summary = "your objects, less potency"
|
10
10
|
spec.homepage = "https://github.com/testdouble/mocktail"
|
11
|
-
spec.required_ruby_version = Gem::Requirement.new(">=
|
11
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
|
12
12
|
|
13
13
|
spec.metadata["homepage_uri"] = spec.homepage
|
14
14
|
spec.metadata["source_code_uri"] = spec.homepage
|
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: 0.0.2
|
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-10-02 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -93,7 +93,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
96
|
+
version: 3.0.0
|
97
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
98
|
requirements:
|
99
99
|
- - ">="
|