mocktail 0.0.2 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/Gemfile.lock +2 -2
  4. data/README.md +178 -18
  5. data/bin/console +21 -1
  6. data/lib/mocktail/explains_thing.rb +132 -0
  7. data/lib/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +13 -0
  8. data/lib/mocktail/handles_dry_call/fulfills_stubbing.rb +4 -0
  9. data/lib/mocktail/handles_dry_call/validates_arguments.rb +2 -23
  10. data/lib/mocktail/imitates_type/makes_double/declares_dry_class.rb +29 -23
  11. data/lib/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +21 -0
  12. data/lib/mocktail/imitates_type/makes_double.rb +8 -4
  13. data/lib/mocktail/raises_neato_no_method_error.rb +81 -0
  14. data/lib/mocktail/share/creates_identifier.rb +28 -0
  15. data/lib/mocktail/{verifies_call/raises_verification_error → share}/stringifies_call.rb +16 -7
  16. data/lib/mocktail/share/stringifies_method_name.rb +11 -0
  17. data/lib/mocktail/simulates_argument_error/cleans_backtrace.rb +17 -0
  18. data/lib/mocktail/simulates_argument_error/reconciles_args_with_params.rb +20 -0
  19. data/lib/mocktail/simulates_argument_error/recreates_message.rb +29 -0
  20. data/lib/mocktail/simulates_argument_error/transforms_params.rb +32 -0
  21. data/lib/mocktail/simulates_argument_error.rb +30 -0
  22. data/lib/mocktail/value/cabinet.rb +12 -0
  23. data/lib/mocktail/value/double.rb +7 -8
  24. data/lib/mocktail/value/double_data.rb +10 -0
  25. data/lib/mocktail/value/explanation.rb +26 -0
  26. data/lib/mocktail/value/signature.rb +36 -0
  27. data/lib/mocktail/value/stub_returned_nil.rb +26 -0
  28. data/lib/mocktail/value/top_shelf.rb +24 -25
  29. data/lib/mocktail/value/type_replacement_data.rb +13 -0
  30. data/lib/mocktail/value/unsatisfied_stubbing.rb +8 -0
  31. data/lib/mocktail/value.rb +6 -0
  32. data/lib/mocktail/verifies_call/raises_verification_error.rb +4 -2
  33. data/lib/mocktail/version.rb +1 -1
  34. data/lib/mocktail.rb +9 -0
  35. data/mocktail.gemspec +1 -1
  36. metadata +22 -6
  37. data/lib/mocktail/share/simulates_argument_error.rb +0 -28
