bogus 0.0.2 → 0.0.3.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile.lock +14 -2
  3. data/Guardfile +5 -0
  4. data/README.md +7 -249
  5. data/bogus.gemspec +3 -1
  6. data/features/.nav +21 -0
  7. data/features/authors.md +42 -0
  8. data/{CHANGELOG.md → features/changelog.md} +9 -2
  9. data/features/configuration_options.feature +1 -2
  10. data/features/{contract_tests_mocks.feature → contract_tests/contract_tests_mocks.feature} +4 -1
  11. data/features/{contract_tests_spies.feature → contract_tests/contract_tests_spies.feature} +2 -0
  12. data/features/{contract_tests_stubs.feature → contract_tests/contract_tests_stubs.feature} +14 -0
  13. data/features/contract_tests/readme.md +26 -0
  14. data/features/{return_value_contracts.feature → contract_tests/return_value_contracts.feature} +6 -2
  15. data/features/{anonymous_doubles.feature → fakes/anonymous_doubles.feature} +24 -8
  16. data/features/{fake_objects.feature → fakes/fake_objects.feature} +39 -4
  17. data/features/fakes/global_fake_configuration.feature +88 -0
  18. data/features/fakes/readme.md +11 -0
  19. data/features/fakes/replacing_classes.feature +148 -0
  20. data/features/getting_started.md +36 -0
  21. data/features/license.md +9 -0
  22. data/features/readme.md +173 -0
  23. data/features/safe_stubbing/argument_matchers.feature +30 -0
  24. data/features/safe_stubbing/readme.md +35 -0
  25. data/features/{safe_mocking.feature → safe_stubbing/safe_mocking.feature} +14 -0
  26. data/features/{safe_stubbing.feature → safe_stubbing/safe_stubbing.feature} +18 -4
  27. data/features/{spies.feature → safe_stubbing/spies.feature} +19 -4
  28. data/features/step_definitions/rspec_steps.rb +14 -5
  29. data/features/support/env.rb +4 -0
  30. data/lib/bogus.rb +2 -0
  31. data/lib/bogus/adds_recording.rb +11 -7
  32. data/lib/bogus/any_args.rb +7 -0
  33. data/lib/bogus/anything.rb +11 -0
  34. data/lib/bogus/contract_not_fulfilled.rb +17 -10
  35. data/lib/bogus/copies_classes.rb +2 -6
  36. data/lib/bogus/creates_fakes_with_stubbed_methods.rb +40 -0
  37. data/lib/bogus/double.rb +28 -8
  38. data/lib/bogus/ensures_all_interactions_satisfied.rb +36 -0
  39. data/lib/bogus/fake.rb +6 -0
  40. data/lib/bogus/fake_configuration.rb +69 -0
  41. data/lib/bogus/fakes_classes.rb +21 -0
  42. data/lib/bogus/has_overwritten_methods.rb +24 -0
  43. data/lib/bogus/have_received_matcher.rb +63 -0
  44. data/lib/bogus/injector.rb +36 -14
  45. data/lib/bogus/interaction.rb +33 -17
  46. data/lib/bogus/makes_substitute_methods.rb +15 -0
  47. data/lib/bogus/mocking_dsl.rb +31 -0
  48. data/lib/bogus/multi_stubber.rb +15 -0
  49. data/lib/bogus/not_all_expectations_satisfied.rb +27 -0
  50. data/lib/bogus/overwriten_classes.rb +15 -0
  51. data/lib/bogus/overwrites_classes.rb +2 -2
  52. data/lib/bogus/overwrites_methods.rb +42 -0
  53. data/lib/bogus/proxies_method_calls.rb +23 -0
  54. data/lib/bogus/proxy_class.rb +25 -16
  55. data/lib/bogus/public_methods.rb +36 -11
  56. data/lib/bogus/record_interactions.rb +3 -9
  57. data/lib/bogus/recording_proxy.rb +5 -0
  58. data/lib/bogus/registers_created_fakes.rb +2 -1
  59. data/lib/bogus/resets_overwritten_classes.rb +14 -0
  60. data/lib/bogus/resets_stubbed_methods.rb +12 -0
  61. data/lib/bogus/responds_to_everything.rb +11 -0
  62. data/lib/bogus/rspec.rb +7 -0
  63. data/lib/bogus/rspec_adapter.rb +17 -0
  64. data/lib/bogus/rspec_extensions.rb +8 -20
  65. data/lib/bogus/shadow.rb +60 -0
  66. data/lib/bogus/verifies_contracts.rb +5 -1
  67. data/lib/bogus/verifies_stub_definition.rb +5 -0
  68. data/lib/bogus/version.rb +1 -1
  69. data/lib/tracks_existence_of_test_doubles.rb +11 -0
  70. data/spec/bogus/adds_recording_spec.rb +46 -10
  71. data/spec/bogus/anything_spec.rb +13 -0
  72. data/spec/bogus/copies_classes_spec.rb +4 -3
  73. data/spec/bogus/creates_fakes_with_stubbed_methods_spec.rb +121 -0
  74. data/spec/bogus/double_spec.rb +63 -20
  75. data/spec/bogus/ensures_all_interactions_satisfied_spec.rb +43 -0
  76. data/spec/bogus/fake_configuration_spec.rb +99 -0
  77. data/spec/bogus/fakes_classes_spec.rb +46 -0
  78. data/spec/bogus/have_received_matcher_spec.rb +56 -0
  79. data/spec/bogus/interaction_spec.rb +18 -7
  80. data/spec/bogus/interactions_repository_spec.rb +42 -0
  81. data/spec/bogus/makes_substitute_methods_spec.rb +24 -0
  82. data/spec/bogus/mocking_dsl_spec.rb +234 -7
  83. data/spec/bogus/multi_stubber_spec.rb +31 -0
  84. data/spec/bogus/overwriten_classes_spec.rb +27 -0
  85. data/spec/bogus/overwrites_classes_spec.rb +2 -2
  86. data/spec/bogus/overwrites_methods_spec.rb +107 -0
  87. data/spec/bogus/proxy_class_spec.rb +6 -0
  88. data/spec/bogus/record_interactions_spec.rb +3 -4
  89. data/spec/bogus/registers_created_fakes_spec.rb +8 -0
  90. data/spec/bogus/resets_overwritten_classes_spec.rb +26 -0
  91. data/spec/bogus/resets_stubbed_methods_spec.rb +16 -0
  92. data/spec/bogus/shadow_spec.rb +182 -0
  93. data/spec/bogus/verifies_contracts_spec.rb +9 -3
  94. data/spec/bogus/verifies_stub_definition_spec.rb +4 -0
  95. data/spec/spec_helper.rb +3 -0
  96. data/spec/support/fake_creator_of_fakes.rb +15 -0
  97. data/spec/support/sample_fake.rb +13 -0
  98. data/spec/tracks_existence_of_test_doubles_spec.rb +26 -0
  99. metadata +105 -32
  100. data/lib/bogus/creates_anonymous_stubs.rb +0 -27
  101. data/lib/bogus/invocation_matcher.rb +0 -27
  102. data/lib/bogus/rr_proxy.rb +0 -5
  103. data/spec/bogus/invocation_matcher_spec.rb +0 -26
