bogus 0.1.0 → 0.1.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 (48) hide show
  1. data/.travis.yml +1 -0
  2. data/Gemfile.lock +6 -1
  3. data/README.md +3 -0
  4. data/features/changelog.md +30 -22
  5. data/features/configuration/fake_ar_attributes.feature +3 -1
  6. data/features/configuration/search_modules.feature +5 -0
  7. data/features/contract_tests/contract_tests_mocks.feature +12 -1
  8. data/features/contract_tests/contract_tests_spies.feature +14 -2
  9. data/features/contract_tests/contract_tests_stubs.feature +13 -2
  10. data/features/contract_tests/return_value_contracts.feature +15 -4
  11. data/features/fakes/anonymous_doubles.feature +15 -1
  12. data/features/fakes/duck_types.feature +12 -2
  13. data/features/fakes/fake_objects.feature +11 -1
  14. data/features/fakes/global_fake_configuration.feature +4 -1
  15. data/features/readme.md +2 -2
  16. data/features/safe_stubbing/argument_matchers.feature +32 -2
  17. data/features/safe_stubbing/safe_mocking.feature +9 -2
  18. data/features/safe_stubbing/safe_stubbing.feature +11 -2
  19. data/features/safe_stubbing/spies.feature +52 -1
  20. data/features/step_definitions/rspec_steps.rb +1 -2
  21. data/features/support/env.rb +19 -0
  22. data/lib/bogus/active_record_accessors.rb +4 -4
  23. data/lib/bogus/adds_recording.rb +1 -0
  24. data/lib/bogus/any_args.rb +32 -3
  25. data/lib/bogus/class_methods.rb +7 -1
  26. data/lib/bogus/copies_classes.rb +5 -2
  27. data/lib/bogus/fake.rb +0 -2
  28. data/lib/bogus/have_received_matcher.rb +5 -0
  29. data/lib/bogus/interaction.rb +66 -35
  30. data/lib/bogus/interactions_repository.rb +1 -1
  31. data/lib/bogus/makes_substitute_methods.rb +3 -2
  32. data/lib/bogus/mocking_dsl.rb +8 -0
  33. data/lib/bogus/public_methods.rb +1 -1
  34. data/lib/bogus/rspec_extensions.rb +8 -1
  35. data/lib/bogus/same_class.rb +14 -0
  36. data/lib/bogus/shadow.rb +8 -12
  37. data/lib/bogus/verifies_stub_definition.rb +1 -5
  38. data/lib/bogus/version.rb +1 -1
  39. data/spec/bogus/adds_recording_spec.rb +8 -0
  40. data/spec/bogus/class_methods_spec.rb +8 -2
  41. data/spec/bogus/clean_ruby_spec.rb +15 -0
  42. data/spec/bogus/have_received_matcher_spec.rb +45 -27
  43. data/spec/bogus/instance_methods_spec.rb +1 -1
  44. data/spec/bogus/interaction_spec.rb +87 -83
  45. data/spec/bogus/interactions_repository_spec.rb +8 -11
  46. data/spec/bogus/shadow_spec.rb +4 -1
  47. metadata +81 -39
  48. checksums.yaml +0 -7
@@ -10,6 +10,7 @@ module Bogus
10
10
  new_klass = create_proxy_class.call(name, klass)
11
11
  overwrites_classes.overwrite(klass.name, new_klass)
12
12
  overwritten_classes.add(klass.name, klass)
13
+ new_klass
13
14
  end
14
15
  end
15
16
  end
@@ -1,7 +1,36 @@
1
1
  module Bogus
2
- module AnyArgs
3
- def self.inspect
4
- "any_args"
2
+ class WithArguments
3
+ attr_reader :predicate
4
+
5
+ def initialize(&predicate)
6
+ @predicate = predicate
5
7
  end
8
+
9
+ def matches?(args)
10
+ predicate.call(*args)
11
+ end
12
+
13
+ def self.matches?(opts = {})
14
+ stubbed = opts.fetch(:stubbed)
15
+ recorded = opts.fetch(:recorded)
16
+ return false unless with_matcher?(stubbed)
17
+ return extract(stubbed).matches?(recorded)
18
+ end
19
+
20
+ def self.with_matcher?(args)
21
+ args.first.is_a?(WithArguments)
22
+ end
23
+
24
+ private
25
+
26
+ def self.extract(args)
27
+ args.first
28
+ end
29
+ end
30
+
31
+ AnyArgs = WithArguments.new{ true }
32
+
33
+ def AnyArgs.inspect
34
+ "any_args"
6
35
  end
7
36
  end
