bogus 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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