mocktail 1.2.0 → 1.2.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: 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