@@ -0,0 +1,7 @@
1
+ module Bogus
2
+ module AnyArgs
3
+ def self.inspect
4
+ "any_args"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Bogus
2
+ module Anything
3
+ def self.==(other)
4
+ true
5
+ end
6
+
7
+ def self.inspect
8
+ "anything"
9
+ end
10
+ end
11
+ end
@@ -1,24 +1,31 @@
1
1
  module Bogus
2
2
  class ContractNotFulfilled < StandardError
3
- attr_reader :interactions
3
+ attr_reader :fake_name, :missed_interactions, :actual_interactions
4
4
 
5
- def initialize(interactions)
6
- @interactions = interactions
5
+ def initialize(fake_name, opts = {})
6
+ @fake_name = fake_name
7
+ @actual_interactions = opts.fetch(:actual)
8
+ @missed_interactions = opts.fetch(:missed)
7
9
  super(message)
8
10
  end
9
11
 
10
12
  def message
11
- interactions.map { |fake_name, missed| missed_for_fake(fake_name, missed) }.join("\n")
12
- end
13
+ str = <<-EOF
14
+ Contract not fullfilled for #{fake_name}!
13
15
 
14
- private
16
+ Missed interactions:
17
+ #{interactions_str(missed_interactions)}
15
18
 
16
- def missed_for_fake(fake_name, missed)
17
- "Contract not fullfilled for #{fake_name}:\n#{missed_interactions(missed)}"
19
+ Actual interactions:
20
+ #{interactions_str(actual_interactions)}
21
+ EOF
22
+ str.gsub(' ' * 6, '')
18
23
  end
