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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8792bd9f9b3ccc36c01046557e63c390eccd97c53e25b54da6c54ca2690f839d
4
- data.tar.gz: 13c9444150fd0bd4098660f5960d268d37d67c8a27058834259dd2f1717933b6
3
+ metadata.gz: d5a4729eb601d9f3ed3ece213a460dd0881105a505a812c4d035c14cec252223
4
+ data.tar.gz: f4b3a3352f63de7de812ce8db616b570de740f1ce7550f8677eb47ecb99305fb
5
5
  SHA512:
6
- metadata.gz: 43a3c90e6edcbc9f04f2d4eb26e930a43dd8ac685f50d98d95f2c6128edd873776d02123258aa5e12766a1940e77e1704a252a18e5541bd52e8be3e5c6605d91
7
- data.tar.gz: 6c872542db03bc16e548c5d1f48b18229638f2553612c4e9897443e9f43d01d6056428eca76da9814a4c82823d658ce434ec88316d579b6aab7741d8dcbeff1c
6
+ metadata.gz: 469b39914d0d887b7cd8d25006f5dc6f3ca8a1a3b5594f39bfae5e3c21f36f2983a30ec2849c916129e216dd9fb731f476abbdc5a41ad0f80c740d7106793a7d
7
+ data.tar.gz: d7c24487a15c7ddc9b540d6c9d5c2b3f07da3599eba61eee128a79f5faa3fd5f6c4e54b7ea4318119a86737ead855f30d814a7f39e30c1e20a6f3880f0c21ce2
@@ -4,15 +4,21 @@ on: [push,pull_request]
4
4
 
5
5
  jobs:
6
6
  build:
7
- runs-on: ubuntu-latest
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: 3.0.1
19
+ ruby-version: ${{ matrix.ruby-version }}
14
20
  - name: Run the default task
15
21
  run: |
16
- gem install bundler -v 2.2.15
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mocktail (0.0.1)
4
+ mocktail (0.0.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -49,6 +49,7 @@ GEM
49
49
 
50
50
  PLATFORMS
51
51
  arm64-darwin-20
52
+ ruby
52
53
 
53
54
  DEPENDENCIES
54
55
  minitest
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. It offers a simple API and robust feature-set.
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
- ## First, an aperitif
13
+ ## An aperitif
12
14
 
13
15
  Before getting into the details, let's demonstrate what Mocktail's API looks
14
- like. Suppose you have a class `Negroni`:
16
+ like. Suppose you want to test a `Bartender` class:
15
17
 
16
18
  ```ruby
17
- class Negroni
18
- def self.ingredients
19
- [:gin, :campari, :sweet_vermouth]
20
- end
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 sip(amount)
27
- raise "unimplemented"
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
- 1. Create a mocked instance: `negroni = Mocktail.of(Negroni)`
33
- 2. Stub a response with `stubs { negroni.sip(4) }.with { :ahh }`
34
- * Calling `negroni.sip(4)` will subsequently return `:ahh`
35
- * Another example: `stubs { |m| negroni.sip(m.numeric) }.with { :nice }`
36
- 3. Verify a call with `verify { negroni.shake!(:some_shaker) }`
37
- * `verify` will raise an error unless `negroni.shake!(:some_shaker)` has
38
- been called
39
- * Another example: `verify { |m| negroni.shake!(m.that { |arg|
40
- arg.respond_to?(:mix) }) }`
41
- 4. Deliver a mock to your code under test with `negroni =
42
- Mocktail.of_next(Negroni)`
43
- * `of_next` will return a fake `Negroni`
44
- * The next call to `Negroni.new` will return _exactly the same_ fake
45
- instance, allowing the code being tested to seamlessly instantiate and
46
- interact with it
47
- * This means no dependency injection is necessary, nor is a sweeping
48
- override like
49
- [any_instance](https://relishapp.com/rspec/rspec-mocks/docs/working-with-legacy-code/any-instance)
50
- * `Negroni.new` will be unaffected on other threads and will continue
51
- behaving like normal as soon as the next `new` call
52
-
53
- Mocktail can do a whole lot more than this, and was also designed with
54
- descriptive error messages and common edge cases in mind:
55
-
56
- * Entire classes and modules can be replaced with `Mocktail.replace(type)` while
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
- * Arity of arguments and keyword arguments is enforced on faked methods to
59
- prevent isolated unit tests from continuing to pass after an API contract
60
- changes
61
- * For mocked methods that take a block, `stubs` & `verify` can inspect and
62
- invoke the passed block to determine whether the call satisfies their
63
- conditions
64
- * Dynamic stubbings that return a value based on how the mocked method was
65
- called
66
- * Advanced stubbing and verification options like specifying the number of
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
- ### Add the DSL
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 after each test
106
+ ### Clean up when you're done
106
107
 
107
- When making so many concoctions, it's important to keep a clean bar! To reset
108
- Mocktail's internal state between tests and avoid test pollution, you should
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 a pretty quick read of the [top-level module's
135
- source](lib/mocktail.rb). Here's a longer menu to explain what goes into each
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"
@@ -1,3 +1,3 @@
1
1
  module Mocktail
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
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(">= 2.7.0")
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.1
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-09-22 00:00:00.000000000 Z
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: 2.7.0
96
+ version: 3.0.0
97
97
  required_rubygems_version: !ruby/object:Gem::Requirement
98
98
  requirements:
99
99
  - - ">="