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