19
24
 
20
- def missed_interactions(missed)
21
- missed.map { |i| " - #{InteractionPresenter.new(i)}" }.join("\n")
25
+ private
26
+
27
+ def interactions_str(interactions)
28
+ interactions.map { |i| " - #{InteractionPresenter.new(i)}" }.join("\n")
22
29
  end
23
30
  end
24
31
  end
@@ -2,7 +2,7 @@ module Bogus
2
2
  class CopiesClasses
3
3
  extend Takes
4
4
 
5
- takes :method_stringifier
5
+ takes :makes_substitute_methods
6
6
 
7
7
  def copy(klass)
8
8
  copy_class = Class.new(Bogus::Fake)
@@ -33,11 +33,7 @@ module Bogus
33
33
  end
34
34
 
35
35
  def method_as_string(method)
36
- args = @method_stringifier.arguments_as_string(method.parameters)
37
- args_no_defaults = args.gsub(' = {}', '')
38
-
39
- @method_stringifier.stringify(method,
40
- "__record__(:#{method.name}, #{args_no_defaults})")
36
+ makes_substitute_methods.stringify(method)
41
37
  end
42
38
 
43
39
  end
@@ -0,0 +1,40 @@
1
+ module Bogus
2
+ class CreatesFakesWithStubbedMethods
3
+ extend Bogus::Takes
4
+
5
+ takes :multi_stubber, :creates_fakes,
6
+ :responds_to_everything, :fake_configuration
7
+
8
+ def create(name = nil, methods = {}, &block)
9
+ if name.is_a?(Hash)
10
+ methods = name
11
+ name = nil
12
+ end
13
+
14
+ fake = responds_to_everything unless name
15
+
16
+ fake_opts, methods = split_methods(methods)
17
+ fake_definition = get_configuration(name, fake_opts, methods, block)
18
+
19
+ fake ||= creates_fakes.create(fake_definition.name, fake_definition.opts,
20
+ &fake_definition.class_block)
21
+
22
+ multi_stubber.stub_all(fake, fake_definition.stubs)
23
+ end
24
+
25
+ private
26
+
27
+ def split_methods(methods)
28
+ fake_args = proc{ |k,_| [:as].include?(k) }
29
+ [methods.select(&fake_args), methods.reject(&fake_args)]
30
+ end
31
+
32
+ def get_configuration(name, fake_opts, methods, block)
33
+ fake = FakeDefinition.new(name: name, opts: fake_opts, stubs: methods, class_block: block)
34
+ return fake unless fake_configuration.include?(name)
35
+
36
+ configured_fake = fake_configuration.get(name)
37
+ configured_fake.merge(fake)
38
+ end
39
+ end
40
+ end
data/lib/bogus/double.rb CHANGED
@@ -1,10 +1,30 @@
1
- class Bogus::Double < BasicObject
2
- extend ::Bogus::Takes
3
- takes :object, :double, :verifies_stub_definition, :records_double_interactions
4
-
5
- def method_missing(name, *args, &block)
6
- @verifies_stub_definition.verify!(@object, name, args)
7
- @records_double_interactions.record(@object, name, args, &block)
8
- @double.__send__(name, *args, &block)
1
+ module Bogus
2
+ class Double
3
+ extend Takes
4
+ include ProxiesMethodCalls
5
+
6
+ takes :object, :double_tracker, :verifies_stub_definition,
7
+ :overwrites_methods, :records_double_interactions
8
+
9
+ def stub
10
+ proxy(:stubs)
11
+ end
12
+
13
+ def stubs(name, *args, &return_value)
14
+ double_tracker.track(object)
15
+ verifies_stub_definition.verify!(object, name, args)
16
+ records_double_interactions.record(object, name, args, &return_value)
17
+ overwrites_methods.overwrite(object, name)
18
+ object.__shadow__.stubs(name, *args, &return_value)
19
+ end
20
+
21
+ def mock
22
+ proxy(:mocks)
23
+ end
24
+
25
+ def mocks(name, *args, &return_value)
26
+ stubs(name, *args, &return_value)
27
+ object.__shadow__.mocks(name, *args, &return_value)
28
+ end
9
29
  end