@@ -0,0 +1,81 @@
1
+ require_relative "share/stringifies_call"
2
+ require_relative "share/stringifies_method_name"
3
+ require_relative "share/creates_identifier"
4
+
5
+ module Mocktail
6
+ class RaisesNeatoNoMethodError
7
+ def initialize
8
+ @stringifies_call = StringifiesCall.new
9
+ @stringifies_method_name = StringifiesMethodName.new
10
+ @creates_identifier = CreatesIdentifier.new
11
+ end
12
+
13
+ def call(call)
14
+ raise NoMethodError.new <<~MSG
15
+ No method `#{@stringifies_method_name.stringify(call)}' exists for call:
16
+
17
+ #{@stringifies_call.stringify(call, anonymous_blocks: true, always_parens: true)}
18
+
19
+ Need to define the method? Here's a sample definition:
20
+
21
+ def #{call.method}#{params(call)}
22
+ end
23
+ #{corrections(call)}
24
+ MSG
25
+ end
26
+
27
+ private
28
+
29
+ def params(call)
30
+ return if (params_lists = [
31
+ params_list(call.args),
32
+ kwparams_list(call.kwargs),
33
+ block_param(call.block)
34
+ ].compact).empty?
35
+
36
+ "(#{params_lists.join(", ")})"
37
+ end
38
+
39
+ def params_list(args)
40
+ return if args.empty?
41
+
42
+ count_repeats(args.map { |arg|
43
+ @creates_identifier.create(arg, default: "arg")
44
+ }).join(", ")
45
+ end
46
+
47
+ def kwparams_list(kwargs)
48
+ return if kwargs.empty?
49
+
50
+ kwargs.keys.map { |key| "#{key}:" }.join(", ")
51
+ end
52
+
53
+ def block_param(block)
54
+ return if block.nil?
55
+
56
+ "&blk"
57
+ end
58
+
59
+ def count_repeats(identifiers)
60
+ identifiers.map.with_index { |id, i|
61
+ if (preceding_matches = identifiers[0...i].count { |other_id| id == other_id }) > 0
62
+ "#{id}#{preceding_matches + 1}"
63
+ else
64
+ id
65
+ end
66
+ }
67
+ end
68
+
69
+ def corrections(call)
70
+ return if (corrections = DidYouMean::SpellChecker.new(dictionary: call.original_type.instance_methods).correct(call.method)).empty?
71
+
72
+ <<~MSG
73
+
74
+ There #{corrections.size == 1 ? "is" : "are"} also #{corrections.size} similar method#{"s" if corrections.size != 1} on #{call.original_type.name}.
75
+
76
+ Did you mean?
77
+ #{corrections.map { |c| " #{c}" }.join("\n")}
78
+ MSG
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,28 @@
1
+ module Mocktail
2
+ class CreatesIdentifier
3
+ KEYWORDS = %w[__FILE__ __LINE__ alias and begin BEGIN break case class def defined? do else elsif end END ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield]
4
+
5
+ def create(s, default: "identifier", max_length: 24)
6
+ id = s.to_s.downcase
7
+ .gsub(/:0x[0-9a-f]+/, "") # Lazy attempt to wipe any Object:0x802beef identifiers
8
+ .gsub(/[^\w\s]/, "")
9
+ .gsub(/^\d+/, "")[0...max_length]
10
+ .strip
11
+ .gsub(/\s+/, "_") # snake_case
12
+
13
+ if id.empty?
14
+ default
15
+ else
16
+ unreserved(id, default)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def unreserved(id, default)
23
+ return id unless KEYWORDS.include?(id)
24
+
25
+ "#{id}_#{default}"
26
+ end
27
+ end
28
+ end
@@ -1,18 +1,22 @@
1
1
  module Mocktail
2
2
  class StringifiesCall
3
- def stringify(call)
4
- "#{call.method}#{args_to_s(call)}#{blockify(call.block)}"
3
+ def stringify(call, anonymous_blocks: false, always_parens: false)
4
+ "#{call.method}#{args_to_s(call, parens: always_parens)}#{blockify(call.block, anonymous: anonymous_blocks)}"
5
5
  end
6
6
 
7
7
  private
8
8
 
9
- def args_to_s(call)
10
- unless (args_lists = [
9
+ def args_to_s(call, parens: true)
10
+ args_lists = [
11
11
  argify(call.args),
12
12
  kwargify(call.kwargs),
13
13
  lambdafy(call.block)
14
- ].compact).empty?
14
+ ].compact
15
+
16
+ if !args_lists.empty?
15
17
  "(#{args_lists.join(", ")})"
18
+ elsif parens
19
+ "()"
16
20
  end
17
21
  end
18
22
 
@@ -31,9 +35,14 @@ module Mocktail
31
35
  "&lambda[#{source_locationify(block)}]"
32
36
  end
33
37
 
34
- def blockify(block)
38
+ def blockify(block, anonymous:)
35
39
  return unless block && !block.lambda?
36
- " { Proc at #{source_locationify(block)} }"
40
+
41
+ if anonymous
42
+ " {…}"
43
+ else
44
+ " { Proc at #{source_locationify(block)} }"
45
+ end
37
46
  end
38
47
 
39
48
  def source_locationify(block)
@@ -0,0 +1,11 @@
1
+ module Mocktail
2
+ class StringifiesMethodName
3
+ def stringify(call)
4
+ [
5
+ call.original_type.name,
6
+ call.singleton ? "." : "#",
7
+ call.method
8
+ ].join
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ require "pathname"
2
+
3
+ module Mocktail
4
+ class CleansBacktrace
5
+ BASE_PATH = (Pathname.new(__FILE__) + "../../..").to_s
6
+
7
+ def clean(error)
8
+ raise error
9
+ rescue => e
10
+ e.tap do |e|
11
+ e.set_backtrace(e.backtrace.drop_while { |frame|
12
+ frame.start_with?(BASE_PATH)
13
+ })
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module Mocktail
2
+ class ReconcilesArgsWithParams
3
+ def reconcile(signature)
4
+ args_match?(signature.positional_params, signature.positional_args) &&
5
+ kwargs_match?(signature.keyword_params, signature.keyword_args)
6
+ end
7
+
8
+ private
9
+
10
+ def args_match?(arg_params, args)
11
+ args.size >= arg_params.required.size &&
12
+ (arg_params.rest? || args.size <= arg_params.allowed.size)
13
+ end
14
+
15
+ def kwargs_match?(kwarg_params, kwargs)
16
+ kwarg_params.required.all? { |name| kwargs.key?(name) } &&
17
+ (kwarg_params.rest? || kwargs.keys.all? { |name| kwarg_params.allowed.include?(name) })
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ module Mocktail
2
+ class RecreatesMessage
3
+ def recreate(signature)
4
+ req_args = signature.positional_params.required.size
5
+ allowed_args = signature.positional_params.allowed.size
6
+ rest_args = signature.positional_params.rest?
7
+ req_kwargs = signature.keyword_params.required
8
+
9
+ if signature.positional_args.size < req_args || (!rest_args && signature.positional_args.size > allowed_args)
10
+ expected_desc = if rest_args
11
+ "#{req_args}+"
12
+ elsif allowed_args != req_args
13
+ "#{req_args}..#{allowed_args}"
14
+ else
15
+ req_args.to_s
16
+ end
17
+
18
+ "wrong number of arguments (given #{signature.positional_args.size}, expected #{expected_desc}#{"; required keyword#{"s" if req_kwargs.size > 1}: #{req_kwargs.join(", ")}" unless req_kwargs.empty?})"
19
+
20
+ elsif !(missing_kwargs = req_kwargs.reject { |name| signature.keyword_args.key?(name) }).empty?
21
+ "missing keyword#{"s" if missing_kwargs.size > 1}: #{missing_kwargs.map { |name| name.inspect }.join(", ")}"
22
+ elsif !(unknown_kwargs = signature.keyword_args.keys.reject { |name| signature.keyword_params.all.include?(name) }).empty?
23
+ "unknown keyword#{"s" if unknown_kwargs.size > 1}: #{unknown_kwargs.map { |name| name.inspect }.join(", ")}"
24
+ else
25
+ "unknown cause (this is probably a bug in Mocktail)"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ module Mocktail
2
+ class TransformsParams
3
+ def transform(dry_call)
4
+ params = dry_call.original_method.parameters
5
+
6
+ Signature.new(
7
+ positional_params: Params.new(
8
+ all: params.select { |type, _|
9
+ [:req, :opt, :rest].include?(type)
10
+ }.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]
14
+ ),
15
+ positional_args: dry_call.args,
16
+
17
+ keyword_params: Params.new(
18
+ all: params.select { |type, _|
19
+ [:keyreq, :key, :keyrest].include?(type)
20
+ }.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]
24
+ ),
25
+ keyword_args: dry_call.kwargs,
26
+
27
+ block_param: params.find { |type, _| type == :block } & [1],
28
+ block_arg: dry_call.block
29
+ )
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ require_relative "simulates_argument_error/transforms_params"
2
+ require_relative "simulates_argument_error/reconciles_args_with_params"
3
+ require_relative "simulates_argument_error/recreates_message"
4
+ require_relative "simulates_argument_error/cleans_backtrace"
5
+ require_relative "share/stringifies_call"
6
+
7
+ module Mocktail
8
+ class SimulatesArgumentError
9
+ def initialize
10
+ @transforms_params = TransformsParams.new
11
+ @reconciles_args_with_params = ReconcilesArgsWithParams.new
12
+ @recreates_message = RecreatesMessage.new
13
+ @cleans_backtrace = CleansBacktrace.new
14
+ @stringifies_call = StringifiesCall.new
15
+ end
16
+
17
+ def simulate(dry_call)
18
+ signature = @transforms_params.transform(dry_call)
19
+
20
+ unless @reconciles_args_with_params.reconcile(signature)
21
+ @cleans_backtrace.clean(
22
+ ArgumentError.new([
23
+ @recreates_message.recreate(signature),
24
+ "[Mocktail call: `#{@stringifies_call.stringify(dry_call)}']"
25
+ ].join(" "))
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
@@ -37,5 +37,17 @@ module Mocktail
37
37
  def demonstration_in_progress?
