bogus 0.0.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.
- 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
|