10
30
  end
@@ -0,0 +1,36 @@
1
+ module Bogus
2
+ class EnsuresAllInteractionsSatisfied
3
+ extend Bogus::Takes
4
+
5
+ def ensure_satisfied!(objects)
6
+ unsatisfied = unsatisfied_interactions(objects)
7
+ return if unsatisfied.empty?
8
+
9
+ calls = all_calls(objects)
10
+ raise NotAllExpectationsSatisfied.create(unsatisfied, calls)
11
+ end
12
+
13
+ private
14
+
15
+ def unsatisfied_interactions(objects)
16
+ mapcat_shadows(objects) do |object, shadow|
17
+ shadow.unsatisfied_interactions.map{|c| [object, c]}
18
+ end
19
+ end
20
+
21
+ def all_calls(objects)
22
+ mapcat_shadows(objects) do |object, shadow|
23
+ shadow.calls.map{|c| [object, c]}
24
+ end
25
+ end
26
+
27
+ def mapcat_shadows(objects, &block)
28
+ mapped = objects.map do |object|
29
+ shadow = object.__shadow__
30
+ block.call(object, shadow)
31
+ end
32
+
33
+ mapped.reduce([], :concat)
34
+ end
35
+ end
36
+ end
data/lib/bogus/fake.rb CHANGED
@@ -1,7 +1,13 @@
1
1
  module Bogus
2
+ module FakeObject
3
+ # marker for fake objects
4
+ end
5
+
2
6
  class Fake
3
7
  include RecordInteractions
4
8
  extend RecordInteractions
9
+ include FakeObject
10
+ extend FakeObject
5
11
 
6
12
  def initialize(*args)
7
13
  end