@@ -4,7 +4,7 @@ module Bogus
4
4
  takes :klass
5
5
 
6
6
  def all
7
- klass.methods - Class.methods
7
+ klass.methods - Class.methods - bogus_methods
8
8
  end
9
9
 
10
10
  def get(name)
@@ -18,5 +18,11 @@ module Bogus
18
18
  def define(body)
19
19
  klass.instance_eval(body)
20
20
  end
21
+
22
+ private
23
+
24
+ def bogus_methods
25
+ [:__shadow__, :__reset__, :__overwrite__, :__overwritten_methods__, :__record__]
26
+ end
21
27
  end
22
28
  end
@@ -5,8 +5,11 @@ module Bogus
5
5
  takes :copies_methods
6
6
 
7
7
  def copy(klass)
8
- copy_class = Class.new(Bogus::Fake)
9
- copy_class.__copied_class__ = klass
8
+ copy_class = Class.new(Bogus::Fake) do
9
+ define_singleton_method(:__copied_class__) do
10
+ klass
11
+ end
12
+ end
10
13
  copies_methods.copy(klass, copy_class)
11
14
  copy_class
12
15
  end
@@ -26,8 +26,6 @@ module Bogus
26
26
  end
27
27
 
28
28
  class << self
29
- attr_accessor :__copied_class__
30
-
31
29
  alias :__create__ :new
32
30
 
33
31
  def new(*args, &block)
@@ -35,6 +35,11 @@ module Bogus
35
35
  proxy(:set_method)
36
36
  end
37
37
 
38
+ def build(*args)
39
+ return method_call if args.empty?
40
+ set_method(*args)
41
+ end
42
+
38
43
  def set_method(name, *args, &block)
39
44
  @name = name
40
45
  @args = args
@@ -1,5 +1,11 @@
1
1
  module Bogus
2
2
  class Interaction < Struct.new(:method, :args, :return_value, :error, :has_result)
3
+ attr_accessor :arguments
4
+
5
+ def self.same?(opts = {})
6
+ InteractionComparator.new(opts).same?
7
+ end
8
+
3
9
  def initialize(method, args, &block)
4
10
  self.method = method
5
11
  self.args = args
@@ -10,53 +16,78 @@ module Bogus
10
16
  end
11
17
  end
12
18
 
13
- def ==(other)
14
- method == other.method && same_args?(other) && same_result?(other)
15
- end
19
+ private
16
20
 
17
- def any_args?
18
- [AnyArgs] == args
21
+ def evaluate_return_value(block)
22
+ self.return_value = block.call
23
+ rescue => e
24
+ self.error = e.class
19
25
  end
20
26
 
21
- def args
22
- args = super.map { |arg| remove_default_values_from_hash(arg) }
23
- args.reject { |arg| arg.eql?(DefaultValue) }
24
- end
27
+ class InteractionComparator
28
+ attr_reader :recorded, :stubbed
25
29
 
26
- private
30
+ def initialize(opts = {})
31
+ @recorded = opts.fetch(:recorded)
32
+ @stubbed = opts.fetch(:stubbed)
33
+ end
27
34
 
28
- def same_args?(other)
29
- return true if any_args? || other.any_args?
35
+ def same?
36
+ return false unless recorded.method == stubbed.method
37
+ return false unless same_result?
38
+ same_args?
39
+ end
30
40
 
31
- other_args = normalize_other_args(args, other.args)
32
- return false unless args.size == other_args.size
33
- args.zip(other_args).all?{|a1, a2| a1 == a2 || a2 == a1}
34
- end
41
+ private
35
42
 
36
- def normalize_other_args(args, other_args)
37
- if args.last.is_a?(Hash) && !other_args.last.is_a?(Hash)
38
- other_args + [{}]
39
- else
40
- other_args
43
+ def same_args?
44
+ ArgumentComparator.new(recorded: recorded.args, stubbed: stubbed.args).same?
41
45
  end
42
- end
43
46
 
44
- def same_result?(other)
45
- return true unless has_result && other.has_result
46
- return_value == other.return_value && error == other.error
47
+ def same_result?
48
+ return true unless recorded.has_result && stubbed.has_result
49
+ recorded.return_value == stubbed.return_value && recorded.error == stubbed.error
50
+ end
47
51
  end
48
52
 
