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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +6 -1
- data/lib/mocktail/debug.rb +49 -0
- data/lib/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +4 -2
- data/lib/mocktail/imitates_type/makes_double/declares_dry_class.rb +3 -0
- data/lib/mocktail/share/compares_safely.rb +7 -0
- data/lib/mocktail/share/determines_matching_calls.rb +9 -3
- data/lib/mocktail/simulates_argument_error/transforms_params.rb +15 -9
- data/lib/mocktail/value/cabinet.rb +10 -3
- data/lib/mocktail/version.rb +1 -1
- data/lib/mocktail.rb +1 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7eb64f90875e53a9bfd5b4d995f8526eb6695dd9bd8a88c850b02eed851cb96d
|
4
|
+
data.tar.gz: ae2b4443a3740c375c70e7e490cbe3463d9672f17665ae47930b6ee614e9891d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de8d47071e93c4d406391a1155dea9b6a1a9ac89f56422ae29e6dda5f134c9e5e724ec8006aba833b5ff19d9d2dff4550384bc349506f5ab9cefe3fd2185af1d
|
7
|
+
data.tar.gz: 5bf88d0aadc08fe9a9380312cf491146439e72e335091e2e79849196b74e93aba20b0fac2d122152a4661008ee243b5b3b39cb6408fc776b2cc2567ffca0924f
|
data/Gemfile.lock
CHANGED
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://
|
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
|
14
|
-
dry_call.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,
|
@@ -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
|
5
|
-
real_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 { |
|
9
|
-
[:req, :opt, :rest].
|
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
|
12
|
-
optional: params.select { |t, _| t
|
13
|
-
rest: params.find { |
|
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
|
22
|
-
optional: params.select { |t, _| t
|
23
|
-
rest: params.find { |
|
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 { |
|
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
|
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|
|
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|
|
61
|
+
@calls.select { |call|
|
62
|
+
@compares_safely.compare(call.double, double.dry_instance)
|
63
|
+
}
|
57
64
|
end
|
58
65
|
end
|
59
66
|
end
|
data/lib/mocktail/version.rb
CHANGED
data/lib/mocktail.rb
CHANGED
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.
|
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-
|
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.
|
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
|