@@ -0,0 +1,69 @@
1
+ module Bogus
2
+ class FakeConfiguration
3
+ def include?(name)
4
+ fakes.key?(name)
5
+ end
6
+
7
+ def fake(name, opts = {}, &block)
8
+ opts = opts.dup
9
+ class_block = opts.delete(:class)
10
+ fakes[name] = FakeDefinition.new(name: name,
11
+ opts: opts,
12
+ stubs: stubs_hash(&block),
13
+ class_block: class_block)
14
+ end
15
+
16
+ def evaluate(&block)
17
+ instance_eval(&block)
18
+ end
19
+
20
+ def get(name)
21
+ fakes[name]
22
+ end
23
+
24
+ private
25
+
26
+ def stubs_hash(&block)
27
+ stubs = StubsConfiguration.new(&block)
28
+ stubs.stubs
29
+ end
30
+
31
+ def fakes
32
+ @fakes ||= {}
33
+ end
34
+ end
35
+
36
+ class FakeDefinition
37
+ attr_reader :name, :class_block, :opts, :stubs
38
+
39
+ def initialize(attrs = {})
40
+ @name = attrs[:name]
41
+ @class_block = attrs[:class_block]
42
+ @opts = attrs[:opts] || {}
43
+ @stubs = attrs[:stubs] || {}
44
+ end
45
+
46
+ def merge(other)
47
+ FakeDefinition.new(name: other.name,
48
+ opts: opts.merge(other.opts),
49
+ stubs: stubs.merge(other.stubs),
50
+ class_block: other.class_block || class_block)
51
+ end
52
+ end
53
+
54
+ class StubsConfiguration
55
+ include ProxiesMethodCalls
56
+
57
+ def initialize(&block)
58
+ proxy(:add_stub).instance_eval(&block) if block
59
+ end
60
+
61
+ def add_stub(name, value = nil, &block)
62
+ stubs[name] = block || value
63
+ end
64
+
65
+ def stubs
66
+ @stubs ||= {}
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,21 @@
1
+ module Bogus
2
+ class FakesClasses
3
+ extend Takes
4
+
5
+ takes :creates_fakes_with_stubbed_methods, :overwrites_classes, :overwritten_classes
6
+
7
+ def fake(klass, opts = {})
8
+ opts = opts.merge(as: :class)
9
+ name = opts.delete(:fake_name) || underscore(klass.name.split('::').last).to_sym
10
+ fake = creates_fakes_with_stubbed_methods.create(name, opts) { klass }
11
+ overwrites_classes.overwrite(klass.name, fake)
12
+ overwritten_classes.add(klass.name, klass)
13
+ end
14
+
15
+ private
16
+
17
+ def underscore(str)
18
+ str.gsub(/[A-Z]/) { |s| "_" + s.downcase }.gsub(/^_/, '')
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ module Bogus
2
+ module HasOverwritenMethods
3
+ def __overwritten_methods__
4
+ @__overwritten_methods__ ||= {}
5
+ end
6
+
7
+ def __overwrite__(name, method, body)
8
+ return if __overwritten_methods__[name]
9
+ method = method.to_proc if method
10
+ __overwritten_methods__[name] = method || :no_such_method
11
+ instance_eval(body)
12
+ end
13
+
14
+ def __reset__
15
+ __overwritten_methods__.each do |name, method|
16
+ method = __overwritten_methods__.delete(name)
17
+ instance_eval "undef #{name}"
18
+ next if method == :no_such_method
19
+ define_singleton_method(name, method)
20
+ end
21
+ @__overwritten_methods__ = {}
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,63 @@
1
+ module Bogus
2
+ class HaveReceivedMatcher
3
+ include ProxiesMethodCalls
4
+
5
+ extend Takes
6
+ NO_SHADOW = "Given object is not a fake and nothing was ever stubbed or mocked on it!"
7
+
8
+ takes :verifies_stub_definition, :records_double_interactions
9
+
10
+ def matches?(subject)
11
+ @subject = subject
12
+ return false unless Shadow.has_shadow?(subject)
13
+
14
+ verifies_stub_definition.verify!(subject, @name, @args)
15
+ records_double_interactions.record(subject, @name, @args)
16
+
17
+ subject.__shadow__.has_received(@name, @args)
18
+ end
19
+
20
+ def description
21
+ "have received #{call_str(@name, @args)}"
22
+ end
23
+
24
+ def failure_message_for_should
25
+ return NO_SHADOW unless Shadow.has_shadow?(@subject)
26
+ %Q{Expected #{@subject.inspect} to #{description}, but it didn't.\n\n} + all_calls_str
27
+ end
28
+
29
+ def failure_message_for_should_not
30
+ return NO_SHADOW unless Shadow.has_shadow?(@subject)
31
+ %Q{Expected #{@subject.inspect} not to #{description}, but it did.}
32
+ end
33
+
34
+ def method_call
35
+ proxy(:set_method)
36
+ end
37
+
38
+ def set_method(name, *args, &block)
39
+ @name = name
40
+ @args = args
41
+ self
42
+ end
43
+
44
+ private
45
+
46
+ def call_str(method, args)
47
+ "#{method}(#{args.map(&:inspect).join(', ')})"
48
+ end
49
+
50
+ def all_calls_str
51
+ shadow = @subject.__shadow__
52
+ calls = shadow.calls.map{|i| call_str(i.method, i.args)}
53
+
54
+ if calls.any?
55
+ message = "The recorded interactions were:\n"
56
+ calls.each{|s| message << " - #{s}\n"}
57
+ message
58
+ else
59
+ "There were no interactions with this object.\n"
60
+ end
61
+ end
62
+ end
63
+ end
@@ -4,7 +4,11 @@ module Bogus
4
4
  look_in_modules Bogus
5
5
 
6
6
  def configuration
7
- @configuration ||= Bogus::Configuration.new
7
+ @configuration ||= inject(Configuration)
8
+ end
9
+
10
+ def fake_configuration
11
+ @fake_configuration ||= inject(FakeConfiguration)
8
12
  end
9
13
 
10
14
  def search_modules
@@ -15,47 +19,65 @@ module Bogus
15
19
  Bogus::RRProxy
16
20
  end
17
21
 
22
+ def rr_shadow(object)
23
+ inject(Bogus::RRShadow, object: object)
24
+ end
25
+
18
26
  def fake_registry
19
- @fake_registry ||= inject(Bogus::FakeRegistry)
27
+ @fake_registry ||= inject(FakeRegistry)
20
28
  end
21
29
 
22
30
  def creates_fakes
23
- creates_fakes = inject(Bogus::CreatesFakes)
24
- inject(Bogus::RegistersCreatedFakes, creates_fakes: creates_fakes)
31
+ creates_fakes = inject(CreatesFakes)
32
+ inject(RegistersCreatedFakes, creates_fakes: creates_fakes)
33
+ end
34
+
35
+ def create_double(object)
36
+ inject(Double, object: object)
25
37
  end
26
38
 
27
39
  def create_stub(object)
28
- stub = rr_proxy.stub(object)
29
- inject(Bogus::Double, object: object, double: stub)
40
+ create_double(object).stub
30
41
  end
31
42
 
32
43
  def create_mock(object)
33
- mock = rr_proxy.mock(object)
34
- inject(Bogus::Double, object: object, double: mock)
44
+ create_double(object).mock
35
45
  end
36
46
 
37
- def invocation_matcher(method = nil)
38
- inject(Bogus::InvocationMatcher, method: method)
47
+ def have_received_matcher
48
+ inject(HaveReceivedMatcher)
39
49
  end
40
50
 
41
51
  def interactions_repository
42
52
  raise "Specify either real_interactions or stubbed_interactions"
43
53
  end
44
54
 
55
+ def double_tracker
56
+ @double_tracker ||= inject(TracksExistenceOfTestDoubles)
57
+ end
58
+
59
+ def clear_tracked_doubles
60
+ @double_tracker = nil
61
+ end
62
+
45
63
  def real_interactions
46
- @real_interactions ||= inject(Bogus::InteractionsRepository)
64
+ @real_interactions ||= inject(InteractionsRepository)
47
65
  end
48
66
 
49
67
  def doubled_interactions
50
- @doubled_interactions ||= inject(Bogus::InteractionsRepository)
68
+ @doubled_interactions ||= inject(InteractionsRepository)
69
+ end
70
+
71
+ def overwritten_classes
72
+ @overwritten_classes ||= inject(OverwrittenClasses)
51
73
  end
52
74
 
53
75
  def create_proxy_class(fake_name, klass)
54
- inject(Bogus::ProxyClass, fake_name: fake_name, klass: klass)
76
+ inject(ProxyClass, fake_name: fake_name, klass: klass)
55
77
  end
56
78
 
57
79
  def create_recording_proxy(instance, fake_name)
58
- inject(Bogus::RecordingProxy,
80
+ inject(RecordingProxy,
59
81
  instance: instance,
60
82
  fake_name: fake_name,
61
83
  interactions_repository: real_interactions)