38
38
  @demonstration_in_progress
39
39
  end
40
+
41
+ def double_for_instance(thing)
42
+ @doubles.find { |double| double.dry_instance == thing }
43
+ end
44
+
45
+ def stubbings_for_double(double)
46
+ @stubbings.select { |stubbing| stubbing.recording.double == double.dry_instance }
47
+ end
48
+
49
+ def calls_for_double(double)
50
+ @calls.select { |call| call.double == double.dry_instance }
51
+ end
40
52
  end
41
53
  end
@@ -1,11 +1,10 @@
1
1
  module Mocktail
2
- class Double
3
- attr_reader :original_type, :dry_type, :dry_instance
4
-
5
- def initialize(original_type:, dry_type:, dry_instance:)
6
- @original_type = original_type
7
- @dry_type = dry_type
8
- @dry_instance = dry_instance
9
- end
2
+ class Double < Struct.new(
3
+ :original_type,
4
+ :dry_type,
5
+ :dry_instance,
6
+ :dry_methods,
7
+ keyword_init: true
8
+ )
10
9
  end
11
10
  end
@@ -0,0 +1,10 @@
1
+ module Mocktail
2
+ class DoubleData < Struct.new(
3
+ :type,
4
+ :double,
5
+ :calls,
6
+ :stubbings,
7
+ keyword_init: true
8
+ )
9
+ end
10
+ end
@@ -0,0 +1,26 @@
1
+ module Mocktail
2
+ class Explanation
3
+ attr_reader :reference, :message
4
+
5
+ def initialize(reference, message)
6
+ @reference = reference
7
+ @message = message
8
+ end
9
+
10
+ def type
11
+ self.class
12
+ end
13
+ end
14
+
15
+ class NoExplanation < Explanation
16
+ end
17
+
18
+ class UnsatisfiedStubExplanation < Explanation
19
+ end
20
+
21
+ class DoubleExplanation < Explanation
22
+ end
23
+
24
+ class ReplacedTypeExplanation < Explanation
25
+ end
26
+ end
@@ -0,0 +1,36 @@
1
+ module Mocktail
2
+ class Signature < Struct.new(
3
+ :positional_params,
4
+ :positional_args,
5
+ :keyword_params,
6
+ :keyword_args,
7
+ :block_param,
8
+ :block_arg,
9
+ keyword_init: true
10
+ )
11
+ end
12
+
13
+ class Params < Struct.new(
14
+ :all,
15
+ :required,
16
+ :optional,
17
+ :rest,
18
+ keyword_init: true
19
+ )
20
+
21
+ def initialize(**params)
22
+ super
23
+ self.all ||= []
24
+ self.required ||= []
25
+ self.optional ||= []
26
+ end
27
+
28
+ def allowed
29
+ required + optional
30
+ end
31
+
32
+ def rest?
33
+ !!rest
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,26 @@
1
+ module Mocktail
2
+ class StubReturnedNil < BasicObject
3
+ attr_reader :unsatisfied_stubbing
4
+
5
+ def initialize(unsatisfied_stubbing)
6
+ @unsatisfied_stubbing = unsatisfied_stubbing
7
+ end
8
+
9
+ def was_returned_by_unsatisfied_stub?
10
+ true
11
+ end
12
+
13
+ def tap
14
+ yield self
15
+ self
16
+ end
17
+
18
+ def method_missing(name, *args, **kwargs, &blk)
19
+ nil.send(name, *args, **kwargs, &blk)
20
+ end
21
+
22
+ def respond_to_missing?(name, include_all = false)
23
+ nil.respond_to?(name, include_all)
24
+ end
25
+ end
26
+ end
@@ -1,61 +1,60 @@
1
- # The top shelf stores all cross-thread & thread-aware state, so anything that
2
- # goes here is on its own when it comes to ensuring thread safety.
3
1
  module Mocktail
