bogus 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/.gitignore +7 -0
  2. data/.pelusa.yml +1 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +75 -0
  6. data/Guardfile +15 -0
  7. data/README.md +272 -0
  8. data/Rakefile +1 -0
  9. data/bogus.gemspec +34 -0
  10. data/features/configuration_options.feature +43 -0
  11. data/features/contract_tests_mocks.feature +100 -0
  12. data/features/contract_tests_spies.feature +72 -0
  13. data/features/contract_tests_stubs.feature +101 -0
  14. data/features/fake_objects.feature +94 -0
  15. data/features/return_value_contracts.feature +90 -0
  16. data/features/safe_mocking.feature +59 -0
  17. data/features/safe_stubbing.feature +62 -0
  18. data/features/spies.feature +78 -0
  19. data/features/step_definitions/rspec_steps.rb +68 -0
  20. data/features/support/env.rb +1 -0
  21. data/lib/bogus.rb +35 -0
  22. data/lib/bogus/adds_recording.rb +11 -0
  23. data/lib/bogus/configuration.rb +9 -0
  24. data/lib/bogus/contract_not_fulfilled.rb +24 -0
  25. data/lib/bogus/converts_name_to_class.rb +31 -0
  26. data/lib/bogus/copies_classes.rb +44 -0
  27. data/lib/bogus/creates_fakes.rb +32 -0
  28. data/lib/bogus/double.rb +10 -0
  29. data/lib/bogus/fake.rb +33 -0
  30. data/lib/bogus/fake_registry.rb +13 -0
  31. data/lib/bogus/injector.rb +64 -0
  32. data/lib/bogus/interaction.rb +24 -0
  33. data/lib/bogus/interaction_presenter.rb +29 -0
  34. data/lib/bogus/interactions_repository.rb +19 -0
  35. data/lib/bogus/invocation_matcher.rb +27 -0
  36. data/lib/bogus/method_stringifier.rb +31 -0
  37. data/lib/bogus/overwrites_classes.rb +9 -0
  38. data/lib/bogus/proxy_class.rb +22 -0
  39. data/lib/bogus/public_methods.rb +46 -0
  40. data/lib/bogus/record_interactions.rb +17 -0
  41. data/lib/bogus/recording_proxy.rb +18 -0
  42. data/lib/bogus/records_double_interactions.rb +11 -0
  43. data/lib/bogus/registers_created_fakes.rb +11 -0
  44. data/lib/bogus/rr_proxy.rb +5 -0
  45. data/lib/bogus/rspec.rb +4 -0
  46. data/lib/bogus/rspec_extensions.rb +32 -0
  47. data/lib/bogus/takes.rb +8 -0
  48. data/lib/bogus/verifies_contracts.rb +12 -0
  49. data/lib/bogus/verifies_stub_definition.rb +37 -0
  50. data/lib/bogus/version.rb +3 -0
  51. data/pelusa.sh +3 -0
  52. data/rbs.sh +3 -0
  53. data/spec/bogus/adds_recording_spec.rb +33 -0
  54. data/spec/bogus/configuration_spec.rb +17 -0
  55. data/spec/bogus/converts_name_to_class_spec.rb +40 -0
  56. data/spec/bogus/copies_classes_spec.rb +175 -0
  57. data/spec/bogus/creates_fakes_spec.rb +59 -0
  58. data/spec/bogus/double_spec.rb +31 -0
  59. data/spec/bogus/fake_registry_spec.rb +24 -0
  60. data/spec/bogus/interaction_spec.rb +68 -0
  61. data/spec/bogus/interactions_repository_spec.rb +50 -0
  62. data/spec/bogus/invocation_matcher_spec.rb +26 -0
  63. data/spec/bogus/mocking_dsl_spec.rb +76 -0
  64. data/spec/bogus/overwrites_classes_spec.rb +26 -0
  65. data/spec/bogus/proxy_class_spec.rb +95 -0
  66. data/spec/bogus/record_interactions_spec.rb +27 -0
  67. data/spec/bogus/records_double_interactions_spec.rb +25 -0
  68. data/spec/bogus/registers_created_fakes_spec.rb +25 -0
  69. data/spec/bogus/verifies_contracts_spec.rb +42 -0
  70. data/spec/bogus/verifies_stub_definition_spec.rb +71 -0
  71. data/spec/spec_helper.rb +14 -0
  72. 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'
@@ -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,9 @@
1
+ module Bogus
2
+ class Configuration
3
+ attr_writer :search_modules
4
+
5
+ def search_modules
6
+ @search_modules ||= [Object]
7
+ end
8
+ end
9
+ 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