bogus 0.0.2 → 0.0.3.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile.lock +14 -2
  3. data/Guardfile +5 -0
  4. data/README.md +7 -249
  5. data/bogus.gemspec +3 -1
  6. data/features/.nav +21 -0
  7. data/features/authors.md +42 -0
  8. data/{CHANGELOG.md → features/changelog.md} +9 -2
  9. data/features/configuration_options.feature +1 -2
  10. data/features/{contract_tests_mocks.feature → contract_tests/contract_tests_mocks.feature} +4 -1
  11. data/features/{contract_tests_spies.feature → contract_tests/contract_tests_spies.feature} +2 -0
  12. data/features/{contract_tests_stubs.feature → contract_tests/contract_tests_stubs.feature} +14 -0
  13. data/features/contract_tests/readme.md +26 -0
  14. data/features/{return_value_contracts.feature → contract_tests/return_value_contracts.feature} +6 -2
  15. data/features/{anonymous_doubles.feature → fakes/anonymous_doubles.feature} +24 -8
  16. data/features/{fake_objects.feature → fakes/fake_objects.feature} +39 -4
  17. data/features/fakes/global_fake_configuration.feature +88 -0
  18. data/features/fakes/readme.md +11 -0
  19. data/features/fakes/replacing_classes.feature +148 -0
  20. data/features/getting_started.md +36 -0
  21. data/features/license.md +9 -0
  22. data/features/readme.md +173 -0
  23. data/features/safe_stubbing/argument_matchers.feature +30 -0
  24. data/features/safe_stubbing/readme.md +35 -0
  25. data/features/{safe_mocking.feature → safe_stubbing/safe_mocking.feature} +14 -0
  26. data/features/{safe_stubbing.feature → safe_stubbing/safe_stubbing.feature} +18 -4
  27. data/features/{spies.feature → safe_stubbing/spies.feature} +19 -4
  28. data/features/step_definitions/rspec_steps.rb +14 -5
  29. data/features/support/env.rb +4 -0
  30. data/lib/bogus.rb +2 -0
  31. data/lib/bogus/adds_recording.rb +11 -7
  32. data/lib/bogus/any_args.rb +7 -0
  33. data/lib/bogus/anything.rb +11 -0
  34. data/lib/bogus/contract_not_fulfilled.rb +17 -10
  35. data/lib/bogus/copies_classes.rb +2 -6
  36. data/lib/bogus/creates_fakes_with_stubbed_methods.rb +40 -0
  37. data/lib/bogus/double.rb +28 -8
  38. data/lib/bogus/ensures_all_interactions_satisfied.rb +36 -0
  39. data/lib/bogus/fake.rb +6 -0
  40. data/lib/bogus/fake_configuration.rb +69 -0
  41. data/lib/bogus/fakes_classes.rb +21 -0
  42. data/lib/bogus/has_overwritten_methods.rb +24 -0
  43. data/lib/bogus/have_received_matcher.rb +63 -0
  44. data/lib/bogus/injector.rb +36 -14
  45. data/lib/bogus/interaction.rb +33 -17
  46. data/lib/bogus/makes_substitute_methods.rb +15 -0
  47. data/lib/bogus/mocking_dsl.rb +31 -0
  48. data/lib/bogus/multi_stubber.rb +15 -0
  49. data/lib/bogus/not_all_expectations_satisfied.rb +27 -0
  50. data/lib/bogus/overwriten_classes.rb +15 -0
  51. data/lib/bogus/overwrites_classes.rb +2 -2
  52. data/lib/bogus/overwrites_methods.rb +42 -0
  53. data/lib/bogus/proxies_method_calls.rb +23 -0
  54. data/lib/bogus/proxy_class.rb +25 -16
  55. data/lib/bogus/public_methods.rb +36 -11
  56. data/lib/bogus/record_interactions.rb +3 -9
  57. data/lib/bogus/recording_proxy.rb +5 -0
  58. data/lib/bogus/registers_created_fakes.rb +2 -1
  59. data/lib/bogus/resets_overwritten_classes.rb +14 -0
  60. data/lib/bogus/resets_stubbed_methods.rb +12 -0
  61. data/lib/bogus/responds_to_everything.rb +11 -0
  62. data/lib/bogus/rspec.rb +7 -0
  63. data/lib/bogus/rspec_adapter.rb +17 -0
  64. data/lib/bogus/rspec_extensions.rb +8 -20
  65. data/lib/bogus/shadow.rb +60 -0
  66. data/lib/bogus/verifies_contracts.rb +5 -1
  67. data/lib/bogus/verifies_stub_definition.rb +5 -0
  68. data/lib/bogus/version.rb +1 -1
  69. data/lib/tracks_existence_of_test_doubles.rb +11 -0
  70. data/spec/bogus/adds_recording_spec.rb +46 -10
  71. data/spec/bogus/anything_spec.rb +13 -0
  72. data/spec/bogus/copies_classes_spec.rb +4 -3
  73. data/spec/bogus/creates_fakes_with_stubbed_methods_spec.rb +121 -0
  74. data/spec/bogus/double_spec.rb +63 -20
  75. data/spec/bogus/ensures_all_interactions_satisfied_spec.rb +43 -0
  76. data/spec/bogus/fake_configuration_spec.rb +99 -0
  77. data/spec/bogus/fakes_classes_spec.rb +46 -0
  78. data/spec/bogus/have_received_matcher_spec.rb +56 -0
  79. data/spec/bogus/interaction_spec.rb +18 -7
  80. data/spec/bogus/interactions_repository_spec.rb +42 -0
  81. data/spec/bogus/makes_substitute_methods_spec.rb +24 -0
  82. data/spec/bogus/mocking_dsl_spec.rb +234 -7
  83. data/spec/bogus/multi_stubber_spec.rb +31 -0
  84. data/spec/bogus/overwriten_classes_spec.rb +27 -0
  85. data/spec/bogus/overwrites_classes_spec.rb +2 -2
  86. data/spec/bogus/overwrites_methods_spec.rb +107 -0
  87. data/spec/bogus/proxy_class_spec.rb +6 -0
  88. data/spec/bogus/record_interactions_spec.rb +3 -4
  89. data/spec/bogus/registers_created_fakes_spec.rb +8 -0
  90. data/spec/bogus/resets_overwritten_classes_spec.rb +26 -0
  91. data/spec/bogus/resets_stubbed_methods_spec.rb +16 -0
  92. data/spec/bogus/shadow_spec.rb +182 -0
  93. data/spec/bogus/verifies_contracts_spec.rb +9 -3
  94. data/spec/bogus/verifies_stub_definition_spec.rb +4 -0
  95. data/spec/spec_helper.rb +3 -0
  96. data/spec/support/fake_creator_of_fakes.rb +15 -0
  97. data/spec/support/sample_fake.rb +13 -0
  98. data/spec/tracks_existence_of_test_doubles_spec.rb +26 -0
  99. metadata +105 -32
  100. data/lib/bogus/creates_anonymous_stubs.rb +0 -27
  101. data/lib/bogus/invocation_matcher.rb +0 -27
  102. data/lib/bogus/rr_proxy.rb +0 -5
  103. data/spec/bogus/invocation_matcher_spec.rb +0 -26