4
2
  class TopShelf
5
3
  def self.instance
6
- @self ||= new
4
+ Thread.current[:mocktail_top_shelf] ||= new
7
5
  end
8
6
 
7
+ @@type_replacements = {}
8
+ @@type_replacements_mutex = Mutex.new
9
+
9
10
  def initialize
10
- @type_replacements = {}
11
- @new_registrations = {}
12
- @of_next_registrations = {}
13
- @singleton_method_registrations = {}
11
+ @new_registrations = []
12
+ @of_next_registrations = []
13
+ @singleton_method_registrations = []
14
14
  end
15
15
 
16
16
  def type_replacement_for(type)
17
- @type_replacements[type] ||= TypeReplacement.new(type: type)
17
+ @@type_replacements_mutex.synchronize {
18
+ @@type_replacements[type] ||= TypeReplacement.new(type: type)
19
+ }
20
+ end
21
+
22
+ def type_replacement_if_exists_for(type)
23
+ @@type_replacements_mutex.synchronize {
24
+ @@type_replacements[type]
25
+ }
18
26
  end
19
27
 
20
28
  def reset_current_thread!
21
- @new_registrations[Thread.current] = []
22
- @of_next_registrations[Thread.current] = []
23
- @singleton_method_registrations[Thread.current] = []
29
+ Thread.current[:mocktail_top_shelf] = self.class.new
24
30
  end
