mocktail 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
- - ">="
|