mocktail 1.2.0 → 1.2.1

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: 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