bogus 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -0
- data/Gemfile.lock +6 -1
- data/README.md +3 -0
- data/features/changelog.md +30 -22
- data/features/configuration/fake_ar_attributes.feature +3 -1
- data/features/configuration/search_modules.feature +5 -0
- data/features/contract_tests/contract_tests_mocks.feature +12 -1
- data/features/contract_tests/contract_tests_spies.feature +14 -2
- data/features/contract_tests/contract_tests_stubs.feature +13 -2
- data/features/contract_tests/return_value_contracts.feature +15 -4
- data/features/fakes/anonymous_doubles.feature +15 -1
- data/features/fakes/duck_types.feature +12 -2
- data/features/fakes/fake_objects.feature +11 -1
- data/features/fakes/global_fake_configuration.feature +4 -1
- data/features/readme.md +2 -2
- data/features/safe_stubbing/argument_matchers.feature +32 -2
- data/features/safe_stubbing/safe_mocking.feature +9 -2
- data/features/safe_stubbing/safe_stubbing.feature +11 -2
- data/features/safe_stubbing/spies.feature +52 -1
- data/features/step_definitions/rspec_steps.rb +1 -2
- data/features/support/env.rb +19 -0
- data/lib/bogus/active_record_accessors.rb +4 -4
- data/lib/bogus/adds_recording.rb +1 -0
- data/lib/bogus/any_args.rb +32 -3
- data/lib/bogus/class_methods.rb +7 -1
- data/lib/bogus/copies_classes.rb +5 -2
- data/lib/bogus/fake.rb +0 -2
- data/lib/bogus/have_received_matcher.rb +5 -0
- data/lib/bogus/interaction.rb +66 -35
- data/lib/bogus/interactions_repository.rb +1 -1
- data/lib/bogus/makes_substitute_methods.rb +3 -2
- data/lib/bogus/mocking_dsl.rb +8 -0
- data/lib/bogus/public_methods.rb +1 -1
- data/lib/bogus/rspec_extensions.rb +8 -1
- data/lib/bogus/same_class.rb +14 -0
- data/lib/bogus/shadow.rb +8 -12
- data/lib/bogus/verifies_stub_definition.rb +1 -5
- data/lib/bogus/version.rb +1 -1
- data/spec/bogus/adds_recording_spec.rb +8 -0
- data/spec/bogus/class_methods_spec.rb +8 -2
- data/spec/bogus/clean_ruby_spec.rb +15 -0
- data/spec/bogus/have_received_matcher_spec.rb +45 -27
- data/spec/bogus/instance_methods_spec.rb +1 -1
- data/spec/bogus/interaction_spec.rb +87 -83
- data/spec/bogus/interactions_repository_spec.rb +8 -11
- data/spec/bogus/shadow_spec.rb +4 -1
- metadata +81 -39
- checksums.yaml +0 -7
data/lib/bogus/adds_recording.rb
CHANGED
data/lib/bogus/any_args.rb
CHANGED
@@ -1,7 +1,36 @@
|
|
1
1
|
module Bogus
|
2
|
-
|
3
|
-
|
4
|
-
|
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
|
data/lib/bogus/class_methods.rb
CHANGED
@@ -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
|
data/lib/bogus/copies_classes.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/bogus/fake.rb
CHANGED
data/lib/bogus/interaction.rb
CHANGED
@@ -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
|
-
|
14
|
-
method == other.method && same_args?(other) && same_result?(other)
|
15
|
-
end
|
19
|
+
private
|
16
20
|
|
17
|
-
def
|
18
|
-
|
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
|
-
|
22
|
-
|
23
|
-
args.reject { |arg| arg.eql?(DefaultValue) }
|
24
|
-
end
|
27
|
+
class InteractionComparator
|
28
|
+
attr_reader :recorded, :stubbed
|
25
29
|
|
26
|
-
|
30
|
+
def initialize(opts = {})
|
31
|
+
@recorded = opts.fetch(:recorded)
|
32
|
+
@stubbed = opts.fetch(:stubbed)
|
33
|
+
end
|
27
34
|
|
28
|
-
|
29
|
-
|
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
|
-
|
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
|
-
|
37
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
@@ -7,8 +7,9 @@ module Bogus
|
|
7
7
|
def stringify(method)
|
8
8
|
args = method_stringifier.argument_values(method.parameters)
|
9
9
|
|
10
|
-
|
11
|
-
|
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
|
data/lib/bogus/mocking_dsl.rb
CHANGED
data/lib/bogus/public_methods.rb
CHANGED
@@ -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|
|
data/lib/bogus/shadow.rb
CHANGED
@@ -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.
|
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
|
-
|
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
|
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|
|
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
|
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
|
data/lib/bogus/version.rb
CHANGED
@@ -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
|
19
|
-
class_methods.all.should
|
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
|