mocktail 1.1.1 → 1.1.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: 3f1b0c2f99097d0f23cb67928b5455ce5acf9fad24baee1dff64c748b60a8da9
4
- data.tar.gz: 9595e5e672d13711a45447b8242977189700e6d0a044f9553e75d642e455f898
3
+ metadata.gz: 7eb64f90875e53a9bfd5b4d995f8526eb6695dd9bd8a88c850b02eed851cb96d
4
+ data.tar.gz: ae2b4443a3740c375c70e7e490cbe3463d9672f17665ae47930b6ee614e9891d
5
5
  SHA512:
6
- metadata.gz: 5469cfd8d09da0ba752c60529cfc016acecea8b69107d07a467e576812e1d1abf74d05f3431228c4789bf3397059e249a3307dd788a7c4bce410a85f8d86d0ee
7
- data.tar.gz: c7dea9981f4ed8f76b4f5595c0dd88d747f30371d6c5c6245f8bd498864850700e5ca9f1c1af06b67879b24bcdf90e14d5cfd7b2111838580f3fbd4554b3cbec
6
+ metadata.gz: de8d47071e93c4d406391a1155dea9b6a1a9ac89f56422ae29e6dda5f134c9e5e724ec8006aba833b5ff19d9d2dff4550384bc349506f5ab9cefe3fd2185af1d
7
+ data.tar.gz: 5bf88d0aadc08fe9a9380312cf491146439e72e335091e2e79849196b74e93aba20b0fac2d122152a4661008ee243b5b3b39cb6408fc776b2cc2567ffca0924f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mocktail (1.1.1)
4
+ mocktail (1.1.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -10,6 +10,11 @@ library for Ruby that provides a terse and robust API for creating mocks,
10
10
  getting them in the hands of the code you're testing, stub & verify behavior,
11
11
  and even safely override class methods.
12
12
 
13
+ If you'd prefer a voice & video introduction to Mocktail aside from this README,
14
+ you might enjoy this ⚡️[Lightning
15
+ Talk](https://blog.testdouble.com/talks/2022-05-18-please-mock-me?utm_source=twitter&utm_medium=organic-social&utm_campaign=conf-talk)⚡️
16
+ from RailsConf 2022.
17
+
13
18
  ## An aperitif
14
19
 
15
20
  Before getting into the details, let's demonstrate what Mocktail's API looks
@@ -841,7 +846,7 @@ Talks:
841
846
  ## Acknowledgements
842
847
 
843
848
  Mocktail is created & maintained by the software agency [Test
844
- Double](https://twitter.com). If you've ever come across our eponymously-named
849
+ Double](https://testdouble.com). If you've ever come across our eponymously-named
845
850
  [testdouble.js](https://github.com/testdouble/testdouble.js/), you might find
846
851
  Mocktail's API to be quite similar. The term "test double" was originally coined
847
852
  by Gerard Meszaros in his book [xUnit Test
@@ -0,0 +1,49 @@
1
+ module Mocktail
2
+ module Debug
3
+ # It would be easy and bad for the mocktail lib to call something like
4
+ #
5
+ # double == other_double
6
+ #
7
+ # But if it's a double, that means anyone who stubs that method could change
8
+ # the internal behavior of the library in unexpected ways (as happened here:
9
+ # https://github.com/testdouble/mocktail/issues/7 )
10
+ #
11
+ # For that reason when we run our tests, we also want to blow up if this
12
+ # happens unintentionally. This works in conjunction with the test
13
+ # MockingMethodfulClassesTest, because it mocks every defined method on the
14
+ # mocked BasicObject
15
+ def self.guard_against_mocktail_accidentally_calling_mocks_if_debugging!
16
+ return unless ENV["MOCKTAIL_DEBUG_ACCIDENTAL_INTERNAL_MOCK_CALLS"]
17
+ raise
18
+ rescue => e
19
+ base_path = Pathname.new(__FILE__).dirname.to_s
20
+ backtrace_minus_this_and_whoever_called_this = e.backtrace[2..]
21
+ internal_call_sites = backtrace_minus_this_and_whoever_called_this.take_while { |call_site|
22
+ # the "in `block" is very confusing but necessary to include lines after
23
+ # a stubs { blah.foo }.with { … } call, since that's when most of the
24
+ # good stuff happens
25
+ call_site.start_with?(base_path) || call_site.include?("in `block")
26
+ }.reject { |call_site| call_site.include?("in `block") }
27
+
28
+ approved_call_sites = [
29
+ "fulfills_stubbing.rb:14",
30
+ "validates_arguments.rb:16",
31
+ "validates_arguments.rb:19"
32
+ ]
33
+ if internal_call_sites.any? && approved_call_sites.none? { |approved_call_site|
34
+ internal_call_sites.first.include?(approved_call_site)
35
+ }
36
+ raise Error.new <<~MSG
37
+ Unauthorized internal call of a mock internally by Mocktail itself:
38
+
39
+ #{internal_call_sites.first}
40
+
41
+ Offending call's complete stack trace:
42
+
43
+ #{backtrace_minus_this_and_whoever_called_this.join("\n")}
44
+ ==END OFFENDING TRACE==
45
+ MSG
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,17 +1,19 @@
1
1
  require_relative "../../share/cleans_backtrace"
2
+ require_relative "../../share/compares_safely"
2
3
 
3
4
  module Mocktail
4
5
  class DescribesUnsatisfiedStubbing
5
6
  def initialize
6
7
  @cleans_backtrace = CleansBacktrace.new
8
+ @compares_safely = ComparesSafely.new
7
9
  end
8
10
 
9
11
  def describe(dry_call)
10
12
  UnsatisfyingCall.new(
11
13
  call: dry_call,
12
14
  other_stubbings: Mocktail.cabinet.stubbings.select { |stubbing|
13
- dry_call.double == stubbing.recording.double &&
14
- dry_call.method == stubbing.recording.method
15
+ @compares_safely.compare(dry_call.double, stubbing.recording.double) &&
16
+ @compares_safely.compare(dry_call.method, stubbing.recording.method)
15
17
  },
16
18
  backtrace: @cleans_backtrace.clean(Error.new).backtrace
17
19
  )
@@ -41,7 +41,10 @@ module Mocktail
41
41
  def define_double_methods!(dry_class, type, instance_methods)
42
42
  handles_dry_call = @handles_dry_call
43
43
  instance_methods.each do |method|
44
+ dry_class.undef_method(method) if dry_class.method_defined?(method)
45
+
44
46
  dry_class.define_method method, ->(*args, **kwargs, &block) {
47
+ Debug.guard_against_mocktail_accidentally_calling_mocks_if_debugging!
45
48
  handles_dry_call.handle(Call.new(
46
49
  singleton: false,
47
50
  double: self,
@@ -0,0 +1,7 @@
1
+ module Mocktail
2
+ class ComparesSafely
3
+ def compare(thing, other_thing)
4
+ Object.instance_method(:==).bind_call(thing, other_thing)
5
+ end
6
+ end
7
+ end
@@ -1,8 +1,14 @@
1
+ require_relative "compares_safely"
2
+
1
3
  module Mocktail
2
4
  class DeterminesMatchingCalls
5
+ def initialize
6
+ @compares_safely = ComparesSafely.new
7
+ end
8
+
3
9
  def determine(real_call, demo_call, demo_config)
4
- real_call.double == demo_call.double &&
5
- real_call.method == demo_call.method &&
10
+ @compares_safely.compare(real_call.double, demo_call.double) &&
11
+ @compares_safely.compare(real_call.method, demo_call.method) &&
6
12
 
7
13
  # Matcher implementation will replace this:
8
14
  args_match?(real_call.args, demo_call.args, demo_config.ignore_extra_args) &&
@@ -53,7 +59,7 @@ module Mocktail
53
59
  demo_arg.is_mocktail_matcher?
54
60
  demo_arg.match?(real_arg)
55
61
  else
56
- demo_arg == real_arg
62
+ demo_arg == real_arg # TODO <-- test if mock object and call safe compare if so, otherwise ==
57
63
  end
58
64
  end
59
65
  end
@@ -1,16 +1,22 @@
1
+ require_relative "../share/compares_safely"
2
+
1
3
  module Mocktail
2
4
  class TransformsParams
5
+ def initialize
6
+ @compares_safely = ComparesSafely.new
7
+ end
8
+
3
9
  def transform(dry_call)
4
10
  params = dry_call.original_method.parameters
5
11
 
6
12
  Signature.new(
7
13
  positional_params: Params.new(
8
- all: params.select { |type, _|
9
- [:req, :opt, :rest].include?(type)
14
+ all: params.select { |t, _|
15
+ [:req, :opt, :rest].any? { |param_type| @compares_safely.compare(t, param_type) }
10
16
  }.map { |_, name| name },
11
- required: params.select { |t, _| t == :req }.map { |_, n| n },
12
- optional: params.select { |t, _| t == :opt }.map { |_, n| n },
13
- rest: params.find { |type, _| type == :rest } & [1]
17
+ required: params.select { |t, _| @compares_safely.compare(t, :req) }.map { |_, n| n },
18
+ optional: params.select { |t, _| @compares_safely.compare(t, :opt) }.map { |_, n| n },
19
+ rest: params.find { |t, _| @compares_safely.compare(t, :rest) } & [1]
14
20
  ),
15
21
  positional_args: dry_call.args,
16
22
 
@@ -18,13 +24,13 @@ module Mocktail
18
24
  all: params.select { |type, _|
19
25
  [:keyreq, :key, :keyrest].include?(type)
20
26
  }.map { |_, name| name },
21
- required: params.select { |t, _| t == :keyreq }.map { |_, n| n },
22
- optional: params.select { |t, _| t == :key }.map { |_, n| n },
23
- rest: params.find { |type, _| type == :keyrest } & [1]
27
+ required: params.select { |t, _| @compares_safely.compare(t, :keyreq) }.map { |_, n| n },
28
+ optional: params.select { |t, _| @compares_safely.compare(t, :key) }.map { |_, n| n },
29
+ rest: params.find { |t, _| @compares_safely.compare(t, :keyrest) } & [1]
24
30
  ),
25
31
  keyword_args: dry_call.kwargs,
26
32
 
27
- block_param: params.find { |type, _| type == :block } & [1],
33
+ block_param: params.find { |t, _| @compares_safely.compare(t, :block) } & [1],
28
34
  block_arg: dry_call.block
29
35
  )
30
36
  end
@@ -1,3 +1,5 @@
1
+ require_relative "../share/compares_safely"
2
+
1
3
  # The Cabinet stores all thread-local state, so anything that goes here
2
4
  # is guaranteed by Mocktail to be local to the currently-running thread
3
5
  module Mocktail
@@ -6,6 +8,7 @@ module Mocktail
6
8
  attr_reader :calls, :stubbings, :unsatisfying_calls
7
9
 
8
10
  def initialize
11
+ @compares_safely = ComparesSafely.new
9
12
  @doubles = []
10
13
  @calls = []
11
14
  @stubbings = []
@@ -45,15 +48,19 @@ module Mocktail
45
48
  end
46
49
 
47
50
  def double_for_instance(thing)
48
- @doubles.find { |double| double.dry_instance == thing }
51
+ @doubles.find { |double| @compares_safely.compare(double.dry_instance, thing) }
49
52
  end
50
53
 
51
54
  def stubbings_for_double(double)
52
- @stubbings.select { |stubbing| stubbing.recording.double == double.dry_instance }
55
+ @stubbings.select { |stubbing|
56
+ @compares_safely.compare(stubbing.recording.double, double.dry_instance)
57
+ }
53
58
  end
54
59
 
55
60
  def calls_for_double(double)
56
- @calls.select { |call| call.double == double.dry_instance }
61
+ @calls.select { |call|
62
+ @compares_safely.compare(call.double, double.dry_instance)
63
+ }
57
64
  end
58
65
  end
59
66
  end
@@ -1,3 +1,3 @@
1
1
  module Mocktail
2
- VERSION = "1.1.1"
2
+ VERSION = "1.1.2"
3
3
  end
data/lib/mocktail.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require_relative "mocktail/debug"
1
2
  require_relative "mocktail/dsl"
2
3
  require_relative "mocktail/errors"
3
4
  require_relative "mocktail/explains_thing"
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.1.1
4
+ version: 1.1.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-05-14 00:00:00.000000000 Z
11
+ date: 2022-06-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -29,6 +29,7 @@ files:
29
29
  - bin/console
30
30
  - bin/setup
31
31
  - lib/mocktail.rb
32
+ - lib/mocktail/debug.rb
32
33
  - lib/mocktail/dsl.rb
33
34
  - lib/mocktail/errors.rb
34
35
  - lib/mocktail/explains_nils.rb
@@ -67,6 +68,7 @@ files:
67
68
  - lib/mocktail/replaces_type/redefines_singleton_methods.rb
68
69
  - lib/mocktail/resets_state.rb
69
70
  - lib/mocktail/share/cleans_backtrace.rb
71
+ - lib/mocktail/share/compares_safely.rb
70
72
  - lib/mocktail/share/creates_identifier.rb
71
73
  - lib/mocktail/share/determines_matching_calls.rb
72
74
  - lib/mocktail/share/stringifies_call.rb
@@ -117,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
119
  - !ruby/object:Gem::Version
118
120
  version: '0'
119
121
  requirements: []
120
- rubygems_version: 3.3.7
122
+ rubygems_version: 3.3.6
121
123
  signing_key:
122
124
  specification_version: 4
123
125
  summary: Take your objects, and make them a double