mocktail 1.1.1 → 1.1.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: 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