49
- def evaluate_return_value(block)
50
- self.return_value = block.call
51
- rescue => e
52
- self.error = e.class
53
- end
53
+ class ArgumentComparator
54
+ attr_reader :recorded, :stubbed
55
+
56
+ def initialize(opts = {})
57
+ @recorded = opts.fetch(:recorded)
58
+ @stubbed = opts.fetch(:stubbed)
59
+ end
60
+
61
+ def same?
62
+ return true if with_matcher_args?
63
+
64
+ stubbed == recorded_without_defaults
65
+ end
66
+
67
+ private
68
+
69
+ def recorded_without_defaults
70
+ without_defaults = recorded.reject{|v| DefaultValue == v}
71
+ remove_default_keywords(without_defaults)
72
+ end
73
+
74
+ def remove_default_keywords(recorded)
75
+ return recorded unless recorded_has_keyword?
76
+ positional = recorded[0...-1]
77
+ keyword = recorded.last
78
+ without_defaults = keyword.reject{|_, v| DefaultValue == v}
79
+ return positional if without_defaults.empty?
80
+ positional + [without_defaults]
81
+ end
82
+
83
+ def with_matcher_args?
84
+ WithArguments.matches?(stubbed: stubbed, recorded: recorded_without_defaults)
85
+ end
54
86
 
55
- def remove_default_values_from_hash(arg)
56
- if arg.is_a?(Hash)
57
- arg.reject { |_, val| val.eql?(DefaultValue) }
58
- else
59
- arg
87
+ def recorded_has_keyword?
88
+ last_recorded = recorded.last
89
+ return false unless last_recorded.is_a?(Hash)
90
+ last_recorded.values.any? { |v| DefaultValue == v }
60
91
  end
61
92
  end
62
93
  end
@@ -9,7 +9,7 @@ module Bogus
9
9
  end
10
10
 
11
11
  def recorded?(fake_name, interaction)
12
- @interactions[fake_name].include?(interaction)
12
+ @interactions[fake_name].any?{ |i| Interaction.same?(stubbed: interaction, recorded: i) }
13
13
  end
14
14
 
15
15
  def for_fake(fake_name)
@@ -7,8 +7,9 @@ module Bogus
7
7
  def stringify(method)
8
8
  args = method_stringifier.argument_values(method.parameters)
9
9
 
10
- method_stringifier.stringify(method,
11
- "__record__(:#{method.name}, #{args})")
10
+ send_args = [method.name.inspect, args].reject(&:empty?).join(', ')
11
+
12
+ method_stringifier.stringify(method, "__record__(#{send_args})")
12
13
  end
13
14
  end
14
15
  end
@@ -28,6 +28,14 @@ module Bogus
28
28
  Bogus::AnyArgs
29
29
  end
30
30
 
31
+ def with(&block)
32
+ Bogus::WithArguments.new(&block)
33
+ end
34
+
35
+ def any(klass)
36
+ Bogus::SameClass.new(klass)
37
+ end
38
+
31
39
  def anything
32
40
  Bogus::Anything
33
41
  end
@@ -34,7 +34,7 @@ module Bogus
34
34
  end
35
35
 
36
36
  def have_received(*args)
37
- inject.have_received_matcher(*args).method_call
37
+ inject.have_received_matcher.build(*args)
38
38
  end
39
39
 
40
40
  def fake_for(*args, &block)
@@ -11,8 +11,15 @@ module Bogus
11
11
  end
12
12
 
13
13
  def verify_contract(name)
14
+ old_described_class = described_class
15
+
14
16
  before do
15
- Bogus.record_calls_for(name, described_class)
17
+ new_class = Bogus.record_calls_for(name, described_class)
18
+ example.metadata[:example_group][:described_class] = new_class
19
+ end
20
+
21
+ after do
22
+ example.metadata[:example_group][:described_class] = old_described_class
16
23
  end
17
24
 
18
25
  RSpec.configure do |config|
@@ -0,0 +1,14 @@
1
+ module Bogus
2
+ class SameClass
3
+ extend Takes
4
+ takes :klass
5
+
6
+ def inspect
7
+ "any(#{klass.name})"
8
+ end
9
+
10
+ def ==(other)
11
+ other.is_a?(klass)
12
+ end
13
+ end
14
+ end
@@ -5,7 +5,6 @@ module Bogus
5
5
  def initialize
6
6
  @calls = []
7
7
  @stubs = []
8
- @defaults = {}
9
8
  @required = Set.new
10
9
  end
11
10
 
@@ -16,14 +15,13 @@ module Bogus
16
15
  end
17
16
 
18
17
  def has_received(name, args)
19
- @calls.include?(Interaction.new(name, args))
18
+ @calls.any? { |i| Interaction.same?(recorded: i, stubbed: Interaction.new(name, args)) }
20
19
  end
21
20
 
