mocktail 1.2.0 → 1.2.1

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: f98c176c091bd92d1c3f28be3c8c3eea66cbc3f3b56f9a2fac3f7f78d5eb2775
4
+ data.tar.gz: 9422af274277a791b225540f74e3c77f99aff75b908fb1529c56d6f47e35a27f
5
5
  SHA512:
6
- metadata.gz: f2aa7140202bfb3d5cadc45964f344cce19ba688d807b9c25b2033d27dd15710424fc00315633129429215892ea149160d2fbba2a8671bb2237867b12ce7c225
7
- data.tar.gz: 95f1c02d8f23849f0a5932e8cddb3f811dc01962e13e0553f1964ebf9f77489b49ce375506e3f2a5f9132bcda7493b2ffc8a03dc61dd4f5018f84fe33995245f
6
+ metadata.gz: bc5ae9538c17efdc5675c1f19ca1daaa72dea52162f469fd4a295e68d6d5755792f8533a4c7244d0146e82edfb688dab3a608ea449ba57da3b56ac2e15c75f48
7
+ data.tar.gz: 381e52bf6c28151c0ab1dfba3f300df8cd8bc5cf218ddcc735e7dfdced93649f5f4e299fffefc449eb24cf3589c4067d23f7cffb313262c54409290ca10ac1a2
data/.standard.yml CHANGED
@@ -1 +1 @@
1
- ruby_version: 2.7
1
+ ruby_version: 3.0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 1.2.1
2
+
3
+ * Adds support for faking methods that use options hashes that are called with
4
+ and without curly braces [#17](https://github.com/testdouble/mocktail/pull/17)
5
+ (This is a sweeping change and there will probably be bugs.)
6
+
1
7
  # 1.2.0
2
8
 
3
9
  * Introduce the Mocktail.calls() API https://github.com/testdouble/mocktail/pull/16
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.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -9,30 +9,32 @@ GEM
9
9
  ast (2.4.2)
10
10
  coderay (1.1.3)
11
11
  docile (1.4.0)
12
+ json (2.6.2)
12
13
  method_source (1.0.0)
13
- minitest (5.15.0)
14
+ minitest (5.16.3)
14
15
  parallel (1.22.1)
15
- parser (3.1.2.0)
16
+ parser (3.1.2.1)
16
17
  ast (~> 2.4.1)
17
18
  pry (0.14.1)
18
19
  coderay (~> 1.1)
19
20
  method_source (~> 1.0)
20
21
  rainbow (3.1.1)
21
22
  rake (13.0.6)
22
- regexp_parser (2.3.0)
23
+ regexp_parser (2.6.0)
23
24
  rexml (3.2.5)
24
- rubocop (1.27.0)
25
+ rubocop (1.39.0)
26
+ json (~> 2.3)
25
27
  parallel (~> 1.10)
26
- parser (>= 3.1.0.0)
28
+ parser (>= 3.1.2.1)
27
29
  rainbow (>= 2.2.2, < 4.0)
28
30
  regexp_parser (>= 1.8, < 3.0)
29
- rexml
30
- rubocop-ast (>= 1.16.0, < 2.0)
31
+ rexml (>= 3.2.5, < 4.0)
32
+ rubocop-ast (>= 1.23.0, < 2.0)
31
33
  ruby-progressbar (~> 1.7)
32
34
  unicode-display_width (>= 1.4.0, < 3.0)
33
- rubocop-ast (1.17.0)
35
+ rubocop-ast (1.23.0)
34
36
  parser (>= 3.1.1.0)
35
- rubocop-performance (1.13.3)
37
+ rubocop-performance (1.15.0)
36
38
  rubocop (>= 1.7.0, < 2.0)
37
39
  rubocop-ast (>= 0.4.0)
38
40
  ruby-progressbar (1.11.0)
@@ -42,10 +44,10 @@ GEM
42
44
  simplecov_json_formatter (~> 0.1)
43
45
  simplecov-html (0.12.3)
44
46
  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)
47
+ standard (1.18.0)
48
+ rubocop (= 1.39.0)
49
+ rubocop-performance (= 1.15.0)
50
+ unicode-display_width (2.3.0)
49
51
 
50
52
  PLATFORMS
51
53
  arm64-darwin-20
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
 
@@ -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,9 +2,7 @@ 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
7
-
5
+ def transform(dry_call, params: dry_call.original_method.parameters)
8
6
  Signature.new(
9
7
  positional_params: Params.new(
10
8
  all: params.select { |t, _|
@@ -12,7 +10,7 @@ module Mocktail
12
10
  }.map { |_, name| name },
13
11
  required: params.select { |t, _| Bind.call(t, :==, :req) }.map { |_, n| n },
14
12
  optional: params.select { |t, _| Bind.call(t, :==, :opt) }.map { |_, n| n },
15
- rest: params.find { |t, _| Bind.call(t, :==, :rest) } & [1]
13
+ rest: params.find { |t, _| Bind.call(t, :==, :rest) }&.last
16
14
  ),
17
15
  positional_args: dry_call.args,
18
16
 
@@ -22,11 +20,11 @@ module Mocktail
22
20
  }.map { |_, name| name },
23
21
  required: params.select { |t, _| Bind.call(t, :==, :keyreq) }.map { |_, n| n },
24
22
  optional: params.select { |t, _| Bind.call(t, :==, :key) }.map { |_, n| n },
25
- rest: params.find { |t, _| Bind.call(t, :==, :keyrest) } & [1]
23
+ rest: params.find { |t, _| Bind.call(t, :==, :keyrest) }&.last
26
24
  ),
27
25
  keyword_args: dry_call.kwargs,
28
26
 
29
- block_param: params.find { |t, _| Bind.call(t, :==, :block) } & [1],
27
+ block_param: params.find { |t, _| Bind.call(t, :==, :block) }&.last,
30
28
  block_arg: dry_call.block
31
29
  )
32
30
  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.1"
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.1
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-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -47,6 +47,7 @@ files:
47
47
  - lib/mocktail/imitates_type/ensures_imitation_support.rb
48
48
  - lib/mocktail/imitates_type/makes_double.rb
49
49
  - lib/mocktail/imitates_type/makes_double/declares_dry_class.rb
50
+ - lib/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb
50
51
  - lib/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb
51
52
  - lib/mocktail/initializes_mocktail.rb
52
53
  - lib/mocktail/matcher_presentation.rb
@@ -79,6 +80,7 @@ files:
79
80
  - lib/mocktail/simulates_argument_error/reconciles_args_with_params.rb
80
81
  - lib/mocktail/simulates_argument_error/recreates_message.rb
81
82
  - lib/mocktail/simulates_argument_error/transforms_params.rb
83
+ - lib/mocktail/stringifies_method_signature.rb
82
84
  - lib/mocktail/value.rb
83
85
  - lib/mocktail/value/cabinet.rb
84
86
  - lib/mocktail/value/call.rb