data/.gitignore CHANGED
@@ -5,3 +5,5 @@ tmp/*
5
5
  .rvmrc
6
6
  tags
7
7
  .rbx
8
+ tags
9
+ gems.tags
data/Gemfile.lock CHANGED
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bogus (0.0.2)
4
+ bogus (0.0.3.rc.1)
5
5
  dependor (>= 0.0.4)
6
- rr
7
6
 
8
7
  GEM
9
8
  remote: http://rubygems.org/
10
9
  specs:
10
+ archive-tar-minitar (0.5.2)
11
11
  aruba (0.4.11)
12
12
  childprocess (>= 0.2.3)
13
13
  cucumber (>= 1.1.1)
@@ -30,6 +30,8 @@ GEM
30
30
  guard (1.1.1)
31
31
  listen (>= 0.4.2)
32
32
  thor (>= 0.14.6)
33
+ guard-ctags-bundler (0.1.6)
34
+ guard (>= 1.1)
33
35
  guard-cucumber (1.1.0)
34
36
  cucumber (>= 1.2.0)
35
37
  guard (>= 1.1.0)
@@ -42,12 +44,19 @@ GEM
42
44
  rb-fchange (~> 0.0.5)
43
45
  rb-fsevent (~> 0.9.1)
44
46
  rb-inotify (~> 0.8.8)
47
+ mime-types (1.19)
45
48
  rake (0.9.2.2)
46
49
  rb-fchange (0.0.5)
47
50
  ffi
48
51
  rb-fsevent (0.9.1)
49
52
  rb-inotify (0.8.8)
50
53
  ffi (>= 0.5.0)
54
+ relish (0.6)
55
+ archive-tar-minitar (>= 0.5.2)
56
+ json (>= 1.4.6)
57
+ rest-client (>= 1.6.1)
58
+ rest-client (1.6.7)
59
+ mime-types (>= 1.16)
51
60
  rr (1.0.4)
52
61
  rspec (2.10.0)
53
62
  rspec-core (~> 2.10.0)
@@ -68,8 +77,11 @@ DEPENDENCIES
68
77
  cucumber
69
78
  growl
70
79
  guard
80
+ guard-ctags-bundler
71
81
  guard-cucumber
72
82
  guard-rspec
73
83
  libnotify
74
84
  rake
85
+ relish
86
+ rr
75
87
  rspec
data/Guardfile CHANGED
@@ -13,3 +13,8 @@ guard 'rspec', :version => 2 do
13
13
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
14
14
  watch('spec/spec_helper.rb') { "spec" }
15
15
  end
16
+
17
+ guard 'ctags-bundler', :src_path => ["lib", "spec"] do
18
+ watch(/^(lib|spec)\/.*\.rb$/)
19
+ watch('Gemfile.lock')
20
+ end
data/README.md CHANGED
@@ -7,266 +7,24 @@
7
7
 
8
8
  Bogus aims to make your unit tests more reliable by ensuring that you don't stub or mock methods that don't actually exist in the mocked objects.
9
9
 
10
- ## What are fakes?
10
+ Bogus provides facilities to create *fakes* - test doubles that have the same interface as the doubled class.
11
11
 
12
- Fakes are alternative implementations of classes for testing purposes.
13
- In most cases, fakes are just named stubs: they don't implement the logic (otherwise they should be tested too), but they have the same interface as the real object and they make it easier to remove duplication in your stubs.
12
+ Another feature of Bogus is *safe stubbing*, which does not allow you to stub methods that don't exist or don't match the signature specified when stubbing.
14
13
 
15
- ## Stubs and mocks scatter the interface specification all over your specs
14
+ A unique feature of Bogus are the *contract tests*, which reduce the need for integrated tests to a minimum by ensuring that the things you stub match how the object really behaves.
16
15
 
17
- Let's assume that you have a couple of classes that share a collaborator. In this case we have a finds_users object that allows finding users by location and suggests_followings and lists_users_near_city that use it in turn to compute a list of users that match some criteria.
16
+ ## Documentation
18
17
 
19
- ```ruby
20
- class FindsUsers
21
- takes :users_repository
22
-
23
- def near_point(location, distance_in_km);
24
- # do something with users_repository and return a list of users
25
- end
26
- end
27
-
28
- class Geocoder
29
- takes :http_client
30
-
31
- def location_for(address)
32
- # do something to http_client and return location
33
- end
34
- end
35
-
36
- class SuggestsFollowings
37
- takes :finds_users
38
-
39
- def suggestions_for(user)
40
- finds_users.near_point(user.location, 50).select{|user| user.followers_count > 10}
41
- end
42
- end
43
-
44
- class ListsUsersNearCity
45
- takes :finds_users, :geocoder
46
-
47
- def users(city)
48
- city_location = geocoder.location_for(city.name)
49
- finds_users.near_point(city_location, 10)
50
- end
51
- end
52
- ```
53
-
54
- Typically, when testing SuggestsFollowings and ListsUsersNearCity you'd have code like this in your specs:
55
-
56
- ```ruby
57
- describe SuggestsFollowings
58
- let(:finds_users) { stub(:finds_users) }
59
- let(:user) { new_user }
60
-
61
- subject { SuggestsFollowings.new(finds_users) }
62
-
63
- it "returns users with follower count of 10 or more" do
64
- user_with_followers = new_user(followers_count: 10)
65
- users = [new_user(followers_count: 5), user_with_followers]
66
-
67
- finds_users.should_receive(:near_point).with(user.location, 50).and_return(users)
68
-
69
- subject.suggestions_for(user).should == [user_with_followers]
70
- end
71
-
72
- # ...
73
- end
74
-
75
- describe ListsUsersNearCity
76
- let(:finds_users) { stub(:finds_users) }
77
- let(:geocoder) { stub(:geocoder) }
78
- let(:city) { new_city(name: "New York") }
79
-
80
- subject { ListsUsersNearCity.new(finds_users, geocoder) }
81
-
82
- it "returns users near center of the city" do
83
- location = Location.new(1, 2)
84
- users = [new_user, new_user]
85
-
86
- geocoder.should_receive(:location_for).with("New York").and_return(location)
87
- finds_users.should_receive(:near_point).with(location, 10).and_return(users)
88
-
89
- subject.users(city).should == users
90
- end
91
-
92
- # ...
93
- end
94
- ```
95
-
96
- Notice how the stubbing/mocking code is duplicated between both specs.
97
- This might not be a huge problem in a small project, but as your codebase grows, there might be more and more objects that interact with finds_users, and the more objects interact with finds_users, the more places it's interface is specified in using stubbing or mock expectations.
98
-
99
- ## Bogus to the rescue!
100
-
101
- Bogus is aimed to help you remove duplication in specifying interfaces from your specs.
102
- It will reduce the need for integration testing, because with Bogus, you make sure that the test doubles you use have the same interface as the real object, while still allowing you to test in isolation and benefit from that.
103
-
104
- So how would your tests look like if you used Bogus?
105
-
106
- ```ruby
107
- require 'bogus/rspec'
108
-
109
- Bogus.configure do |config|
110
- config.spy_by_default = true
111
- config.stub_dsl = :rr # only :rr available for now
112
- end
113
-
114
- shared_context 'fakes' do
115
- fake(:geocoder) # same as let(:geocoder) { Bogus.fake_for(:geocoder) }
116
- fake(:finds_users)
117
- end
118
-
119
- describe SuggestsFollowings
120
- include_context "fakes"
121
-
122
- let(:user) { new_user }
123
-
124
- subject { SuggestsFollowings.new(finds_users) }
125
-
126
- it "returns users with follower count of 10 or more" do
127
- user_with_followers = new_user(followers_count: 10)
128
- users = [new_user(followers_count: 5), user_with_followers]
129
-
130
- # this will actually fail if FindsUsers does not have a near_point method
131
- # that takes 2 arguments
132
- mock(finds_users).near_point(user.location, 50) { users }
133
-
134
- subject.suggestions_for(user).should == [user_with_followers]
135
- end
136
-
137
- # ...
138
- end
139
-
140
- describe ListsUsersNearCity
141
- include_context "fakes"
142
-
143
- let(:city) { new_city(name: "New York") }
144
-
145
- subject { ListsUsersNearCity.new(finds_users, geocoder) }
146
-
147
- it "returns users near center of the city" do
148
- location = Location.new(1, 2)
149
- users = [new_user, new_user]
150
-
151
- mock(geocoder).location_for("New York") { location }
152
- mock(finds_users).near_point(location, 10) { users }
153
-
154
- subject.users(city).should == users
155
- end
156
-
157
- # ...
158
- end
159
- ```
160
-
161
- As you can see, by using Bogus you can keep writing truly isolated unit tests just like you are used to, but with higher degree of security, because Bogus makes sure, that the methods you are stubbing actually exist and take same arguments.
162
-
163
- ## Bogus and commands
164
-
165
- By default, fakes created by Bogus are nil-objects.
166
- This is particularly useful when dealing with "command" methods or if you follow the "tell, don't ask" principle.
167
-
168
- ```ruby
169
- class SendsEmailNotifications
170
- def notify_password_changed(user)
171
- # do something
172
- end
173
- end
174
-
175
- class Logger
176
- def info(*message)
177
- end
178
- end
179
-
180
- class ChangesPassword
181
- takes :sends_email_notifications, :logger
182
-
183
- def change_password(user, old_pass, new_pass)
184
- # change user password
185
- user.password = new_pass if user.password == old_pass
186
-
187
- # notify by email
188
- sends_email_notifications.notify_password_changed(user)
189
-
190
- # and log
191
- logger.info("User", user.name, "changed password")
192
- end
193
- end
194
-
195
- describe ChangesPassword do
196
- fake(:sends_email_notifications)
197
- fake(:logger)
198
-
199
- let(:user) { new_user }
200
-
201
- subject{ ChangesPassword.new(sends_email_notifications, logger) }
202
-
203
- it "sends the email notification" do
204
- subject.change_password(user, 'old', 'new')
205
-
206
- # no need to set up the stub on sends_email_notifications for this to work
207
- sends_email_notifications.should have_received.notify_password_changed(user)
208
- end
209
- end
210
- ```
211
-
212
- The beautiful thing is, that even if you don't care about testing something (like logging in this example), you still get the benefit of ensuring that the interface matches, even in a fully isolated test.
213
-
214
- ## Contract tests
215
-
216
- Knowing that a method exists and takes the right number of parameters is great, but why stop there?
217
- Bogus can also make sure that the interface you stub is actually tested somewhere with the correct arguments and return value.
218
-
219
- ```ruby
220
- describe ChangesPassword do
221
- fake(:sends_email_notifications)
222
- fake(:logger)
223
-
224
- let(:user) { new_user }
225
-
226
- subject{ ChangesPassword.new(sends_email_notifications, logger) }
227
-
228
- it "sends the email notification" do
229
- subject.change_password(user, 'old', 'new')
230
-
231
- sends_email_notifications.should have_received.notify_password_changed(user)
232
- end
233
- end
234
- ```
235
-
236
- In the above spec, SendsEmailNotifications#notify_password_changed is specified as a method that takes a user as an argument.
237
- Thanks to Bogus, we already know that there is a class named SendsEmailNotifications and it has a #notify_password_changed method that takes one argument.
238
- But how can we be sure that this argument is a User, and not a String for example?
239
-
240
- Here's how Bogus can help with this problem:
241
-
242
- ```ruby
243
- describe SendsEmailNotifications do
244
- verify_contract(:sends_email_notifications)
245
-
246
- let(:sends_email_notifications) { SendsEmailNotifications.new }
247
-
248
- it "send email notifications" do
249
- user = new_user
250
-
251
- sends_email_notifications.notify_password_changed(user)
252
-
253
- # ...
254
- end
255
- end
256
- ```
18
+ [You can find our (executable) documentation on Relish.][docs]
257
19
 
258
20
  ## License
259
21
 
260
22
  MIT. See the LICENSE file.
261
23
 
262
- ## TODO:
263
-
264
- This is a README driven project, so don't expect stuff in the README to be implemented ;P
265
-
266
- See the features directory for the completed functionality.
267
-
268
24
  ## Authors
269
25
 
270
26
  * [Adam Pohorecki](http://github.com/psyho)
271
27
  * [Paweł Pierzchała](http://github.com/wrozka)
272
28
  * [Marek Nowak](https://github.com/yundt)
29
+
30
+ [docs]: http://www.relishapp.com/bogus/bogus/docs
data/bogus.gemspec CHANGED
@@ -19,7 +19,6 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
 
21
21
  s.add_dependency 'dependor', '>= 0.0.4'
22
- s.add_dependency 'rr'
23
22
 
24
23
  s.add_development_dependency 'rake'
25
24
  s.add_development_dependency 'rspec'
@@ -29,6 +28,9 @@ Gem::Specification.new do |s|
29
28
  s.add_development_dependency 'guard'
30
29
  s.add_development_dependency 'guard-rspec'
31
30
  s.add_development_dependency 'guard-cucumber'
31
+ s.add_development_dependency 'guard-ctags-bundler'
32
32
  s.add_development_dependency 'growl'
33
33
  s.add_development_dependency 'libnotify'
34
+ s.add_development_dependency 'rr'
35
+ s.add_development_dependency 'relish'
34
36
  end
data/features/.nav ADDED
@@ -0,0 +1,21 @@
1
+ - getting_started.md (Getting Started)
2
+ - readme.md (Introduction to Bogus)
3
+ - changelog.md
4
+ - fakes (Fakes):
5
+ - fake_objects.feature
6
+ - global_fake_configuration.feature
7
+ - anonymous_doubles.feature
8
+ - replacing_classes.feature
9
+ - safe_stubbing (Safe Stubbing):
10
+ - safe_mocking.feature
11
+ - safe_stubbing.feature
12
+ - spies.feature
13
+ - argument_matchers.feature
14
+ - contract_tests (Contract Tests):
15
+ - contract_tests_mocks.feature
16
+ - contract_tests_stubs.feature
17
+ - contract_tests_spies.feature
18
+ - return_value_contracts.feature
19
+ - configuration_options.feature
20
+ - license.md
21
+ - authors.md
@@ -0,0 +1,42 @@
1
+ ## Adam Pohorecki
2
+
3
+ github: [psyho][psyho]
4
+ twitter: [@apohorecki][apohorecki]
5
+ blog: [adam.pohorecki.pl][blog]
6
+
7
+ ## Paweł Pierzchała
8
+
9
+ github: [wrozka][wrozka]
10
+ twitter: [@zwrozka][zwrozka]
11
+
12
+ ## Marek Nowak
13
+
14
+ github: [yundt][yundt]
15
+ twitter: [@yundt][yundt]
16
+
17
+ ## Contract tests
18
+
19
+ The contract tests feature was inspired by [J. B. Rainsberger's talk: "Integration Tests are a Scam"][scam].
20
+
21
+ ## Mocking DSL syntax
22
+
23
+ The mocking DSL syntax was inspired by the [RR framework][rr].
24
+
25
+ ## Sponsor
26
+
27
+ The development of Bogus was partially sponsored by [Lunar Logic][llp].
28
+
29
+ [llp]: http://www.lunarlogicpolska.com
30
+
31
+ [psyho]: https://github.com/psyho
32
+ [apohorecki]: http://twitter.com/apohorecki
33
+ [blog]: http://adam.pohorecki.pl
34
+
35
+ [wrozka]: https://github.com/wrozka
36
+ [zwrozka]: http://twitter.com/zwrozka
37
+
38
+ [yundt]: https://github.com/yundt
39
+ [yundt]: http://twitter.com/yundt
40
+
41
+ [scam]: http://www.infoq.com/presentations/integration-tests-scam
42
+ [rr]: https://github.com/btakita/rr
@@ -1,5 +1,3 @@
1
- # Changelog
2
-
3
1
  ## 0.0.1
4
2
 
5
3
  Initial version.
@@ -15,3 +13,12 @@ Initial version.
15
13
  - Implemented anonymous fakes.
16
14
  - Fixed a bug in copying ActiveRecord classes.
17
15
  - (internal) Replaced autoloads with require.
16
+
17
+ ## 0.0.3 (not released yet)
18
+
19
+ - Global fake configuration
20
+ - Inline method stubbing syntax
21
+ - Removed dependency on RR
22
+ - verifies_contracts records on described_class instead of class based on fake name
23
+ - Replacing classes with fakes
24
+