bogus 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/.pelusa.yml +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +75 -0
- data/Guardfile +15 -0
- data/README.md +272 -0
- data/Rakefile +1 -0
- data/bogus.gemspec +34 -0
- data/features/configuration_options.feature +43 -0
- data/features/contract_tests_mocks.feature +100 -0
- data/features/contract_tests_spies.feature +72 -0
- data/features/contract_tests_stubs.feature +101 -0
- data/features/fake_objects.feature +94 -0
- data/features/return_value_contracts.feature +90 -0
- data/features/safe_mocking.feature +59 -0
- data/features/safe_stubbing.feature +62 -0
- data/features/spies.feature +78 -0
- data/features/step_definitions/rspec_steps.rb +68 -0
- data/features/support/env.rb +1 -0
- data/lib/bogus.rb +35 -0
- data/lib/bogus/adds_recording.rb +11 -0
- data/lib/bogus/configuration.rb +9 -0
- data/lib/bogus/contract_not_fulfilled.rb +24 -0
- data/lib/bogus/converts_name_to_class.rb +31 -0
- data/lib/bogus/copies_classes.rb +44 -0
- data/lib/bogus/creates_fakes.rb +32 -0
- data/lib/bogus/double.rb +10 -0
- data/lib/bogus/fake.rb +33 -0
- data/lib/bogus/fake_registry.rb +13 -0
- data/lib/bogus/injector.rb +64 -0
- data/lib/bogus/interaction.rb +24 -0
- data/lib/bogus/interaction_presenter.rb +29 -0
- data/lib/bogus/interactions_repository.rb +19 -0
- data/lib/bogus/invocation_matcher.rb +27 -0
- data/lib/bogus/method_stringifier.rb +31 -0
- data/lib/bogus/overwrites_classes.rb +9 -0
- data/lib/bogus/proxy_class.rb +22 -0
- data/lib/bogus/public_methods.rb +46 -0
- data/lib/bogus/record_interactions.rb +17 -0
- data/lib/bogus/recording_proxy.rb +18 -0
- data/lib/bogus/records_double_interactions.rb +11 -0
- data/lib/bogus/registers_created_fakes.rb +11 -0
- data/lib/bogus/rr_proxy.rb +5 -0
- data/lib/bogus/rspec.rb +4 -0
- data/lib/bogus/rspec_extensions.rb +32 -0
- data/lib/bogus/takes.rb +8 -0
- data/lib/bogus/verifies_contracts.rb +12 -0
- data/lib/bogus/verifies_stub_definition.rb +37 -0
- data/lib/bogus/version.rb +3 -0
- data/pelusa.sh +3 -0
- data/rbs.sh +3 -0
- data/spec/bogus/adds_recording_spec.rb +33 -0
- data/spec/bogus/configuration_spec.rb +17 -0
- data/spec/bogus/converts_name_to_class_spec.rb +40 -0
- data/spec/bogus/copies_classes_spec.rb +175 -0
- data/spec/bogus/creates_fakes_spec.rb +59 -0
- data/spec/bogus/double_spec.rb +31 -0
- data/spec/bogus/fake_registry_spec.rb +24 -0
- data/spec/bogus/interaction_spec.rb +68 -0
- data/spec/bogus/interactions_repository_spec.rb +50 -0
- data/spec/bogus/invocation_matcher_spec.rb +26 -0
- data/spec/bogus/mocking_dsl_spec.rb +76 -0
- data/spec/bogus/overwrites_classes_spec.rb +26 -0
- data/spec/bogus/proxy_class_spec.rb +95 -0
- data/spec/bogus/record_interactions_spec.rb +27 -0
- data/spec/bogus/records_double_interactions_spec.rb +25 -0
- data/spec/bogus/registers_created_fakes_spec.rb +25 -0
- data/spec/bogus/verifies_contracts_spec.rb +42 -0
- data/spec/bogus/verifies_stub_definition_spec.rb +71 -0
- data/spec/spec_helper.rb +14 -0
- metadata +299 -0
data/lib/bogus/takes.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
class Bogus::VerifiesContracts
|
2
|
+
extend Bogus::Takes
|
3
|
+
|
4
|
+
takes :doubled_interactions, :real_interactions
|
5
|
+
|
6
|
+
def verify(fake_name)
|
7
|
+
missed = doubled_interactions.for_fake(fake_name).reject do |interaction|
|
8
|
+
real_interactions.recorded?(fake_name, interaction)
|
9
|
+
end
|
10
|
+
raise Bogus::ContractNotFulfilled.new(fake_name => missed) unless missed.empty?
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Bogus::VerifiesStubDefinition
|
2
|
+
extend Bogus::Takes
|
3
|
+
|
4
|
+
takes :method_stringifier
|
5
|
+
|
6
|
+
def verify!(object, method_name, args)
|
7
|
+
stubbing_non_existent_method!(object, method_name) unless object.respond_to?(method_name)
|
8
|
+
method = object.method(method_name)
|
9
|
+
wrong_number_of_arguments!(method, args) if under_number_of_required_arguments?(method, args.size)
|
10
|
+
wrong_number_of_arguments!(method, args) if over_number_of_allowed_arguments?(method, args.size)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def wrong_number_of_arguments!(method, args)
|
16
|
+
args_string = method_stringifier.arguments_as_string(method.parameters)
|
17
|
+
raise ArgumentError, "tried to stub #{method.name}(#{args_string}) with #{args.size} arguments"
|
18
|
+
end
|
19
|
+
|
20
|
+
def stubbing_non_existent_method!(object, method_name)
|
21
|
+
raise NameError, "#{object.inspect} does not respond to #{method_name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def under_number_of_required_arguments?(method, args_count)
|
25
|
+
number_of_arguments = method.arity
|
26
|
+
number_of_arguments = -number_of_arguments - 1 if number_of_arguments < 0
|
27
|
+
|
28
|
+
args_count < number_of_arguments
|
29
|
+
end
|
30
|
+
|
31
|
+
def over_number_of_allowed_arguments?(method, args_count)
|
32
|
+
return false if method.parameters.find{|type, name| type == :rest}
|
33
|
+
number_of_arguments = method.parameters.count{|type, name| [:opt, :req].include?(type) }
|
34
|
+
|
35
|
+
args_count > number_of_arguments
|
36
|
+
end
|
37
|
+
end
|
data/pelusa.sh
ADDED
data/rbs.sh
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Bogus::AddsRecording do
|
4
|
+
module SampleModule
|
5
|
+
class Library
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:converts_name_to_class) { stub }
|
10
|
+
let(:create_proxy_class) { stub }
|
11
|
+
let(:overwrites_classes) { stub }
|
12
|
+
let(:adds_recording) { Bogus::AddsRecording.new(converts_name_to_class, create_proxy_class, overwrites_classes) }
|
13
|
+
|
14
|
+
before do
|
15
|
+
stub(converts_name_to_class).convert { SampleModule::Library }
|
16
|
+
stub(create_proxy_class).call { Object }
|
17
|
+
stub(overwrites_classes).overwrite
|
18
|
+
|
19
|
+
adds_recording.add(:library)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "converts the fake name to class" do
|
23
|
+
converts_name_to_class.should have_received.convert(:library)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "creates the proxy" do
|
27
|
+
create_proxy_class.should have_received.call(:library, SampleModule::Library)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "swaps the classes" do
|
31
|
+
overwrites_classes.should have_received.overwrite(SampleModule::Library, Object)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Bogus::Configuration do
|
4
|
+
after { Bogus.reset! }
|
5
|
+
|
6
|
+
it "stores the modules to be searched" do
|
7
|
+
Bogus.configure do |c|
|
8
|
+
c.search_modules << String
|
9
|
+
end
|
10
|
+
|
11
|
+
Bogus.config.search_modules.should == [Object, String]
|
12
|
+
end
|
13
|
+
|
14
|
+
it "defaults search_modules to [Object]" do
|
15
|
+
Bogus.config.search_modules.should == [Object]
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Bogus::ConvertsNameToClass do
|
4
|
+
FooBarBaz = Class.new
|
5
|
+
|
6
|
+
module Foo
|
7
|
+
FooBarBaz = Class.new
|
8
|
+
end
|
9
|
+
|
10
|
+
module Bar
|
11
|
+
FooBarBaz = Class.new
|
12
|
+
Bam = Class.new
|
13
|
+
end
|
14
|
+
|
15
|
+
it "finds classes in golbal namespace by default" do
|
16
|
+
converts_name_to_class = Bogus::ConvertsNameToClass.new(Bogus.config.search_modules)
|
17
|
+
|
18
|
+
converts_name_to_class.convert(:foo_bar_baz).should == FooBarBaz
|
19
|
+
end
|
20
|
+
|
21
|
+
it "looks in the modules in the specified order" do
|
22
|
+
converts_name_to_class = Bogus::ConvertsNameToClass.new([Foo, Bar])
|
23
|
+
|
24
|
+
converts_name_to_class.convert(:foo_bar_baz).should == Foo::FooBarBaz
|
25
|
+
end
|
26
|
+
|
27
|
+
it "looks in the next module on the list if the first does not contain the class" do
|
28
|
+
converts_name_to_class = Bogus::ConvertsNameToClass.new([Foo, Bar])
|
29
|
+
|
30
|
+
converts_name_to_class.convert(:bam).should == Bar::Bam
|
31
|
+
end
|
32
|
+
|
33
|
+
it "raises an error if it can't find the class" do
|
34
|
+
converts_name_to_class = Bogus::ConvertsNameToClass.new([Foo])
|
35
|
+
|
36
|
+
expect do
|
37
|
+
converts_name_to_class.convert(:bam)
|
38
|
+
end.to raise_error(Bogus::ConvertsNameToClass::CanNotFindClass)
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Bogus::CopiesClasses do
|
4
|
+
module SampleMethods
|
5
|
+
def foo
|
6
|
+
end
|
7
|
+
|
8
|
+
def bar(x)
|
9
|
+
end
|
10
|
+
|
11
|
+
def baz(x, *y)
|
12
|
+
end
|
13
|
+
|
14
|
+
def bam(opts = {})
|
15
|
+
end
|
16
|
+
|
17
|
+
def baa(x, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
shared_examples_for 'the copied class' do
|
22
|
+
it "copies methods with no arguments" do
|
23
|
+
subject.should respond_to(:foo)
|
24
|
+
subject.foo
|
25
|
+
end
|
26
|
+
|
27
|
+
it "copies methods with explicit arguments" do
|
28
|
+
subject.should respond_to(:bar)
|
29
|
+
|
30
|
+
subject.method(:bar).arity.should == 1
|
31
|
+
|
32
|
+
subject.bar('hello')
|
33
|
+
end
|
34
|
+
|
35
|
+
it "copies methods with variable arguments" do
|
36
|
+
subject.should respond_to(:baz)
|
37
|
+
|
38
|
+
subject.baz('hello', 'foo', 'bar', 'baz')
|
39
|
+
end
|
40
|
+
|
41
|
+
it "copies methods with default arguments" do
|
42
|
+
subject.should respond_to(:bam)
|
43
|
+
|
44
|
+
subject.bam
|
45
|
+
subject.bam(hello: 'world')
|
46
|
+
end
|
47
|
+
|
48
|
+
it "copies methods with block arguments" do
|
49
|
+
subject.should respond_to(:baa)
|
50
|
+
|
51
|
+
subject.baa('hello')
|
52
|
+
subject.baa('hello') {}
|
53
|
+
end
|
54
|
+
|
55
|
+
it "makes the methods chainable" do
|
56
|
+
subject.foo.bar('hello').baz('hello', 'world', 'foo').bam.baa('foo')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
let(:method_stringifier) { Bogus::MethodStringifier.new }
|
61
|
+
let(:copies_classes) { Bogus::CopiesClasses.new(method_stringifier) }
|
62
|
+
let(:fake_class) { copies_classes.copy(klass) }
|
63
|
+
let(:fake) { fake_class.new }
|
64
|
+
|
65
|
+
class FooWithInstanceMethods
|
66
|
+
include SampleMethods
|
67
|
+
end
|
68
|
+
|
69
|
+
context "instance methods" do
|
70
|
+
let(:klass) { FooWithInstanceMethods }
|
71
|
+
subject{ fake }
|
72
|
+
|
73
|
+
it_behaves_like 'the copied class'
|
74
|
+
end
|
75
|
+
|
76
|
+
context "constructors" do
|
77
|
+
let(:klass) {
|
78
|
+
Class.new do
|
79
|
+
def initialize(hello)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
}
|
83
|
+
|
84
|
+
it "adds a constructor that allows passing any number of arguments" do
|
85
|
+
fake_class.new('hello', 'w', 'o', 'r', 'l', 'd') { test }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class ClassWithClassMethods
|
90
|
+
extend SampleMethods
|
91
|
+
end
|
92
|
+
|
93
|
+
context "class methods" do
|
94
|
+
let(:klass) { ClassWithClassMethods }
|
95
|
+
subject{ fake_class }
|
96
|
+
|
97
|
+
it_behaves_like 'the copied class'
|
98
|
+
end
|
99
|
+
|
100
|
+
context "identification" do
|
101
|
+
module SomeModule
|
102
|
+
class SomeClass
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
let(:klass) { SomeModule::SomeClass }
|
107
|
+
|
108
|
+
it "should copy the class name" do
|
109
|
+
fake.class.name.should == 'SomeModule::SomeClass'
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should override kind_of?" do
|
113
|
+
fake.should be_kind_of(SomeModule::SomeClass)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should override instance_of?" do
|
117
|
+
fake.should be_instance_of(SomeModule::SomeClass)
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should override is_a?" do
|
121
|
+
fake.should be_a(SomeModule::SomeClass)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should include class name in the output of fake's class #to_s" do
|
125
|
+
fake.class.to_s.should include(klass.name)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should include class name in the output of fake's #to_s" do
|
129
|
+
fake.to_s.should include(klass.name)
|
130
|
+
end
|
131
|
+
|
132
|
+
# TODO
|
133
|
+
it "should override kind_of?/instance_of? for base classes of copied class"
|
134
|
+
it "should override kind_of? for modules included into copied class"
|
135
|
+
end
|
136
|
+
|
137
|
+
shared_examples_for 'spying' do
|
138
|
+
def should_record(method, *args)
|
139
|
+
mock(subject).__record__(method, *args)
|
140
|
+
|
141
|
+
subject.send(method, *args)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "records method calls with no arguments" do
|
145
|
+
should_record(:foo)
|
146
|
+
end
|
147
|
+
|
148
|
+
it "records method calls with explicit arguments" do
|
149
|
+
should_record(:bar, 'hello')
|
150
|
+
end
|
151
|
+
|
152
|
+
it "records method calls with variable arguments" do
|
153
|
+
should_record(:baz, 'hello', 'foo', 'bar', 'baz')
|
154
|
+
end
|
155
|
+
|
156
|
+
it "records method calls with default arguments" do
|
157
|
+
should_record(:bam, hello: 'world')
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context "spying on an instance" do
|
162
|
+
let(:klass) { FooWithInstanceMethods }
|
163
|
+
subject{ fake }
|
164
|
+
|
165
|
+
include_examples 'spying'
|
166
|
+
end
|
167
|
+
|
168
|
+
context "spying on copied class" do
|
169
|
+
let(:klass) { ClassWithClassMethods }
|
170
|
+
subject { fake_class }
|
171
|
+
|
172
|
+
include_examples 'spying'
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Bogus::CreatesFakes do
|
4
|
+
let(:fake_class) { stub() }
|
5
|
+
let(:fake_instance) { stub() }
|
6
|
+
let(:converts_name_to_class) { stub() }
|
7
|
+
let(:copies_classes) { stub() }
|
8
|
+
let(:creates_fakes) { Bogus::CreatesFakes.new(copies_classes, converts_name_to_class) }
|
9
|
+
|
10
|
+
module Foo
|
11
|
+
end
|
12
|
+
|
13
|
+
module Bar
|
14
|
+
end
|
15
|
+
|
16
|
+
before { stub(fake_class).new{fake_instance} }
|
17
|
+
|
18
|
+
context "without block" do
|
19
|
+
before do
|
20
|
+
mock(converts_name_to_class).convert(:foo) { Foo }
|
21
|
+
mock(copies_classes).copy(Foo) { fake_class }
|
22
|
+
end
|
23
|
+
|
24
|
+
it "creates a new instance of copied class by default" do
|
25
|
+
creates_fakes.create(:foo).should == fake_instance
|
26
|
+
end
|
27
|
+
|
28
|
+
it "creates a new instance of copied class if called with as: :instance" do
|
29
|
+
creates_fakes.create(:foo, as: :instance).should == fake_instance
|
30
|
+
end
|
31
|
+
|
32
|
+
it "copies class but does not create an instance if called with as: :class" do
|
33
|
+
creates_fakes.create(:foo, as: :class).should == fake_class
|
34
|
+
end
|
35
|
+
|
36
|
+
it "raises an error if the as mode is not known" do
|
37
|
+
expect do
|
38
|
+
creates_fakes.create(:foo, as: :something)
|
39
|
+
end.to raise_error(Bogus::CreatesFakes::UnknownMode)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with block" do
|
44
|
+
before do
|
45
|
+
stub(converts_name_to_class).convert
|
46
|
+
mock(copies_classes).copy(Bar) { fake_class }
|
47
|
+
end
|
48
|
+
|
49
|
+
it "uses the class provided" do
|
50
|
+
creates_fakes.create(:foo){Bar}.should == fake_instance
|
51
|
+
end
|
52
|
+
|
53
|
+
it "does not convert the class name" do
|
54
|
+
creates_fakes.create(:foo) { Bar}
|
55
|
+
|
56
|
+
copies_classes.should_not have_received.convert
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Bogus::Double do
|
4
|
+
let(:rr_double) { stub }
|
5
|
+
let(:verifies_stub_definition) { stub }
|
6
|
+
let(:records_double_interactions) { stub }
|
7
|
+
|
8
|
+
let(:object) { "strings have plenty of methods to call" }
|
9
|
+
|
10
|
+
let(:bogus_double) { isolate(Bogus::Double, double: rr_double) }
|
11
|
+
|
12
|
+
before do
|
13
|
+
stub(verifies_stub_definition).verify!
|
14
|
+
stub(records_double_interactions).record
|
15
|
+
stub(rr_double).method_name
|
16
|
+
|
17
|
+
bogus_double.method_name(:foo, :bar)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "verifies that stub definition matches the real definition" do
|
21
|
+
verifies_stub_definition.should have_received.verify!(object, :method_name, [:foo, :bar])
|
22
|
+
end
|
23
|
+
|
24
|
+
it "records the stub interaction so that it can be verified later" do
|
25
|
+
records_double_interactions.should have_received.record(object, :method_name, [:foo, :bar])
|
26
|
+
end
|
27
|
+
|
28
|
+
it "proxies the method call" do
|
29
|
+
rr_double.should have_received.method_name(:foo, :bar)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Bogus::FakeRegistry do
|
4
|
+
let(:fake_registry) { Bogus::FakeRegistry.new }
|
5
|
+
|
6
|
+
it "knows the fake's names" do
|
7
|
+
object = Object.new
|
8
|
+
|
9
|
+
fake_registry.store(:name, object)
|
10
|
+
|
11
|
+
fake_registry.name(object).should == :name
|
12
|
+
end
|
13
|
+
|
14
|
+
it "returns name based on object identity" do
|
15
|
+
example = Struct.new(:id)
|
16
|
+
|
17
|
+
object = example.new(1)
|
18
|
+
duplicate = example.new(1)
|
19
|
+
|
20
|
+
fake_registry.store(:object, object)
|
21
|
+
|
22
|
+
fake_registry.name(duplicate).should be_nil
|
23
|
+
end
|
24
|
+
end
|