mocktail 1.2.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c303d4fb9ecc3a03e30fcfb52425cec2c4f3b2ca2ad3dadda3f9a09c1b8785a
4
- data.tar.gz: 90b573a3b6417d1c084fa7ced22af80b0533e36c45240d42c9627b3bfde2a032
3
+ metadata.gz: 0d2944636158dbc47b6da4246d28303f274e2740b7d61ccf3359ff1ce668ab40
4
+ data.tar.gz: ea0aa33b02f8f9dac5be81bf6a5a394d181f078127a7b9e22fb55d5c30c85808
5
5
  SHA512:
6
- metadata.gz: f2aa7140202bfb3d5cadc45964f344cce19ba688d807b9c25b2033d27dd15710424fc00315633129429215892ea149160d2fbba2a8671bb2237867b12ce7c225
7
- data.tar.gz: 95f1c02d8f23849f0a5932e8cddb3f811dc01962e13e0553f1964ebf9f77489b49ce375506e3f2a5f9132bcda7493b2ffc8a03dc61dd4f5018f84fe33995245f
6
+ metadata.gz: 15456b62e2fe1157c17b694c9c8d83d1fa42fa64f4cc3a3aa998a3d21ccf8ae13320694ed3ed1ac500a1636210ae9b153b1070fa427ed9dfd04e5a43b29d56b6
7
+ data.tar.gz: 193f8fca67cfb78fc2eefc21b0c70cf5f9ddb9028b0ff948472c119ea0350dda587a4a150e7b8cc283ed9cfe4f8e451a323f9517517d73dbfd84a20cedfce2ed
data/.standard.yml CHANGED
@@ -1 +1 @@
1
- ruby_version: 2.7
1
+ ruby_version: 3.0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ # 1.2.2
2
+
3
+ * As promised in 1.2.1, there were bugs. [#19](https://github.com/testdouble/mocktail/pull/19)
4
+
5
+ # 1.2.1
6
+
7
+ * Adds support for faking methods that use options hashes that are called with
8
+ and without curly braces [#17](https://github.com/testdouble/mocktail/pull/17)
9
+ (This is a sweeping change and there will probably be bugs.)
10
+
1
11
  # 1.2.0
2
12
 
3
13
  * Introduce the Mocktail.calls() API https://github.com/testdouble/mocktail/pull/16
data/Gemfile CHANGED
@@ -8,3 +8,4 @@ gem "minitest"
8
8
  gem "standard"
9
9
  gem "pry"
10
10
  gem "simplecov"
11
+ gem "m"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mocktail (1.2.0)
4
+ mocktail (1.2.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -9,30 +9,35 @@ GEM
9
9
  ast (2.4.2)
10
10
  coderay (1.1.3)
11
11
  docile (1.4.0)
12
+ json (2.6.2)
13
+ m (1.6.1)
14
+ method_source (>= 0.6.7)
15
+ rake (>= 0.9.2.2)
12
16
  method_source (1.0.0)
13
- minitest (5.15.0)
17
+ minitest (5.16.3)
14
18
  parallel (1.22.1)
15
- parser (3.1.2.0)
19
+ parser (3.1.2.1)
16
20
  ast (~> 2.4.1)
17
21
  pry (0.14.1)
18
22
  coderay (~> 1.1)
19
23
  method_source (~> 1.0)
20
24
  rainbow (3.1.1)
21
25
  rake (13.0.6)
22
- regexp_parser (2.3.0)
26
+ regexp_parser (2.6.0)
23
27
  rexml (3.2.5)
24
- rubocop (1.27.0)
28
+ rubocop (1.39.0)
29
+ json (~> 2.3)
25
30
  parallel (~> 1.10)
26
- parser (>= 3.1.0.0)
31
+ parser (>= 3.1.2.1)
27
32
  rainbow (>= 2.2.2, < 4.0)
28
33
  regexp_parser (>= 1.8, < 3.0)
29
- rexml
30
- rubocop-ast (>= 1.16.0, < 2.0)
34
+ rexml (>= 3.2.5, < 4.0)
35
+ rubocop-ast (>= 1.23.0, < 2.0)
31
36
  ruby-progressbar (~> 1.7)
32
37
  unicode-display_width (>= 1.4.0, < 3.0)
33
- rubocop-ast (1.17.0)
38
+ rubocop-ast (1.23.0)
34
39
  parser (>= 3.1.1.0)
35
- rubocop-performance (1.13.3)
40
+ rubocop-performance (1.15.0)
36
41
  rubocop (>= 1.7.0, < 2.0)
37
42
  rubocop-ast (>= 0.4.0)
38
43
  ruby-progressbar (1.11.0)
@@ -42,16 +47,17 @@ GEM
42
47
  simplecov_json_formatter (~> 0.1)
43
48
  simplecov-html (0.12.3)
44
49
  simplecov_json_formatter (0.1.4)
45
- standard (1.10.0)
46
- rubocop (= 1.27.0)
47
- rubocop-performance (= 1.13.3)
48
- unicode-display_width (2.1.0)
50
+ standard (1.18.0)
51
+ rubocop (= 1.39.0)
52
+ rubocop-performance (= 1.15.0)
53
+ unicode-display_width (2.3.0)
49
54
 
50
55
  PLATFORMS
51
56
  arm64-darwin-20
52
57
  ruby
53
58
 
54
59
  DEPENDENCIES
60
+ m
55
61
  minitest
56
62
  mocktail!
57
63
  pry
data/README.md CHANGED
@@ -814,11 +814,10 @@ which call site produced the unexpected `nil` value.
814
814
  ### Mocktail.calls
815
815
 
816
816
  When practicing test-driven development, you may want to ensure that a
817
- dependency wasn't called at all, and don't particularly care about the
818
- parameters. To provide a terse way to express this, Mocktail offers a top-level
819
- `calls(double, method_name = nil)` method that returns an array of the calls to
820
- the mock (optionally filtered to a particular method name) in the order they
821
- were called.
817
+ dependency wasn't called at all. To provide a terse way to express this,
818
+ Mocktail offers a top-level `calls(double, method_name = nil)` method that
819
+ returns an array of the calls to the mock (optionally filtered to a
820
+ particular method name) in the order they were called.
822
821
 
823
822
  Suppose you were writing a test of this method for example:
824
823
 
data/bin/m ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'm' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("m", "m")
@@ -0,0 +1,47 @@
1
+ module Mocktail
2
+ class ReconstructsCall
3
+ def reconstruct(double:, call_binding:, default_args:, dry_class:, type:, method:, original_method:, signature:)
4
+ Call.new(
5
+ singleton: false,
6
+ double: double,
7
+ original_type: type,
8
+ dry_type: dry_class,
9
+ method: method,
10
+ original_method: original_method,
11
+ args: args_for(signature, call_binding, default_args),
12
+ kwargs: kwargs_for(signature, call_binding, default_args),
13
+ block: call_binding.local_variable_get(signature.block_param || ::Mocktail::Signature::DEFAULT_BLOCK_PARAM)
14
+ )
15
+ end
16
+
17
+ private
18
+
19
+ def args_for(signature, call_binding, default_args)
20
+ arg_names, rest_name = non_default_args(signature.positional_params, default_args)
21
+
22
+ arg_values = arg_names.map { |p| call_binding.local_variable_get(p) }
23
+ rest_value = call_binding.local_variable_get(rest_name) if rest_name
24
+
25
+ arg_values + (rest_value || [])
26
+ end
27
+
28
+ def kwargs_for(signature, call_binding, default_args)
29
+ kwarg_names, kwrest_name = non_default_args(signature.keyword_params, default_args)
30
+
31
+ kwarg_values = kwarg_names.to_h { |p| [p, call_binding.local_variable_get(p)] }
32
+ kwrest_value = call_binding.local_variable_get(kwrest_name) if kwrest_name
33
+
34
+ kwarg_values.merge(kwrest_value || {})
35
+ end
36
+
37
+ def non_default_args(params, default_args)
38
+ named_args = params.allowed
39
+ .reject { |p| default_args&.key?(p) }
40
+ rest_arg = if params.rest && !default_args&.key?(params.rest)
41
+ params.rest
42
+ end
43
+
44
+ [named_args, rest_arg]
45
+ end
46
+ end
47
+ end
@@ -1,8 +1,11 @@
1
+ require_relative "declares_dry_class/reconstructs_call"
2
+
1
3
  module Mocktail
2
4
  class DeclaresDryClass
3
5
  def initialize
4
- @handles_dry_call = HandlesDryCall.new
5
6
  @raises_neato_no_method_error = RaisesNeatoNoMethodError.new
7
+ @transforms_params = TransformsParams.new
8
+ @stringifies_method_signature = StringifiesMethodSignature.new
6
9
  end
7
10
 
8
11
  def declare(type, instance_methods)
@@ -39,24 +42,31 @@ module Mocktail
39
42
  private
40
43
 
41
44
  def define_double_methods!(dry_class, type, instance_methods)
42
- handles_dry_call = @handles_dry_call
43
45
  instance_methods.each do |method|
44
46
  dry_class.undef_method(method) if dry_class.method_defined?(method)
45
-
46
- dry_class.define_method method, ->(*args, **kwargs, &block) {
47
- Debug.guard_against_mocktail_accidentally_calling_mocks_if_debugging!
48
- handles_dry_call.handle(Call.new(
49
- singleton: false,
50
- double: self,
51
- original_type: type,
52
- dry_type: dry_class,
53
- method: method,
54
- original_method: type.instance_method(method),
55
- args: args,
56
- kwargs: kwargs,
57
- block: block
58
- ))
47
+ parameters = type.instance_method(method).parameters
48
+ signature = @transforms_params.transform(Call.new, params: parameters)
49
+ method_signature = @stringifies_method_signature.stringify(signature)
50
+ __mocktail_closure = {
51
+ dry_class: dry_class,
52
+ type: type,
53
+ method: method,
54
+ original_method: type.instance_method(method),
55
+ signature: signature
59
56
  }
57
+
58
+ dry_class.define_method method,
59
+ eval(<<-RUBBY, binding, __FILE__, __LINE__ + 1) # standard:disable Security/Eval
60
+ ->#{method_signature} do
61
+ ::Mocktail::Debug.guard_against_mocktail_accidentally_calling_mocks_if_debugging!
62
+ ::Mocktail::HandlesDryCall.new.handle(::Mocktail::ReconstructsCall.new.reconstruct(
63
+ double: self,
64
+ call_binding: __send__(:binding),
65
+ default_args: (__send__(:binding).local_variable_defined?(:__mocktail_default_args) ? __send__(:binding).local_variable_get(:__mocktail_default_args) : {}),
66
+ **__mocktail_closure
67
+ ))
68
+ end
69
+ RUBBY
60
70
  end
61
71
  end
62
72
 
@@ -71,7 +71,7 @@ module Mocktail
71
71
 
72
72
  <<~MSG
73
73
 
74
- There #{corrections.size == 1 ? "is" : "are"} also #{corrections.size} similar method#{"s" if corrections.size != 1} on #{call.original_type.name}.
74
+ There #{(corrections.size == 1) ? "is" : "are"} also #{corrections.size} similar method#{"s" if corrections.size != 1} on #{call.original_type.name}.
75
75
 
76
76
  Did you mean?
77
77
  #{corrections.map { |c| " #{c}" }.join("\n")}
@@ -30,7 +30,7 @@ module Mocktail
30
30
  }
31
31
  end
32
32
 
33
- mocktails.size == 1 ? mocktails.first : mocktails
33
+ (mocktails.size == 1) ? mocktails.first : mocktails
34
34
  end
35
35
  end
36
36
  end
@@ -15,6 +15,7 @@ module Mocktail
15
15
  private
16
16
 
17
17
  def args_match?(real_args, demo_args, ignore_extra_args)
18
+ # Guard clause for performance:
18
19
  return true if ignore_extra_args && demo_args.empty?
19
20
 
20
21
  (
@@ -2,8 +2,8 @@ require_relative "../share/bind"
2
2
 
3
3
  module Mocktail
4
4
  class TransformsParams
5
- def transform(dry_call)
6
- params = dry_call.original_method.parameters
5
+ def transform(dry_call, params: dry_call.original_method.parameters)
6
+ params = name_unnamed_params(params)
7
7
 
8
8
  Signature.new(
9
9
  positional_params: Params.new(
@@ -12,7 +12,7 @@ module Mocktail
12
12
  }.map { |_, name| name },
13
13
  required: params.select { |t, _| Bind.call(t, :==, :req) }.map { |_, n| n },
14
14
  optional: params.select { |t, _| Bind.call(t, :==, :opt) }.map { |_, n| n },
15
- rest: params.find { |t, _| Bind.call(t, :==, :rest) } & [1]
15
+ rest: params.find { |t, _| Bind.call(t, :==, :rest) }&.last
16
16
  ),
17
17
  positional_args: dry_call.args,
18
18
 
@@ -22,13 +22,25 @@ module Mocktail
22
22
  }.map { |_, name| name },
23
23
  required: params.select { |t, _| Bind.call(t, :==, :keyreq) }.map { |_, n| n },
24
24
  optional: params.select { |t, _| Bind.call(t, :==, :key) }.map { |_, n| n },
25
- rest: params.find { |t, _| Bind.call(t, :==, :keyrest) } & [1]
25
+ rest: params.find { |t, _| Bind.call(t, :==, :keyrest) }&.last
26
26
  ),
27
27
  keyword_args: dry_call.kwargs,
28
28
 
29
- block_param: params.find { |t, _| Bind.call(t, :==, :block) } & [1],
29
+ block_param: params.find { |t, _| Bind.call(t, :==, :block) }&.last,
30
30
  block_arg: dry_call.block
31
31
  )
32
32
  end
33
+
34
+ private
35
+
36
+ def name_unnamed_params(params)
37
+ params.map.with_index { |param, i|
38
+ if param.size == 1
39
+ param + ["unnamed_arg_#{i + 1}"]
40
+ else
41
+ param
42
+ end
43
+ }
44
+ end
33
45
  end
34
46
  end
@@ -0,0 +1,45 @@
1
+ module Mocktail
2
+ class StringifiesMethodSignature
3
+ def stringify(signature)
4
+ positional_params = positional(signature)
5
+ keyword_params = keyword(signature)
6
+ block_param = block(signature)
7
+
8
+ "(#{[positional_params, keyword_params, block_param].compact.join(", ")})"
9
+ end
10
+
11
+ private
12
+
13
+ def positional(signature)
14
+ params = signature.positional_params.all.map do |name|
15
+ if signature.positional_params.allowed.include?(name)
16
+ "#{name} = ((__mocktail_default_args ||= {})[:#{name}] = nil)"
17
+ elsif signature.positional_params.rest == name
18
+ "*#{(name == :*) ? Signature::DEFAULT_REST_ARGS : name}"
19
+ end
20
+ end.compact
21
+
22
+ params.join(", ") if params.any?
23
+ end
24
+
25
+ def keyword(signature)
26
+ params = signature.keyword_params.all.map do |name|
27
+ if signature.keyword_params.allowed.include?(name)
28
+ "#{name}: ((__mocktail_default_args ||= {})[:#{name}] = nil)"
29
+ elsif signature.keyword_params.rest == name
30
+ "**#{(name == :**) ? Signature::DEFAULT_REST_KWARGS : name}"
31
+ end
32
+ end.compact
33
+
34
+ params.join(", ") if params.any?
35
+ end
36
+
37
+ def block(signature)
38
+ if signature.block_param && signature.block_param != :&
39
+ "&#{signature.block_param}"
40
+ else
41
+ "&#{Signature::DEFAULT_BLOCK_PARAM}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -8,6 +8,9 @@ module Mocktail
8
8
  :block_arg,
9
9
  keyword_init: true
10
10
  )
