mocktail 0.0.1

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.
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"