mocktail 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +18 -0
  3. data/.gitignore +8 -0
  4. data/.standard.yml +1 -0
  5. data/CHANGELOG.md +3 -0
  6. data/Gemfile +10 -0
  7. data/Gemfile.lock +62 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.md +557 -0
  10. data/Rakefile +11 -0
  11. data/bin/console +35 -0
  12. data/bin/setup +8 -0
  13. data/lib/mocktail/dsl.rb +21 -0
  14. data/lib/mocktail/errors.rb +15 -0
  15. data/lib/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +16 -0
  16. data/lib/mocktail/handles_dry_call/fulfills_stubbing.rb +21 -0
  17. data/lib/mocktail/handles_dry_call/logs_call.rb +7 -0
  18. data/lib/mocktail/handles_dry_call/validates_arguments.rb +57 -0
  19. data/lib/mocktail/handles_dry_call.rb +19 -0
  20. data/lib/mocktail/handles_dry_new_call.rb +36 -0
  21. data/lib/mocktail/imitates_type/ensures_imitation_support.rb +11 -0
  22. data/lib/mocktail/imitates_type/makes_double/declares_dry_class.rb +95 -0
  23. data/lib/mocktail/imitates_type/makes_double.rb +18 -0
  24. data/lib/mocktail/imitates_type.rb +19 -0
  25. data/lib/mocktail/initializes_mocktail.rb +17 -0
  26. data/lib/mocktail/matcher_presentation.rb +15 -0
  27. data/lib/mocktail/matchers/any.rb +18 -0
  28. data/lib/mocktail/matchers/base.rb +25 -0
  29. data/lib/mocktail/matchers/captor.rb +52 -0
  30. data/lib/mocktail/matchers/includes.rb +24 -0
  31. data/lib/mocktail/matchers/is_a.rb +11 -0
  32. data/lib/mocktail/matchers/matches.rb +13 -0
  33. data/lib/mocktail/matchers/not.rb +11 -0
  34. data/lib/mocktail/matchers/numeric.rb +18 -0
  35. data/lib/mocktail/matchers/that.rb +24 -0
  36. data/lib/mocktail/matchers.rb +14 -0
  37. data/lib/mocktail/records_demonstration.rb +32 -0
  38. data/lib/mocktail/registers_matcher.rb +52 -0
  39. data/lib/mocktail/registers_stubbing.rb +19 -0
  40. data/lib/mocktail/replaces_next.rb +36 -0
  41. data/lib/mocktail/replaces_type/redefines_new.rb +26 -0
  42. data/lib/mocktail/replaces_type/redefines_singleton_methods.rb +39 -0
  43. data/lib/mocktail/replaces_type.rb +26 -0
  44. data/lib/mocktail/resets_state.rb +9 -0
  45. data/lib/mocktail/share/determines_matching_calls.rb +60 -0
  46. data/lib/mocktail/share/simulates_argument_error.rb +28 -0
  47. data/lib/mocktail/value/cabinet.rb +41 -0
  48. data/lib/mocktail/value/call.rb +15 -0
  49. data/lib/mocktail/value/demo_config.rb +10 -0
  50. data/lib/mocktail/value/double.rb +11 -0
  51. data/lib/mocktail/value/matcher_registry.rb +19 -0
  52. data/lib/mocktail/value/stubbing.rb +24 -0
  53. data/lib/mocktail/value/top_shelf.rb +61 -0
  54. data/lib/mocktail/value/type_replacement.rb +11 -0
  55. data/lib/mocktail/value.rb +8 -0
  56. data/lib/mocktail/verifies_call/finds_verifiable_calls.rb +15 -0
  57. data/lib/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +10 -0
  58. data/lib/mocktail/verifies_call/raises_verification_error/stringifies_call.rb +47 -0
  59. data/lib/mocktail/verifies_call/raises_verification_error.rb +63 -0
  60. data/lib/mocktail/verifies_call.rb +29 -0
  61. data/lib/mocktail/version.rb +3 -0
  62. data/lib/mocktail.rb +63 -0
  63. data/mocktail.gemspec +31 -0
  64. metadata +107 -0
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+ require "standard/rake"
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << "test"
7
+ t.libs << "lib"
8
+ t.test_files = FileList["test/**/*_test.rb"]
9
+ end
10
+
11
+ task default: [:test, "standard:fix"]
data/bin/console ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ require "bundler/setup"
3
+ require "mocktail"
4
+
5
+ # You can add fixtures and/or initialization code here to make experimenting
6
+ # with your gem easier. You can also use a different console, if you like.
7
+ class Negroni
8
+ def self.ingredients
9
+ [:gin, :campari, :sweet_vermouth]
10
+ end
11
+
12
+ def shake!(shaker)
13
+ shaker.mix(self.class.ingredients)
14
+ end
15
+
16
+ def sip(amount)
17
+ raise "unimplemented"
18
+ end
19
+ end
20
+
21
+ include Mocktail::DSL
22
+ class UserRepository
23
+ def find(id); end
24
+
25
+ def transaction(&blk); end
26
+ end
27
+
28
+ class Auditor
29
+ def record!(message, user:, action: nil); end
30
+ end
31
+
32
+
33
+ # (If you use this, don't forget to add pry to your Gemfile!)
34
+ require "pry"
35
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,21 @@
1
+ module Mocktail
2
+ module DSL
3
+ def stubs(ignore_block: false, ignore_extra_args: false, ignore_arity: false, times: nil, &demo)
4
+ RegistersStubbing.new.register(demo, DemoConfig.new(
5
+ ignore_block: ignore_block,
6
+ ignore_extra_args: ignore_extra_args,
7
+ ignore_arity: ignore_arity,
8
+ times: times
9
+ ))
10
+ end
11
+
12
+ def verify(ignore_block: false, ignore_extra_args: false, ignore_arity: false, times: nil, &demo)
13
+ VerifiesCall.new.verify(demo, DemoConfig.new(
14
+ ignore_block: ignore_block,
15
+ ignore_extra_args: ignore_extra_args,
16
+ ignore_arity: ignore_arity,
17
+ times: times
18
+ ))
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ module Mocktail
2
+ class Error < StandardError; end
3
+
4
+ class UnexpectedError < Error; end
5
+
6
+ class UnsupportedMocktail < Error; end
7
+
8
+ class MissingDemonstrationError < Error; end
9
+
10
+ class AmbiguousDemonstrationError < Error; end
11
+
12
+ class InvalidMatcherError < Error; end
13
+
14
+ class VerificationError < Error; end
15
+ end
@@ -0,0 +1,16 @@
1
+ require_relative "../../share/determines_matching_calls"
2
+
3
+ module Mocktail
4
+ class FindsSatisfaction
5
+ def initialize
6
+ @determines_matching_calls = DeterminesMatchingCalls.new
7
+ end
8
+
9
+ def find(dry_call)
10
+ Mocktail.cabinet.stubbings.reverse.find { |stubbing|
11
+ @determines_matching_calls.determine(dry_call, stubbing.recording, stubbing.demo_config) &&
12
+ (stubbing.demo_config.times.nil? || stubbing.demo_config.times > stubbing.satisfaction_count)
13
+ }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ require_relative "fulfills_stubbing/finds_satisfaction"
2
+
3
+ module Mocktail
4
+ class FulfillsStubbing
5
+ def initialize
6
+ @finds_satisfaction = FindsSatisfaction.new
7
+ end
8
+
9
+ def fulfill(dry_call)
10
+ if (stubbing = satisfaction(dry_call))
11
+ stubbing.satisfied!
12
+ stubbing.effect&.call(dry_call)
13
+ end
14
+ end
15
+
16
+ def satisfaction(dry_call)
17
+ return if Mocktail.cabinet.demonstration_in_progress?
18
+ @finds_satisfaction.find(dry_call)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module Mocktail
2
+ class LogsCall
3
+ def log(dry_call)
4
+ Mocktail.cabinet.store_call(dry_call)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,57 @@
1
+ require_relative "../share/simulates_argument_error"
2
+
3
+ module Mocktail
4
+ class ValidatesArguments
5
+ def self.disable!
6
+ Thread.current[:mocktail_arity_validation_disabled] = true
7
+ end
8
+
9
+ def self.enable!
10
+ Thread.current[:mocktail_arity_validation_disabled] = false
11
+ end
12
+
13
+ def self.disabled?
14
+ Thread.current[:mocktail_arity_validation_disabled]
15
+ end
16
+
17
+ def self.optional(disable, &blk)
18
+ return blk.call unless disable
19
+
20
+ disable!
21
+ blk.call.tap do
22
+ enable!
23
+ end
24
+ end
25
+
26
+ def initialize
27
+ @simulates_argument_error = SimulatesArgumentError.new
28
+ end
29
+
30
+ def validate(dry_call)
31
+ return if self.class.disabled?
32
+
33
+ arg_params, kwarg_params = dry_call.original_method.parameters.reject { |type, _|
34
+ type == :block
35
+ }.partition { |type, _|
36
+ [:req, :opt, :rest].include?(type)
37
+ }
38
+
39
+ unless args_match?(arg_params, dry_call.args) &&
40
+ kwargs_match?(kwarg_params, dry_call.kwargs)
41
+ raise @simulates_argument_error.simulate(arg_params, dry_call.args, kwarg_params, dry_call.kwargs)
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def args_match?(arg_params, args)
48
+ args.size >= arg_params.count { |type, _| type == :req } &&
49
+ (arg_params.any? { |type, _| type == :rest } || args.size <= arg_params.size)
50
+ end
51
+
52
+ def kwargs_match?(kwarg_params, kwargs)
53
+ kwarg_params.select { |type, _| type == :keyreq }.all? { |_, name| kwargs.key?(name) } &&
54
+ (kwarg_params.any? { |type, _| type == :keyrest } || kwargs.keys.all? { |name| kwarg_params.any? { |_, key| name == key } })
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "handles_dry_call/fulfills_stubbing"
2
+ require_relative "handles_dry_call/logs_call"
3
+ require_relative "handles_dry_call/validates_arguments"
4
+
5
+ module Mocktail
6
+ class HandlesDryCall
7
+ def initialize
8
+ @validates_arguments = ValidatesArguments.new
9
+ @logs_call = LogsCall.new
10
+ @fulfills_stubbing = FulfillsStubbing.new
11
+ end
12
+
13
+ def handle(dry_call)
14
+ @validates_arguments.validate(dry_call)
15
+ @logs_call.log(dry_call)
16
+ @fulfills_stubbing.fulfill(dry_call)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ module Mocktail
2
+ class HandlesDryNewCall
3
+ def initialize
4
+ @validates_arguments = ValidatesArguments.new
5
+ @logs_call = LogsCall.new
6
+ @fulfills_stubbing = FulfillsStubbing.new
7
+ @imitates_type = ImitatesType.new
8
+ end
9
+
10
+ def handle(type, args, kwargs, block)
11
+ @validates_arguments.validate(Call.new(
12
+ original_method: type.instance_method(:initialize),
13
+ args: args,
14
+ kwargs: kwargs,
15
+ block: block
16
+ ))
17
+
18
+ new_call = Call.new(
19
+ singleton: true,
20
+ double: type,
21
+ original_type: type,
22
+ dry_type: type,
23
+ method: :new,
24
+ args: args,
25
+ kwargs: kwargs,
26
+ block: block
27
+ )
28
+ @logs_call.log(new_call)
29
+ if @fulfills_stubbing.satisfaction(new_call)
30
+ @fulfills_stubbing.fulfill(new_call)
31
+ else
32
+ @imitates_type.imitate(type)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,11 @@
1
+ module Mocktail
2
+ class EnsuresImitationSupport
3
+ def ensure(type)
4
+ unless type.is_a?(Class) || type.is_a?(Module)
5
+ raise UnsupportedMocktail.new <<~MSG.tr("\n", " ")
6
+ Mocktail.of() can only mix mocktail instances of modules and classes.
7
+ MSG
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,95 @@
1
+ module Mocktail
2
+ class DeclaresDryClass
3
+ def initialize
4
+ @handles_dry_call = HandlesDryCall.new
5
+ end
6
+
7
+ def declare(type)
8
+ type_type = type_of(type)
9
+ instance_methods = instance_methods_on(type)
10
+ dry_class = Class.new(Object) {
11
+ include type if type_type == :module
12
+
13
+ def initialize(*args, **kwargs, &blk)
14
+ end
15
+
16
+ define_method :is_a?, ->(thing) {
17
+ type.ancestors.include?(thing)
18
+ }
19
+ alias_method :kind_of?, :is_a?
20
+
21
+ if type_type == :class
22
+ define_method :instance_of?, ->(thing) {
23
+ type == thing
24
+ }
25
+ end
26
+ }
27
+
28
+ add_stringify_methods!(dry_class, :to_s, type, type_type, instance_methods)
29
+ add_stringify_methods!(dry_class, :inspect, type, type_type, instance_methods)
30
+
31
+ define_double_methods!(dry_class, type, instance_methods)
32
+
33
+ dry_class
34
+ end
35
+
36
+ private
37
+
38
+ def define_double_methods!(dry_class, type, instance_methods)
39
+ handles_dry_call = @handles_dry_call
40
+ instance_methods.each do |method|
41
+ dry_class.define_method method, ->(*args, **kwargs, &block) {
42
+ handles_dry_call.handle(Call.new(
43
+ singleton: false,
44
+ double: self,
45
+ original_type: type,
46
+ dry_type: self.class,
47
+ method: method,
48
+ original_method: type.instance_method(method),
49
+ args: args,
50
+ kwargs: kwargs,
51
+ block: block
52
+ ))
53
+ }
54
+ end
55
+ end
56
+
57
+ def add_stringify_methods!(dry_class, method_name, type, type_type, instance_methods)
58
+ dry_class.define_singleton_method method_name, -> {
59
+ if (id_matches = super().match(/:([0-9a-fx]+)>$/))
60
+ "#<Class #{"including module " if type.instance_of?(Module)}for mocktail of #{type.name}:#{id_matches[1]}>"
61
+ else
62
+ super()
63
+ end
64
+ }
65
+
66
+ unless instance_methods.include?(method_name)
67
+ dry_class.define_method method_name, -> {
68
+ if (id_matches = super().match(/:([0-9a-fx]+)>$/))
69
+ "#<Mocktail of #{type.name}:#{id_matches[1]}>"
70
+ else
71
+ super()
72
+ end
73
+ }
74
+ end
75
+ end
76
+
77
+ def type_of(type)
78
+ if type.is_a?(Class)
79
+ :class
80
+ elsif type.is_a?(Module)
81
+ :module
82
+ end
83
+ end
84
+
85
+ def instance_methods_on(type)
86
+ type.instance_methods.reject { |m|
87
+ ignored_ancestors.include?(type.instance_method(m).owner)
88
+ }
89
+ end
90
+
91
+ def ignored_ancestors
92
+ Object.ancestors
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,18 @@
1
+ require_relative "makes_double/declares_dry_class"
2
+
3
+ module Mocktail
4
+ class MakesDouble
5
+ def initialize
6
+ @declares_dry_class = DeclaresDryClass.new
7
+ end
8
+
9
+ def make(klass)
10
+ dry_type = @declares_dry_class.declare(klass)
11
+ Double.new(
12
+ original_type: klass,
13
+ dry_type: dry_type,
14
+ dry_instance: dry_type.new
15
+ )
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "imitates_type/ensures_imitation_support"
2
+ require_relative "imitates_type/makes_double"
3
+
4
+ module Mocktail
5
+ class ImitatesType
6
+ def initialize
7
+ @top_shelf = TopShelf.instance
8
+ @ensures_imitation_support = EnsuresImitationSupport.new
9
+ @makes_double = MakesDouble.new
10
+ end
11
+
12
+ def imitate(type)
13
+ @ensures_imitation_support.ensure(type)
14
+ @makes_double.make(type).tap do |double|
15
+ Mocktail.cabinet.store_double(double)
16
+ end.dry_instance
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module Mocktail
2
+ class InitializesMocktail
3
+ def init
4
+ [
5
+ Mocktail::Matchers::Any,
6
+ Mocktail::Matchers::Includes,
7
+ Mocktail::Matchers::IsA,
8
+ Mocktail::Matchers::Matches,
9
+ Mocktail::Matchers::Not,
10
+ Mocktail::Matchers::Numeric,
11
+ Mocktail::Matchers::That
12
+ ].each do |matcher_type|
13
+ Mocktail.register_matcher(matcher_type)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module Mocktail
2
+ class MatcherPresentation
3
+ def respond_to_missing?(name, include_private = false)
4
+ !!MatcherRegistry.instance.get(name) || super
5
+ end
6
+
7
+ def method_missing(name, *args, **kwargs, &blk)
8
+ if (matcher = MatcherRegistry.instance.get(name))
9
+ matcher.new(*args, **kwargs, &blk)
10
+ else
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ module Mocktail::Matchers
2
+ class Any < Base
3
+ def self.matcher_name
4
+ :any
5
+ end
6
+
7
+ def initialize
8
+ end
9
+
10
+ def match?(actual)
11
+ true
12
+ end
13
+
14
+ def inspect
15
+ "any"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,25 @@
1
+ module Mocktail::Matchers
2
+ class Base
3
+ # Custom matchers can receive any args, kwargs, or block they want. Usually
4
+ # single-argument, though, so that's defaulted here and in #insepct
5
+ def initialize(expected)
6
+ @expected = expected
7
+ end
8
+
9
+ def self.matcher_name
10
+ raise Mocktail::Error.new("The `matcher_name` class method must return a valid method name")
11
+ end
12
+
13
+ def match?(actual)
14
+ raise Mocktail::Error.new("Matchers must implement `match?(argument)`")
15
+ end
16
+
17
+ def inspect
18
+ "#{self.class.matcher_name}(#{@expected.inspect})"
19
+ end
20
+
21
+ def is_mocktail_matcher?
22
+ true
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,52 @@
1
+ module Mocktail::Matchers
2
+ # Captors are conceptually complex implementations, but with a simple usage/purpose:
3
+ # They are values the user can create and hold onto that will return a matcher
4
+ # and then "capture" the value made by the real call, for later analysis & assertion.
5
+ #
6
+ # Unlike other matchers, these don't make any useful sense for stubbing, but are
7
+ # very useful when asserting complication call verifications
8
+ #
9
+ # The fact the user will need the reference outside the verification call is
10
+ # why this is a top-level method on Mocktail, and not included in the |m| block
11
+ # arg to stubs/verify
12
+ #
13
+ # See Mockito, which is the earliest implementation I know of:
14
+ # https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Captor.html
15
+ class Captor
16
+ class Capture < Mocktail::Matchers::Base
17
+ def self.matcher_name
18
+ :capture
19
+ end
20
+
21
+ attr_reader :value
22
+
23
+ def initialize
24
+ @value = nil
25
+ @captured = false
26
+ end
27
+
28
+ def match?(actual)
29
+ @value = actual
30
+ @captured = true
31
+ true
32
+ end
33
+
34
+ def captured?
35
+ @captured
36
+ end
37
+ end
38
+
39
+ attr_reader :capture
40
+ def initialize
41
+ @capture = Capture.new
42
+ end
43
+
44
+ def captured?
45
+ @capture.captured?
46
+ end
47
+
48
+ def value
49
+ @capture.value
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,24 @@
1
+ module Mocktail::Matchers
2
+ class Includes < Base
3
+ def self.matcher_name
4
+ :includes
5
+ end
6
+
7
+ def initialize(*expecteds)
8
+ @expecteds = expecteds
9
+ end
10
+
11
+ def match?(actual)
12
+ @expecteds.all? { |expected|
13
+ (actual.respond_to?(:include?) && actual.include?(expected)) ||
14
+ (actual.is_a?(Hash) && expected.is_a?(Hash) && expected.all? { |k, v| actual[k] == v })
15
+ }
16
+ rescue
17
+ false
18
+ end
19
+
20
+ def inspect
21
+ "#{self.class.matcher_name}(#{@expecteds.map(&:inspect).join(", ")})"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ module Mocktail::Matchers
2
+ class IsA < Base
3
+ def self.matcher_name
4
+ :is_a
5
+ end
6
+
7
+ def match?(actual)
8
+ actual.is_a?(@expected)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ module Mocktail::Matchers
2
+ class Matches < Base
3
+ def self.matcher_name
4
+ :matches
5
+ end
6
+
7
+ def match?(actual)
8
+ actual.respond_to?(:match?) && actual.match?(@expected)
9
+ rescue
10
+ false
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module Mocktail::Matchers
2
+ class Not < Base
3
+ def self.matcher_name
4
+ :not
5
+ end
6
+
7
+ def match?(actual)
8
+ @expected != actual
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ module Mocktail::Matchers
2
+ class Numeric < Base
3
+ def self.matcher_name
4
+ :numeric
5
+ end
6
+
7
+ def initialize
8
+ end
9
+
10
+ def match?(actual)
11
+ [Integer, Float, (BigDecimal if defined?(BigDecimal))].include?(actual.class)
12
+ end
13
+
14
+ def inspect
15
+ "numeric"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ module Mocktail::Matchers
2
+ class That < Base
3
+ def self.matcher_name
4
+ :that
5
+ end
6
+
7
+ def initialize(&blk)
8
+ if blk.nil?
9
+ raise "The `that` matcher must be passed a block (e.g. `that { |arg| … }`)"
10
+ end
11
+ @blk = blk
12
+ end
13
+
14
+ def match?(actual)
15
+ @blk.call(actual)
16
+ rescue
17
+ false
18
+ end
19
+
20
+ def inspect
21
+ "that {…}"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ module Mocktail
2
+ module Matchers
3
+ end
4
+ end
5
+
6
+ require_relative "matchers/base"
7
+ require_relative "matchers/any"
8
+ require_relative "matchers/captor"
9
+ require_relative "matchers/includes"
10
+ require_relative "matchers/is_a"
11
+ require_relative "matchers/matches"
12
+ require_relative "matchers/not"
13
+ require_relative "matchers/numeric"
14
+ require_relative "matchers/that"