11
+ DEFAULT_REST_ARGS = "args"
12
+ DEFAULT_REST_KWARGS = "kwargs"
13
+ DEFAULT_BLOCK_PARAM = "blk"
11
14
  end
12
15
 
13
16
  class Params < Struct.new(
@@ -26,7 +29,7 @@ module Mocktail
26
29
  end
27
30
 
28
31
  def allowed
29
- required + optional
32
+ all.select { |name| required.include?(name) || optional.include?(name) }
30
33
  end
31
34
 
32
35
  def rest?
@@ -1,3 +1,3 @@
1
1
  module Mocktail
2
- VERSION = "1.2.0"
2
+ VERSION = "1.2.2"
3
3
  end
data/lib/mocktail.rb CHANGED
@@ -17,6 +17,7 @@ require_relative "mocktail/replaces_next"
17
17
  require_relative "mocktail/replaces_type"
18
18
  require_relative "mocktail/resets_state"
19
19
  require_relative "mocktail/simulates_argument_error"
20
+ require_relative "mocktail/stringifies_method_signature"
20
21
  require_relative "mocktail/value"
21
22
  require_relative "mocktail/verifies_call"
22
23
  require_relative "mocktail/version"
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.2.0
4
+ version: 1.2.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: 2022-09-21 00:00:00.000000000 Z
11
+ date: 2022-11-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -27,6 +27,7 @@ files:
27
27
  - README.md
28
28
  - Rakefile
29
29
  - bin/console
30
+ - bin/m
30
31
  - bin/rake
31
32
  - bin/setup
32
33
  - lib/mocktail.rb
@@ -47,6 +48,7 @@ files:
47
48
  - lib/mocktail/imitates_type/ensures_imitation_support.rb
48
49
  - lib/mocktail/imitates_type/makes_double.rb
49
50
  - lib/mocktail/imitates_type/makes_double/declares_dry_class.rb
51
+ - lib/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb
50
52
  - lib/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb
51
53
  - lib/mocktail/initializes_mocktail.rb
52
54
  - lib/mocktail/matcher_presentation.rb
@@ -79,6 +81,7 @@ files:
79
81
  - lib/mocktail/simulates_argument_error/reconciles_args_with_params.rb
80
82
  - lib/mocktail/simulates_argument_error/recreates_message.rb
81
83
  - lib/mocktail/simulates_argument_error/transforms_params.rb
84
+ - lib/mocktail/stringifies_method_signature.rb
82
85
  - lib/mocktail/value.rb
83
86
  - lib/mocktail/value/cabinet.rb
84
87
  - lib/mocktail/value/call.rb