25
31
 
26
32
  def register_new_replacement!(type)
27
- @new_registrations[Thread.current] ||= []
28
- @new_registrations[Thread.current] |= [type]
33
+ @new_registrations |= [type]
29
34
  end
30
35
 
31
36
  def new_replaced?(type)
32
- @new_registrations[Thread.current] ||= []
33
- @new_registrations[Thread.current].include?(type)
37
+ @new_registrations.include?(type)
34
38
  end
35
39
 
36
40
  def register_of_next_replacement!(type)
37
- @of_next_registrations[Thread.current] ||= []
38
- @of_next_registrations[Thread.current] |= [type]
41
+ @of_next_registrations |= [type]
39
42
  end
40
43
 
41
44
  def of_next_registered?(type)
42
- @of_next_registrations[Thread.current] ||= []
43
- @of_next_registrations[Thread.current].include?(type)
45
+ @of_next_registrations.include?(type)
44
46
  end
45
47
 
46
48
  def unregister_of_next_replacement!(type)
47
- @of_next_registrations[Thread.current] ||= []
48
- @of_next_registrations[Thread.current] -= [type]
49
+ @of_next_registrations -= [type]
49
50
  end
50
51
 
51
52
  def register_singleton_method_replacement!(type)
52
- @singleton_method_registrations[Thread.current] ||= []
53
- @singleton_method_registrations[Thread.current] |= [type]
53
+ @singleton_method_registrations |= [type]
54
54
  end
55
55
 
56
56
  def singleton_methods_replaced?(type)
57
- @singleton_method_registrations[Thread.current] ||= []
58
- @singleton_method_registrations[Thread.current].include?(type)
57
+ @singleton_method_registrations.include?(type)
59
58
  end
60
59
  end
61
60
  end
@@ -0,0 +1,13 @@
1
+ module Mocktail
2
+ class TypeReplacementData < Struct.new(
3
+ :type,
4
+ :replaced_method_names,
5
+ :calls,
6
+ :stubbings,
7
+ keyword_init: true
8
+ )
9
+ def double
10
+ type
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ module Mocktail
2
+ class UnsatisfiedStubbing < Struct.new(
3
+ :call,
4
+ :other_stubbings,
5
+ keyword_init: true
6
+ )
7
+ end
8
+ end
@@ -2,7 +2,13 @@ require_relative "value/cabinet"
2
2
  require_relative "value/call"