22
21
  def stubs(name, *args, &return_value)
23
22
  interaction = Interaction.new(name, args)
24
23
  add_stub(interaction, return_value)
25
- override_default(interaction, return_value)
26
- @required.delete(interaction)
24
+ @required.reject! { |i| Interaction.same?(recorded: i, stubbed: interaction) }
27
25
  interaction
28
26
  end
29
27
 
@@ -33,7 +31,11 @@ module Bogus
33
31
  end
34
32
 
35
33
  def unsatisfied_interactions
36
- @required.reject { |i| @calls.include?(i) }
34
+ @required.reject do |stubbed|
35
+ @calls.any? do |recorded|
36
+ Interaction.same?(recorded: recorded, stubbed: stubbed)
37
+ end
38
+ end
37
39
  end
38
40
 
39
41
  def self.has_shadow?(object)
@@ -42,18 +44,12 @@ module Bogus
42
44
 
43
45
  private
44
46
 
45
- def override_default(interaction, return_value)
46
- return unless interaction.any_args?
47
- @defaults[interaction.method] = return_value || proc{nil}
48
- end
49
-
50
47
  def add_stub(interaction, return_value_block)
51
48
  @stubs << [interaction, return_value_block] if return_value_block
52
49
  end
53
50
 
54
51
  def return_value(interaction)
55
- _, return_value = @stubs.reverse.find{|i, v| i == interaction}
56
- return_value ||= @defaults[interaction.method]
52
+ _, return_value = @stubs.reverse.find{|i, v| Interaction.same?(recorded: interaction, stubbed: i)}
57
53
  return_value ||= proc{ UndefinedReturnValue.new(interaction) }
58
54
  return_value.call
59
55
  end
@@ -7,7 +7,7 @@ module Bogus
7
7
  def verify!(object, method_name, args)
8
8
  stubbing_non_existent_method!(object, method_name) unless object.respond_to?(method_name)
9
9
  return unless object.methods.include?(method_name)
10
- return if any_args?(args)
10
+ return if WithArguments.with_matcher?(args)
11
11
  method = object.method(method_name)
12
12
  verify_call!(method, args)
13
13
  end
@@ -31,9 +31,5 @@ module Bogus
31
31
  def stubbing_non_existent_method!(object, method_name)
32
32
  raise NameError, "#{object.inspect} does not respond to #{method_name}"
33
33
  end
34
-
35
- def any_args?(args)
36
- [Bogus::AnyArgs] == args
37
- end
38
34
  end
39
35
  end
@@ -1,3 +1,3 @@
1
1
  module Bogus
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -43,6 +43,10 @@ describe Bogus::AddsRecording do
43
43
  it "records the overwritten class, so that it can be later restored" do
44
44
  overwritten_classes.should have_received.add("SampleModule::Library", SampleModule::Library)
45
45
  end
46
+
47
+ it "returns the proxy class" do
48
+ adds_recording.add(:library).should == Object
49
+ end
46
50
  end
47
51
 
48
52
  context "with class argument" do
@@ -65,5 +69,9 @@ describe Bogus::AddsRecording do
65
69
  it "records the overwritten class, so that it can be later restored" do
66
70
  overwritten_classes.should have_received.add("SampleModule::OtherClass", SampleModule::OtherClass)
67
71
  end
72
+
73
+ it "returns the proxy class" do
74
+ adds_recording.add(:library, SampleModule::OtherClass).should == Object
75
+ end
68
76
  end
69
77
  end
@@ -11,12 +11,18 @@ module Bogus
11
11
 
12
12
  def self.hello
13
13
  end
14
+
15
+ def self.__shadow__; end
16
+ def self.__reset__; end
17
+ def self.__overwrite__; end
18
+ def self.__overwritten_methods__; end
19
+ def self.__record__; end
14
20
  end
15
21
 
16
22
  let(:class_methods) { ClassMethods.new(SampleClass) }
17
23
 
18
- it "lists the instance methods" do
19
- class_methods.all.should == [:bar, :hello]
24
+ it "lists the class methods excluding the ones added by Bogus" do
25
+ class_methods.all.should =~ [:bar, :hello]
20
26
  end
21
27
 
22
28
  it "returns the instance methods by name" do
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Ruby syntax" do
4
+ it "is clean" do
5
+ bogus_warnings.should == []
6
+ end
7
+
8
+ def bogus_warnings
9
+ warnings.select { |w| w =~ %r{lib/bogus} }
10
+ end
11
+
12
+ def warnings
13
+ `ruby -w lib/bogus.rb 2>&1`.split("\n")
14
+ end
15
+ end