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
@@ -0,0 +1,59 @@
|
|
1
|
+
Feature: Safe mocking
|
2
|
+
|
3
|
+
Background:
|
4
|
+
Given a file named "foo.rb" with:
|
5
|
+
"""ruby
|
6
|
+
class Library
|
7
|
+
def checkout(book)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
"""
|
11
|
+
|
12
|
+
Scenario: Mocking methods that exist on real object
|
13
|
+
Then spec file with following content should pass:
|
14
|
+
"""ruby
|
15
|
+
describe Library do
|
16
|
+
|
17
|
+
it "does something" do
|
18
|
+
library = Library.new
|
19
|
+
mock(library).checkout("some book") { :checked_out }
|
20
|
+
|
21
|
+
library.checkout("some book").should == :checked_out
|
22
|
+
end
|
23
|
+
end
|
24
|
+
"""
|
25
|
+
|
26
|
+
Scenario: Mocking methods that do not exist on real object
|
27
|
+
Then spec file with following content should fail:
|
28
|
+
"""ruby
|
29
|
+
describe Library do
|
30
|
+
it "does something" do
|
31
|
+
library = Library.new
|
32
|
+
mock(library).buy("some book") { :bought }
|
33
|
+
library.buy("some book")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
"""
|
37
|
+
|
38
|
+
Scenario: Mocking methods with wrong number of arguments
|
39
|
+
Then spec file with following content should fail:
|
40
|
+
"""ruby
|
41
|
+
describe Library do
|
42
|
+
it "does something" do
|
43
|
+
library = Library.new
|
44
|
+
mock(library).checkout("some book", "another book") { :bought }
|
45
|
+
library.checkout("some book", "another book")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
"""
|
49
|
+
|
50
|
+
Scenario: Mocks require the methods to be called
|
51
|
+
Then spec file with following content should fail:
|
52
|
+
"""ruby
|
53
|
+
describe Library do
|
54
|
+
it "does something" do
|
55
|
+
library = Library.new
|
56
|
+
mock(library).checkout("some book") { :bought }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
"""
|
@@ -0,0 +1,62 @@
|
|
1
|
+
Feature: Safe stubbing
|
2
|
+
|
3
|
+
Most Ruby test double libraries let you stub methods that don't exist.
|
4
|
+
Bogus is different in this respect: not only does it not allow stubbing
|
5
|
+
methods that don't exist, it also ensures that the number of arguments
|
6
|
+
you pass to those methods matches the method definition.
|
7
|
+
|
8
|
+
Background:
|
9
|
+
Given a file named "foo.rb" with:
|
10
|
+
"""ruby
|
11
|
+
class Library
|
12
|
+
def checkout(book)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
"""
|
16
|
+
|
17
|
+
Scenario: Stubbing methods that exist on real object
|
18
|
+
Then spec file with following content should pass:
|
19
|
+
"""ruby
|
20
|
+
describe Library do
|
21
|
+
|
22
|
+
it "does something" do
|
23
|
+
library = Library.new
|
24
|
+
stub(library).checkout("some book") { :checked_out }
|
25
|
+
|
26
|
+
library.checkout("some book").should == :checked_out
|
27
|
+
end
|
28
|
+
end
|
29
|
+
"""
|
30
|
+
|
31
|
+
Scenario: Stubbing methods that do not exist on real object
|
32
|
+
Then spec file with following content should fail:
|
33
|
+
"""ruby
|
34
|
+
describe Library do
|
35
|
+
it "does something" do
|
36
|
+
library = Library.new
|
37
|
+
stub(library).buy("some book") { :bought }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
"""
|
41
|
+
|
42
|
+
Scenario: Stubbing methods with wrong number of arguments
|
43
|
+
Then spec file with following content should fail:
|
44
|
+
"""ruby
|
45
|
+
describe Library do
|
46
|
+
it "does something" do
|
47
|
+
library = Library.new
|
48
|
+
stub(library).checkout("some book", "another book") { :bought }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
"""
|
52
|
+
|
53
|
+
Scenario: Stubs allow the methods to be called
|
54
|
+
Then spec file with following content should pass:
|
55
|
+
"""ruby
|
56
|
+
describe Library do
|
57
|
+
it "does something" do
|
58
|
+
library = Library.new
|
59
|
+
stub(library).checkout("some book") { :bought }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
"""
|
@@ -0,0 +1,78 @@
|
|
1
|
+
Feature: Spies
|
2
|
+
|
3
|
+
Object Oriented Programming is all about messages sent between the objects.
|
4
|
+
If you follow principles like "Tell, Don't Ask", you will enable yourself
|
5
|
+
to combine Bogus's powerful feature of faking objects with it's ability
|
6
|
+
to verify object interactions.
|
7
|
+
|
8
|
+
Background:
|
9
|
+
Given a file named "foo.rb" with:
|
10
|
+
"""ruby
|
11
|
+
class Library
|
12
|
+
def checkout(book)
|
13
|
+
# marks book as checked out
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Student
|
18
|
+
def initialize(library)
|
19
|
+
@library = library
|
20
|
+
end
|
21
|
+
|
22
|
+
def study(*book_titles)
|
23
|
+
book_titles.each do |book_title|
|
24
|
+
@library.checkout(book_title)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
"""
|
29
|
+
|
30
|
+
Scenario: Ensuring methods were called
|
31
|
+
Then spec file with following content should pass:
|
32
|
+
"""ruby
|
33
|
+
describe Student do
|
34
|
+
fake(:library)
|
35
|
+
|
36
|
+
it "studies using books from library" do
|
37
|
+
student = Student.new(library)
|
38
|
+
|
39
|
+
student.study("Moby Dick", "Sherlock Holmes")
|
40
|
+
|
41
|
+
library.should have_received.checkout("Moby Dick")
|
42
|
+
library.should have_received.checkout("Sherlock Holmes")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
"""
|
46
|
+
|
47
|
+
Scenario: Spying on methods that do not exist
|
48
|
+
Then spec file with following content should fail:
|
49
|
+
"""ruby
|
50
|
+
describe Student do
|
51
|
+
fake(:library)
|
52
|
+
|
53
|
+
it "studies using books from library" do
|
54
|
+
student = Student.new(library)
|
55
|
+
|
56
|
+
student.study("Moby Dick")
|
57
|
+
|
58
|
+
library.should_not have_received.return_book("Moby Dick")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
"""
|
62
|
+
|
63
|
+
Scenario: Spying on methods with wrong number of arguments
|
64
|
+
Then spec file with following content should fail:
|
65
|
+
"""ruby
|
66
|
+
describe Student do
|
67
|
+
fake(:library)
|
68
|
+
|
69
|
+
it "studies using books from library" do
|
70
|
+
student = Student.new(library)
|
71
|
+
|
72
|
+
student.study("Moby Dick", "Sherlock Holmes")
|
73
|
+
|
74
|
+
library.should_not have_received.checkout("Moby Dick",
|
75
|
+
"Sherlock Holmes")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
"""
|
@@ -0,0 +1,68 @@
|
|
1
|
+
Given /^a spec file named "([^"]*)" with:$/ do |file_name, string|
|
2
|
+
@spec_file_names ||= []
|
3
|
+
@spec_file_names << file_name
|
4
|
+
|
5
|
+
steps %Q{
|
6
|
+
Given a file named "#{file_name}" with:
|
7
|
+
"""ruby
|
8
|
+
require 'bogus'
|
9
|
+
require 'bogus/rspec'
|
10
|
+
require 'rr'
|
11
|
+
|
12
|
+
require_relative 'foo'
|
13
|
+
|
14
|
+
RSpec.configure do |config|
|
15
|
+
config.mock_with :rr
|
16
|
+
end
|
17
|
+
|
18
|
+
#{string}
|
19
|
+
"""
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
Then /^the specs should fail$/ do
|
24
|
+
steps %Q{
|
25
|
+
Then the exit status should be 1
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
Then /^all the specs should pass$/ do
|
30
|
+
steps %Q{
|
31
|
+
Then the exit status should be 0
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
When /^I run spec with the following content:$/ do |string|
|
36
|
+
file_name = 'foo_spec.rb'
|
37
|
+
|
38
|
+
steps %Q{
|
39
|
+
Given a spec file named "#{file_name}" with:
|
40
|
+
"""ruby
|
41
|
+
#{string}
|
42
|
+
"""
|
43
|
+
}
|
44
|
+
|
45
|
+
steps %Q{
|
46
|
+
When I run `rspec #{@spec_file_names.join(' ')}`
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
Then /^spec file with following content should pass:$/ do |string|
|
51
|
+
steps %Q{
|
52
|
+
When I run spec with the following content:
|
53
|
+
"""ruby
|
54
|
+
#{string}
|
55
|
+
"""
|
56
|
+
Then all the specs should pass
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
Then /^spec file with following content should fail:$/ do |string|
|
61
|
+
steps %Q{
|
62
|
+
When I run spec with the following content:
|
63
|
+
"""ruby
|
64
|
+
#{string}
|
65
|
+
"""
|
66
|
+
Then the specs should fail
|
67
|
+
}
|
68
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'aruba/cucumber'
|
data/lib/bogus.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'dependor'
|
2
|
+
|
3
|
+
module Bogus
|
4
|
+
autoload :AddsRecording, 'bogus/adds_recording'
|
5
|
+
autoload :Configuration, 'bogus/configuration'
|
6
|
+
autoload :ContractNotFulfilled, 'bogus/contract_not_fulfilled'
|
7
|
+
autoload :ConvertsNameToClass, 'bogus/converts_name_to_class'
|
8
|
+
autoload :CopiesClasses, 'bogus/copies_classes'
|
9
|
+
autoload :CreatesFakes, 'bogus/creates_fakes'
|
10
|
+
autoload :Double, 'bogus/double'
|
11
|
+
autoload :Fake, 'bogus/fake'
|
12
|
+
autoload :FakeRegistry, 'bogus/fake_registry'
|
13
|
+
autoload :Injector, 'bogus/injector'
|
14
|
+
autoload :Interaction, 'bogus/interaction'
|
15
|
+
autoload :InteractionPresenter, 'bogus/interaction_presenter'
|
16
|
+
autoload :InteractionsRepository, 'bogus/interactions_repository'
|
17
|
+
autoload :InvocationMatcher, 'bogus/invocation_matcher'
|
18
|
+
autoload :MethodStringifier, 'bogus/method_stringifier'
|
19
|
+
autoload :MockingDSL, 'bogus/rspec_extensions'
|
20
|
+
autoload :OverwritesClasses, 'bogus/overwrites_classes'
|
21
|
+
autoload :ProxyClass, 'bogus/proxy_class'
|
22
|
+
autoload :PublicMethods, 'bogus/public_methods'
|
23
|
+
autoload :RRProxy, 'bogus/rr_proxy'
|
24
|
+
autoload :RSpecExtensions, 'bogus/rspec_extensions'
|
25
|
+
autoload :RecordInteractions, 'bogus/record_interactions'
|
26
|
+
autoload :RecordingProxy, 'bogus/recording_proxy'
|
27
|
+
autoload :RecordsDoubleInteractions, 'bogus/records_double_interactions'
|
28
|
+
autoload :RegistersCreatedFakes, 'bogus/registers_created_fakes'
|
29
|
+
autoload :Takes, 'bogus/takes'
|
30
|
+
autoload :VERSION, 'bogus/version'
|
31
|
+
autoload :VerifiesContracts, 'bogus/verifies_contracts'
|
32
|
+
autoload :VerifiesStubDefinition, 'bogus/verifies_stub_definition'
|
33
|
+
|
34
|
+
extend PublicMethods
|
35
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class Bogus::AddsRecording
|
2
|
+
extend Bogus::Takes
|
3
|
+
|
4
|
+
takes :converts_name_to_class, :create_proxy_class, :overwrites_classes
|
5
|
+
|
6
|
+
def add(name)
|
7
|
+
klass = converts_name_to_class.convert(name)
|
8
|
+
new_klass = create_proxy_class.call(name, klass)
|
9
|
+
overwrites_classes.overwrite(klass, new_klass)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Bogus
|
2
|
+
class ContractNotFulfilled < StandardError
|
3
|
+
attr_reader :interactions
|
4
|
+
|
5
|
+
def initialize(interactions)
|
6
|
+
@interactions = interactions
|
7
|
+
super(message)
|
8
|
+
end
|
9
|
+
|
10
|
+
def message
|
11
|
+
interactions.map { |fake_name, missed| missed_for_fake(fake_name, missed) }.join("\n")
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def missed_for_fake(fake_name, missed)
|
17
|
+
"Contract not fullfilled for #{fake_name}:\n#{missed_interactions(missed)}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def missed_interactions(missed)
|
21
|
+
missed.map { |i| " - #{InteractionPresenter.new(i)}" }.join("\n")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Bogus
|
2
|
+
class ConvertsNameToClass
|
3
|
+
extend Takes
|
4
|
+
|
5
|
+
class CanNotFindClass < RuntimeError; end
|
6
|
+
|
7
|
+
takes :search_modules
|
8
|
+
|
9
|
+
def convert(name)
|
10
|
+
class_name = camelize(name)
|
11
|
+
klass = nil
|
12
|
+
|
13
|
+
@search_modules.each do |mod|
|
14
|
+
klass = mod.const_get(class_name) rescue nil
|
15
|
+
break if klass
|
16
|
+
end
|
17
|
+
|
18
|
+
raise CanNotFindClass.new("Can not locate class for name: #{name}") unless klass
|
19
|
+
|
20
|
+
klass
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def camelize(symbol)
|
26
|
+
string = symbol.to_s
|
27
|
+
string = string.gsub(/_\w/) { |match| match[1].upcase }
|
28
|
+
return string.gsub(/^\w/) { |match| match.upcase }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Bogus
|
2
|
+
class CopiesClasses
|
3
|
+
extend Takes
|
4
|
+
|
5
|
+
takes :method_stringifier
|
6
|
+
|
7
|
+
def copy(klass)
|
8
|
+
copy_class = Class.new(Bogus::Fake)
|
9
|
+
|
10
|
+
copy_class.__copied_class__ = klass
|
11
|
+
copy_instance_methods(klass, copy_class)
|
12
|
+
copy_class_methods(klass, copy_class)
|
13
|
+
|
14
|
+
return copy_class
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def copy_instance_methods(klass, copy_class)
|
20
|
+
instance_methods = klass.instance_methods - Object.instance_methods
|
21
|
+
|
22
|
+
instance_methods.each do |name|
|
23
|
+
copy_class.class_eval(method_as_string(klass.instance_method(name)))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def copy_class_methods(klass, copy_class)
|
28
|
+
klass_methods = klass.methods - Class.methods
|
29
|
+
|
30
|
+
klass_methods.each do |name|
|
31
|
+
copy_class.instance_eval(method_as_string(klass.method(name)))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
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})")
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Bogus
|
2
|
+
class CreatesFakes
|
3
|
+
class UnknownMode < RuntimeError; end
|
4
|
+
|
5
|
+
extend Takes
|
6
|
+
|
7
|
+
takes :copies_classes, :converts_name_to_class
|
8
|
+
|
9
|
+
def create(name, opts = {}, &block)
|
10
|
+
klass = self.klass(name, &block)
|
11
|
+
klass_copy = copies_classes.copy(klass)
|
12
|
+
|
13
|
+
mode = opts.fetch(:as, :instance)
|
14
|
+
|
15
|
+
case mode
|
16
|
+
when :instance
|
17
|
+
return klass_copy.new
|
18
|
+
when :class
|
19
|
+
return klass_copy
|
20
|
+
else
|
21
|
+
raise UnknownMode.new("Unknown fake creation mode: #{mode}. Allowed values are :instance, :class")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def klass(name, &block)
|
28
|
+
return block.call if block_given?
|
29
|
+
converts_name_to_class.convert(name)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|