3
3
  require_relative "value/demo_config"
4
4
  require_relative "value/double"
5
+ require_relative "value/double_data"
6
+ require_relative "value/explanation"
5
7
  require_relative "value/matcher_registry"
8
+ require_relative "value/signature"
6
9
  require_relative "value/stubbing"
10
+ require_relative "value/stub_returned_nil"
7
11
  require_relative "value/top_shelf"
8
12
  require_relative "value/type_replacement"
13
+ require_relative "value/type_replacement_data"
14
+ require_relative "value/unsatisfied_stubbing"
@@ -1,16 +1,18 @@
1
1
  require_relative "raises_verification_error/gathers_calls_of_method"
2
- require_relative "raises_verification_error/stringifies_call"
2
+ require_relative "../share/stringifies_method_name"
3
+ require_relative "../share/stringifies_call"
3
4
 
4
5
  module Mocktail
5
6
  class RaisesVerificationError
6
7
  def initialize
7
8
  @gathers_calls_of_method = GathersCallsOfMethod.new
9
+ @stringifies_method_name = StringifiesMethodName.new
8
10
  @stringifies_call = StringifiesCall.new
9
11
  end
10
12
 
11
13
  def raise(recording, verifiable_calls, demo_config)
12
14
  Kernel.raise VerificationError.new <<~MSG
13
- Expected mocktail of #{recording.original_type.name}##{recording.method} to be called like:
15
+ Expected mocktail of `#{@stringifies_method_name.stringify(recording)}' to be called like:
14
16
 
15
17
  #{@stringifies_call.stringify(recording)}#{[
16
18
  (" [#{demo_config.times} #{pl("time", demo_config.times)}]" unless demo_config.times.nil?),
@@ -1,3 +1,3 @@
1
1
  module Mocktail
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.6"
3
3
  end
data/lib/mocktail.rb CHANGED
@@ -1,16 +1,19 @@
1
1
  require_relative "mocktail/dsl"
2
2
  require_relative "mocktail/errors"
3
+ require_relative "mocktail/explains_thing"
3
4
  require_relative "mocktail/handles_dry_call"
4
5
  require_relative "mocktail/handles_dry_new_call"
5
6
  require_relative "mocktail/imitates_type"
6
7
  require_relative "mocktail/initializes_mocktail"
7
8
  require_relative "mocktail/matcher_presentation"
8
9
  require_relative "mocktail/matchers"
10
+ require_relative "mocktail/raises_neato_no_method_error"
9
11
  require_relative "mocktail/registers_matcher"
10
12
  require_relative "mocktail/registers_stubbing"
11
13
  require_relative "mocktail/replaces_next"
12
14
  require_relative "mocktail/replaces_type"
13
15
  require_relative "mocktail/resets_state"
16
+ require_relative "mocktail/simulates_argument_error"
14
17
  require_relative "mocktail/value"
15
18
  require_relative "mocktail/verifies_call"
16
19
  require_relative "mocktail/version"
@@ -54,7 +57,13 @@ module Mocktail
54
57
  ResetsState.new.reset
55
58
  end
56
59
 
60
+ def self.explain(thing)
61
+ ExplainsThing.new.explain(thing)
62
+ end
63
+
57
64
  # Stores most transactional state about calls & stubbing configurations
65
+ # Anything returned by this is undocumented and could change at any time, so
66
+ # don't commit code that relies on it!
58
67
  def self.cabinet
59
68
  Thread.current[:mocktail_store] ||= Cabinet.new
60
69
  end
data/mocktail.gemspec CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |spec|
6
6
  spec.authors = ["Justin Searls"]
7
7
  spec.email = ["searls@gmail.com"]
8
8
 
9
- spec.summary = "your objects, less potency"
9
+ spec.summary = "Take your objects, and make them a double"
10
10
  spec.homepage = "https://github.com/testdouble/mocktail"
11
11
  spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
12
12