riot 0.12.1 → 0.12.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/.gitignore +10 -0
  2. data/.yardopts +6 -0
  3. data/CHANGELOG +58 -46
  4. data/Gemfile +4 -0
  5. data/README.markdown +322 -85
  6. data/Rakefile +3 -38
  7. data/lib/riot.rb +74 -11
  8. data/lib/riot/assertion.rb +32 -1
  9. data/lib/riot/assertion_macro.rb +57 -10
  10. data/lib/riot/assertion_macros/any.rb +4 -2
  11. data/lib/riot/assertion_macros/assigns.rb +18 -4
  12. data/lib/riot/assertion_macros/empty.rb +2 -0
  13. data/lib/riot/assertion_macros/equals.rb +4 -0
  14. data/lib/riot/assertion_macros/equivalent_to.rb +5 -1
  15. data/lib/riot/assertion_macros/exists.rb +4 -2
  16. data/lib/riot/assertion_macros/includes.rb +5 -1
  17. data/lib/riot/assertion_macros/kind_of.rb +5 -1
  18. data/lib/riot/assertion_macros/matches.rb +5 -1
  19. data/lib/riot/assertion_macros/nil.rb +3 -1
  20. data/lib/riot/assertion_macros/not_borat.rb +6 -0
  21. data/lib/riot/assertion_macros/raises.rb +13 -7
  22. data/lib/riot/assertion_macros/respond_to.rb +5 -1
  23. data/lib/riot/assertion_macros/same_elements.rb +6 -2
  24. data/lib/riot/assertion_macros/size.rb +5 -1
  25. data/lib/riot/context.rb +58 -10
  26. data/lib/riot/context_helpers.rb +20 -4
  27. data/lib/riot/context_options.rb +14 -4
  28. data/lib/riot/message.rb +87 -6
  29. data/lib/riot/middleware.rb +69 -4
  30. data/lib/riot/reporter.rb +71 -110
  31. data/lib/riot/reporter/dot_matrix.rb +49 -0
  32. data/lib/riot/reporter/io.rb +85 -0
  33. data/lib/riot/reporter/pretty_dot_matrix.rb +38 -0
  34. data/lib/riot/reporter/silent.rb +18 -0
  35. data/lib/riot/reporter/story.rb +52 -0
  36. data/lib/riot/rr.rb +28 -4
  37. data/lib/riot/runnable.rb +53 -0
  38. data/lib/riot/situation.rb +45 -0
  39. data/lib/riot/version.rb +4 -0
  40. data/riot.gemspec +14 -155
  41. data/test/core/assertion_macros/any_test.rb +10 -10
  42. data/test/core/assertion_macros/assigns_test.rb +7 -7
  43. data/test/core/assertion_macros/equivalent_to_test.rb +3 -3
  44. data/test/core/assertion_macros/exists_test.rb +4 -4
  45. data/test/core/assertion_macros/includes_test.rb +2 -2
  46. data/test/core/assertion_macros/kind_of_test.rb +3 -3
  47. data/test/core/assertion_macros/matches_test.rb +2 -2
  48. data/test/core/assertion_macros/nil_test.rb +2 -2
  49. data/test/core/assertion_macros/raises_test.rb +10 -10
  50. data/test/core/assertion_macros/respond_to_test.rb +2 -2
  51. data/test/core/assertion_macros/same_elements_test.rb +4 -4
  52. data/test/core/assertion_macros/size_test.rb +6 -6
  53. data/test/core/context/asserts_with_arguments_test.rb +12 -0
  54. data/test/core/context/using_describe_in_a_test.rb +1 -1
  55. data/test/core/report_test.rb +9 -5
  56. data/test/core/runnable/message_test.rb +10 -6
  57. data/test/teststrap.rb +0 -6
  58. metadata +20 -33
  59. data/TODO.markdown +0 -14
  60. data/VERSION +0 -1
  61. data/test.watchr +0 -70
  62. data/test/benchmark/colorize.rb +0 -39
@@ -0,0 +1,10 @@
1
+ *.gem
2
+ pkg
3
+ .*.swp
4
+ doc
5
+ .yardoc
6
+ *.tmproj
7
+ _site
8
+ *.watchr
9
+ .DS_Store
10
+
@@ -0,0 +1,6 @@
1
+ -r README.markdown
2
+ --protected
3
+ --no-private
4
+ -
5
+ CHANGELOG
6
+ MIT-LICENSE
data/CHANGELOG CHANGED
@@ -1,22 +1,34 @@
1
- *0.12.1*
1
+ == 0.12.2
2
2
 
3
- * Error reporting now filters the backtrace to include only meaningful line items. [mbriggs]
3
+ ==== RDoc'ed the hell out of everything [jaknowlden]
4
4
 
5
- * Added ability to pass method arguments to asserts. [sirupsen]
5
+ ==== Deprecating the not! assertion macro. It may just be gone by 0.13.0 [jaknowlden]
6
6
 
7
- *0.12.0*
7
+ ==== Remove ANSI-color dependency [achiu]
8
8
 
9
- * Negative tests are finally here! Added support for `denies` and adjusted macros to care about it with `devaluate`. [jaknowlden, achiu]
9
+ ==== Switch from Jeweler to Bundler [achiu]
10
+
11
+ ==== Add PrettyDotMatrixReporter [achiu]
12
+
13
+ == 0.12.1
14
+
15
+ ==== Error reporting now filters the backtrace to include only meaningful line items. [mbriggs]
16
+
17
+ ==== Added ability to pass method arguments to asserts. [sirupsen]
18
+
19
+ == 0.12.0
20
+
21
+ ==== Negative tests are finally here! Added support for `denies` and adjusted macros to care about it with `devaluate`. [jaknowlden, achiu]
10
22
 
11
23
  denies("my name") { "Rumplestiltzkin" }.equals("Henry")
12
24
 
13
- *0.11.4*
25
+ == 0.11.4
14
26
 
15
- * Passing Proc's instead of lambdas to `instance_eval` to comply with ruby 1.9.2. [skade]
27
+ ==== [skade] Passing Proc's instead of lambdas to `instance_eval` to comply with ruby 1.9.2.
16
28
 
17
- * Added `describe` alias for `context` for easier rspec porting. Useful at the top level and within a context. [nu7hatch]
29
+ ==== [nu7hatch] Added `describe` alias for `context` for easier rspec porting. Useful at the top level and within a context.
18
30
 
19
- Who can argue with porting from rspec to riot? Not me. [jaknowlden]
31
+ Who can argue with porting from rspec to riot? Not me.
20
32
 
21
33
  describe "My thing" do
22
34
  asserts(:size).equals(:small)
@@ -30,21 +42,21 @@ The following also works:
30
42
  end # my
31
43
  end # Another thing is
32
44
 
33
- *0.11.3*
45
+ == 0.11.3
34
46
 
35
- * Modified `matches` assertion macro to treat actual as a string before executing regular expression comparison. [jaknowlden]
47
+ ==== [jaknowlden] Modified `matches` assertion macro to treat actual as a string before executing regular expression comparison.
36
48
 
37
49
  asserts("a number") { 42 }.matches(/\d+/)
38
50
  # same as
39
51
  asserts("a number as string") { "42" }.matches(/\d+/)
40
52
 
41
- *0.11.2*
53
+ == 0.11.2
42
54
 
43
- * [ISSUE] Options were not nesting. Now fixed. [jaknowlden]
55
+ ==== [jaknowlden] [ISSUE] Options were not nesting. Now fixed.
44
56
 
45
- *0.11.1*
57
+ == 0.11.1
46
58
 
47
- * Middleware can now acts more like you would expect. Middleware now know the next neighbor in the chain and can do stuff to the context before and after the user-defined context is prepared. Removes support for the handle? method. Now we act more like a Rack app. [jaknowlden]
59
+ ==== [jaknowlden] Middleware can now acts more like you would expect. Middleware now know the next neighbor in the chain and can do stuff to the context before and after the user-defined context is prepared. Removes support for the handle? method. Now we act more like a Rack app.
48
60
 
49
61
  class MyMiddleware < Riot::ContextMiddleware
50
62
  register
@@ -58,9 +70,9 @@ The following also works:
58
70
  end
59
71
  end
60
72
 
61
- *0.11.0*
73
+ == 0.11.0
62
74
 
63
- * Added option to Context#setup which puts the specific setup block at the beginning of the setups to be called for a context. Also useful for middlewares. [jaknowlden]
75
+ ==== [jaknowlden] Added option to Context#setup which puts the specific setup block at the beginning of the setups to be called for a context. Also useful for middlewares.
64
76
 
65
77
  context "Foo" do
66
78
  setup { puts "called second" }
@@ -68,7 +80,7 @@ The following also works:
68
80
  setup(true) { puts "called first" }
69
81
  end # Foo
70
82
 
71
- * Added idea of options for a context. This is another feature picked up from riot-rails work. [jaknowlden]
83
+ ==== [jaknowlden] Added idea of options for a context. This is another feature picked up from riot-rails work.
72
84
 
73
85
  Essentially, these are useful for middlewares. For instance, if you wanted to tell a middleware that was looking for a "transactional" option before running code in a transaction block, you might do this:
74
86
 
@@ -95,9 +107,9 @@ You can call set as many times as you like
95
107
  set :foo, :bar
96
108
  end
97
109
 
98
- * ContextMiddleware: a construction pattern that allows for custom code to be applied to any context given that the middleware chooses to. This is something I started building into riot-rails and decided it was useful enough to just put it into riot itself. [jaknowlden]
110
+ ==== [jaknowlden] ContextMiddleware: a construction pattern that allows for custom code to be applied to any context given that the middleware chooses to.
99
111
 
100
- If, for instance, you wanted to add a setup with some stuff only if the context description was equal to "Your Mom":
112
+ This is something I started building into riot-rails and decided it was useful enough to just put it into riot itself. If, for instance, you wanted to add a setup with some stuff only if the context description was equal to "Your Mom":
101
113
 
102
114
  class YourMomMiddleware < Riot::ContextMiddleware
103
115
  register
@@ -113,9 +125,9 @@ If, for instance, you wanted to add a setup with some stuff only if the context
113
125
  end
114
126
  end # YourMomMiddleware
115
127
 
116
- *0.10.13*
128
+ == 0.10.13
117
129
 
118
- * Helpers are now run with other setups, not separately. Which means you could use a helper in a setup. [jaknowlden]
130
+ ==== [jaknowlden] Helpers are now run with other setups, not separately. Which means you could use a helper in a setup.
119
131
 
120
132
  context "Foo" do
121
133
  helper(:user) { User.new }
@@ -124,7 +136,7 @@ If, for instance, you wanted to add a setup with some stuff only if the context
124
136
  end
125
137
  end # Foo
126
138
 
127
- * Correctly report non-RR assertion failures and errors when RR is used [vandrijevik]
139
+ ==== [vandrijevik] Correctly report non-RR assertion failures and errors when RR is used.
128
140
 
129
141
  context "Foo.bar" do
130
142
  asserts("baz is called") do
@@ -136,7 +148,7 @@ If, for instance, you wanted to add a setup with some stuff only if the context
136
148
  would previously return [:fail, "baz() Called 0 times. Expected 1 times."], and will now
137
149
  correctly return [:error, #<RuntimeError: oh noes>]
138
150
 
139
- * Recording description as is. Providing #detailed_description for proper behavior [jaknowlden]
151
+ ==== [jaknowlden] Recording description as is. Providing #detailed_description for proper behavior
140
152
 
141
153
  foo_context = context(Foo) {}
142
154
  bar_context = foo_context.context(Bar) {}
@@ -145,7 +157,7 @@ If, for instance, you wanted to add a setup with some stuff only if the context
145
157
  bar_context.detailed_description
146
158
  => "Foo Bar"
147
159
 
148
- * No longer assuming topic when no block provided to an assertion. Instead, assuming block fails by default. Use `asserts_topic` only now. [jaknowlden]
160
+ ==== [jaknowlden] No longer assuming topic when no block provided to an assertion. Instead, assuming block fails by default. Use `asserts_topic` only now.
149
161
 
150
162
  context "foo" do
151
163
  setup { "bar" }
@@ -154,11 +166,11 @@ If, for instance, you wanted to add a setup with some stuff only if the context
154
166
  asserts("topic").equals(false) # Will actually pass :)
155
167
  end
156
168
 
157
- *0.10.12*
169
+ == 0.10.12
158
170
 
159
- * Recognizing file and line number of an assertion declaration on failure [vandrijevik]
171
+ ==== [vandrijevik] Recognizing file and line number of an assertion declaration on failure
160
172
 
161
- * RR support in Riot [vandrijevik,jaknowlden]
173
+ ==== [vandrijevik,jaknowlden] RR support in Riot
162
174
 
163
175
  # teststrap.rb
164
176
  require 'riot'
@@ -169,7 +181,7 @@ If, for instance, you wanted to add a setup with some stuff only if the context
169
181
  asserts("failure due to not calling hello") { mock!.hello {"world"} } # actually fails
170
182
  end
171
183
 
172
- * Added Riot::Message to make messages in macros easier to write [jaknowlden]
184
+ ==== [jaknowlden] Added Riot::Message to make messages in macros easier to write
173
185
 
174
186
  def evaluate(actual, expected)
175
187
  # ...
@@ -177,28 +189,28 @@ If, for instance, you wanted to add a setup with some stuff only if the context
177
189
  # ...
178
190
  end
179
191
 
180
- * Added responds_to as a respond_to alias [jaknowlden]
192
+ ==== [jaknowlden] Added responds_to as a respond_to alias
181
193
 
182
- * Added the equivalent_to macro to compare case equality (===). equals is now (==) [jaknowlden]
194
+ ==== [jaknowlden] Added the equivalent_to macro to compare case equality (===). equals is now (==)
183
195
 
184
- * Assuming RootContext if nil parent provided. Added Context#parent to the API [jaknowlden]
196
+ ==== [jaknowlden] Assuming RootContext if nil parent provided. Added Context#parent to the API
185
197
 
186
198
  Riot::Context.new("Hi", nil) {}.parent.class
187
199
  => Riot::RootContext
188
200
 
189
- *0.10.11*
201
+ == 0.10.11
190
202
 
191
- * Context#asserts_topic now takes an optional description [gabrielg, jaknowlden]
203
+ ==== [gabrielg, jaknowlden] Context#asserts_topic now takes an optional description
192
204
 
193
205
  asserts_topic.exists
194
206
  asserts_topic("some kind of description").exists
195
207
 
196
- * Added not! assertion macro [gabrielg, jaknowlden]
208
+ ==== [gabrielg, jaknowlden] Added not! assertion macro
197
209
 
198
210
  setup { User.new(:funny? => false) }
199
211
  asserts(:funny?).not!
200
212
 
201
- * Added Context#hookup to add some setup code to an already defined topic [jaknowlden]
213
+ ==== [jaknowlden] Added Context#hookup to add some setup code to an already defined topic
202
214
 
203
215
  context "yo mama" do
204
216
  setup { YoMama.new }
@@ -209,31 +221,31 @@ If, for instance, you wanted to add a setup with some stuff only if the context
209
221
  end
210
222
  end
211
223
 
212
- * Added Riot.alone! mode to ensure Riot.run is not run at-exit [jaknowlden]
224
+ ==== [jaknowlden] Added Riot.alone! mode to ensure Riot.run is not run at-exit
213
225
 
214
226
  Riot.alone!
215
227
  Riot.run
216
228
 
217
229
  This will still print output unless you also Riot.silently!
218
230
 
219
- * Returning non-zero status at-exit when tests don't pass [gabrielg, jaknowlden]
231
+ ==== [gabrielg, jaknowlden] Returning non-zero status at-exit when tests don't pass
220
232
 
221
- *0.10.10*
233
+ == 0.10.10
222
234
 
223
- * Passing assertion macros can now return a custom message [dasch, jaknowlden]
235
+ ==== [dasch, jaknowlden] Passing assertion macros can now return a custom message
224
236
 
225
237
  def evaluate(actual, *expectings)
226
238
  1 == 1 ? pass("1 does equal 1") : fail("1 does not equal 1 in this universe")
227
239
  end
228
240
 
229
- * Removing Context#extend_assertions and related code [jaknowlden]
241
+ ==== [jaknowlden] Removing Context#extend_assertions and related code
230
242
 
231
- * Allow the use of symbolic descriptions as shorthands for sending the message to the topic [dasch]
243
+ ==== [dasch] Allow the use of symbolic descriptions as shorthands for sending the message to the topic
232
244
 
233
245
  setup { "foo" }
234
246
  asserts(:upcase).equals("FOO")
235
247
 
236
- * Added AssertionMacro and #register for macros [jaknowlden, splattael]
248
+ ==== [jaknowlden, splattael] Added AssertionMacro and #register for macros
237
249
 
238
250
  module My
239
251
  class CustomThingAssertion < Riot::AssertionMacro
@@ -248,7 +260,7 @@ If, for instance, you wanted to add a setup with some stuff only if the context
248
260
  Riot::Assertion.register_macro :custom_thing, CustomThingAssertion
249
261
  end
250
262
 
251
- * Replace IOReporter#say with #puts. Also add #print. [splattael]
263
+ ==== [splattael] Replace IOReporter#say with #puts. Also add #print.
252
264
 
253
265
  class SomeNewReporter < IOReporter
254
266
  def pass
@@ -261,6 +273,6 @@ If, for instance, you wanted to add a setup with some stuff only if the context
261
273
  # ...
262
274
  end
263
275
 
264
- *0.10.9 and before*
276
+ == 0.10.9 and before
265
277
 
266
- See [commit log](http://github.com/thumblemonks/riot/commits/master)
278
+ See the commit log: http://github.com/thumblemonks/riot/commits/master
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
@@ -1,146 +1,383 @@
1
1
  # Riot
2
2
 
3
- A fast, expressive and concise unit-testing framework.
4
-
3
+ A fast, expressive, and contextual ruby unit testing framework. Protest the slow test.
5
4
 
6
5
  ## Installation
7
6
 
8
- Add Gemcutter to your gem sources:
9
-
10
- !!!plain
11
- sudo gem sources -a http://gemcutter.org
12
-
13
- Then, simply install the Riot gem like so:
14
-
15
- !!!plain
16
- sudo gem install riot
17
-
7
+ gem install riot
18
8
 
19
9
  ## Usage
20
10
 
21
- In contrast to other popular Ruby testing frameworks such as Test::Unit,
22
- [Shoulda](http://github.com/thoughtbot/shoulda) and [RSpec](http://rspec.info/),
23
- Riot does not run a `setup` and `teardown` sequence before and after each test. This speeds
24
- up the test runs quite a bit, but also puts restrictions on how you write your tests. In
25
- general, you should avoid mutating any objects under test.
11
+ In contrast to other popular Ruby testing frameworks such as Test::Unit, [Shoulda](http://github.com/thoughtbot/shoulda) and [RSpec](http://rspec.info/), Riot does not run a `setup` and `teardown` sequence before and after each test. This speeds up test execution quite a bit, but also changes how you write your tests. In general and in my opinion, you should avoid mutating any objects under test and if you use Riot you're pretty much going to have to.
26
12
 
27
- In Riot, tests reside in *contexts*. Within these, a *topic* object is defined through a
28
- `setup` block. The actual assertions are then made with `assert`.
13
+ In Riot, tests reside in `contexts`. Within these, a `topic` object is defined through a `setup` block. The actual assertions are then made with an `asserts` or `denies` block.
29
14
 
30
- context "An Array" do
15
+ context "An empty Array" do
31
16
  setup { Array.new }
32
- asserts("is empty") { topic.empty? }
33
- end
17
+ asserts("it is empty") { topic.empty? }
18
+ denies("it has any elements") { topic.any? }
19
+ end # An Array
34
20
 
35
- As you can see, the setup block doesn't use any instance variables to save the object under
36
- test -- rather, the return value of the block is used as the *topic*. This object can then
37
- be accessed in the assertions using the `topic` attribute.
21
+ As you can see, the setup block doesn't use any instance variables to save the object under test &mdash; rather, the return value of the block is used as the `topic`. This object can then be accessed in the assertions using the `topic` attribute. Furthermore, at their very basic level, assertions need only return a boolean. When using `asserts`, `true` indicates a pass while `false` indicates a fail; subsequently, when using `denies`, `true` indicates a failure whereas `false` indicates success.
38
22
 
39
- Furthermore, assertions need only return a boolean; `true` indicates a pass, while `false`
40
- indicates a fail.
41
-
42
- Contexts can also be nested; the `setup` blocks are executed outside-in before.
23
+ Of course, you can nest contexts as well; the `setup` blocks are executed outside-in; as in, the parents' setups are run before the current context allowing for a setup hierarchy. `teardown` blocks are run inside out; the current context's teardowns are run before any of its parents'. This is what you would expect from other frameworks as well.
43
24
 
44
25
  context "An Array" do
45
26
  setup { Array.new }
46
27
 
28
+ asserts("is empty") { topic.empty? }
29
+
47
30
  context "with one element" do
48
31
  setup { topic << "foo" }
49
- asserts("is not empty") { !topic.empty? }
32
+ asserts("array is not empty") { !topic.empty? }
50
33
  asserts("returns the element on #first") { topic.first == "foo" }
51
34
  end
52
- end
35
+ end # An Array
53
36
 
37
+ By the way, you can put any kind of ruby object in your context description. Riot will call `to_s` on the actual value before it is used in a reporting context. This fact will become [useful later](http://thumblemonks.github.com/riot/hacking.html#context-middleware) ;)
54
38
 
55
- ### Using assertion macros
39
+ ### Assertions {#assertions}
56
40
 
57
- Macros greatly simplify a lot of assertions by capturing common practises, e.g. comparing
58
- some value using the `==` operator. They work by calling the macro method on the return
59
- value of `asserts`:
41
+ Well, how useful would Riot be if you could only return true/false from an assertion? Pretty useful, actually; but, we can make it more useful! No; that's not crazy. No it isn't. Yes; I'm sure.
60
42
 
61
- asserts("#first") { topic.first }.equals("foo")
43
+ We can do this with assertion macros. You can think of these as special assertion modifiers that check the return value of the assertion block. Actually, it's not that you **can** think of them this way; you **should** think of them this way.
62
44
 
63
- Other macros are available; for a complete overview, see {Riot::AssertionMacro}.
45
+ Let's take this little for instance:
64
46
 
65
- A common pattern is to make assertions on the return value of some attribute or method
66
- on the `topic` -- to simplify this, we have provided an easy to use shorthand. Simply
67
- give a Symbol as the first argument to `asserts` and leave out the block.
47
+ context "Yummy things" do
48
+ setup { ["cookies", "donuts"] }
68
49
 
69
- asserts(:first).equals("foo")
50
+ asserts("#first") { topic.first }.equals("cookies")
51
+ end # Yummy things
70
52
 
53
+ First, how's that for a readable test? Second, you should notice that the assertion block will return the `first` item from the `topic` (which is assumed to be `Enumerable` in this case); if it isn't `Enumerable`, then you have other problems. Since the first element in the array is "cookies", the assertion will pass. Yay!
71
54
 
72
- If you're like me, you like to write a bunch of failing tests for a context and then make
73
- them pass. This can easily be done by simply calling an `asserts` or `should` command with
74
- no block returning the actual value.
55
+ But wait, there's more. Riot is about helping you write faster and more readable tests. Notice any duplication in the example above (besides the value "cookies")? I do. How about that `first` notation in the assertion name and reference in the assertion block. Riot provides a shortcut which allows you to reference methods on the topic through the assertion name. Here's another way to write the same test:
75
56
 
76
- asserts("something special")
77
- should("do something special")
57
+ context "Yummy things" do
58
+ setup { ["cookies", "donuts"] }
78
59
 
79
- Both of the above tests will fail. You can use this similar to a `should_eventually`. The
80
- difference is that `should_eventually` is not technically a failure and will therefore be
81
- ignored in time by (based on my experience). The above two blocks will actually show up as
82
- failures and should hopefully drive you to making them pass.
60
+ asserts(:first).equals("cookies")
61
+ end # Yummy things
83
62
 
84
- ### Reading Riot's output
63
+ Now that's real yummy. Want some more? Perhaps you just want to test the topic itself &mdash; not a method or attribute of it. You could do this:
85
64
 
86
- Riot can output the test results in several ways, the default being *story reporting*. With
87
- this reporter, the output looks like the following:
65
+ context "Yummy things" do
66
+ setup { ["cookies", "donuts"] }
88
67
 
89
- !!!plain
90
- An Array
91
- + asserts is empty
92
- An Array with one element
93
- + asserts is not empty
94
- + asserts returns the element on #first
95
-
96
- 3 passes, 0 failures, 0 errors in 0.000181 seconds
68
+ asserts("topic") { topic }.size(2)
69
+ end # Yummy things
97
70
 
98
- The various shorthands and macros are built in a way that ensures expressive and specific
99
- feedback, e.g. the assertion
71
+ But, as you can probably already guess, that's gross and redundant. To solve this, Riot provides the `asserts_topic` shortcut which is a helper that pretty much just does `asserts("topic") { topic }` for you.
100
72
 
101
- asserts(:full_name).matches(/^\w+ \w+$/)
73
+ context "Yummy things" do
74
+ setup { ["cookies", "donuts"] }
102
75
 
103
- yields the output
76
+ asserts_topic.size(2)
77
+ end # Yummy things
104
78
 
105
- !!!plain
106
- + asserts #full_name matches /^\w+ \w+$/
79
+ Yep, more readable.
80
+
81
+ #### Negative Assertions {#negative-assertions}
82
+
83
+ Way back in the first code example we saw a reference to `denies`; this is what is called the negative assertion. You could probably also call it a counter assertion, but I don't. You can use `denies` with any assertion macro that you can use `asserts` with; it's just that `denies` expects the assertion to fail for the test to pass. For instance:
84
+
85
+ context "My wallet" do
86
+ setup do
87
+ Wallet.new(1000) # That's 1000 cents, or $10USD yo
88
+ end
89
+
90
+ asserts(:enough_for_lunch?)
91
+ denies(:enough_for_lunch?)
92
+ end # My wallet
93
+
94
+ One of those will pass and the other will fail. If $10 is not enough for lunch the `denies` statement will pass; and then you should move to Chicago where it is enough (if only barely).
95
+
96
+ #### Built-in Assertion Macros {#builtin-macros}
97
+
98
+ There are a bunch of built-in assertion macros for your everyday use. Be sure to [write your own](http://thumblemonks.github.com/riot/hacking.html#writing-assertion-macros) if these don't satisfy your every need. You will notice the two varying mechanisms for passing arguments into the macros: one is the conventional form of message passing (via actual arguments) and the other is derived from a provided block. If the macro expects one argument, you can use either form (but not both). If the macro accepts multiple arguments, the last argument you want to pass in can be provided via the block.
99
+
100
+ The advantage of using the block is that its innards are evaluated against the same scope that the assertion was evaluated against. This means you can use the same helpers and instance variables in the macro block to generate an expected value (if you so desire). It's also useful if you have a fairly complex routine for generating the expected value.
101
+
102
+ {#builtin-macro-list}
103
+ * **Equals**: compares equality of the actual value to the expected value using the `==` operator
104
+ * `asserts.equals(Object)`
105
+ * `denies.equals(Object)`
106
+ * `asserts.equals { Object }`
107
+ * `denies.equals { Object }`
108
+
109
+ * **Equivalent To**: compares equivalence of actual value to the expected value using the `===` operator
110
+ * `asserts.equivalent_to(Object)`
111
+ * `denies.equivalent_to(Object)`
112
+ * `asserts.equivalent_to { Object }`
113
+ * `denies.equivalent_to { Object }`
114
+
115
+ * **Assigns**: checks that the actual value has an instance variable defined within it's scope. You can also validate the value of that variable. Very much mimicing the `assigns` found in Rails-ish tests from way back in form, function, and need.
116
+ * `asserts("a person") { Person.new }.assigns(:email)`
117
+ * `denies("a person") { Person.new }.assigns(:email)`
118
+ * `asserts("a person") { Person.new(:email => "a@b.com") }.assigns(:email, "a@b.com")`
119
+ * `denies("a person") { Person.new(:email => "a@b.com") }.assigns(:email, "a@b.com")`
120
+ * `asserts.assigns { :email }`
121
+ * `denies.assigns { :email }`
122
+ * `asserts.assigns(:email) { "a@b.com" }`
123
+ * `denies.assigns(:email) { "a@b.com" }`
124
+
125
+ * **Nil**: simply checks the actual value for its nil-ness. Expects no arguments.
126
+ * `asserts.nil`
127
+ * `denies.nil`
128
+
129
+ * **Exists**: pretty much the opposite of the `nil` assertion macro. Expects no arguments.
130
+ * `asserts.exists`
131
+ * `denies.exists`
132
+
133
+ * **Matches**: compares the actual value to a provided regular expression
134
+ * `asserts.matches(%r{Regex})`
135
+ * `denies.matches(%r{Regex})`
136
+ * `asserts.matches { /Regex/ }`
137
+ * `denies.matches { /Regex/ }`
138
+
139
+ * **Raises**: validates the type of exception raised from the assertion block. Optionally, you can give it the message you expected in the form of a literal string or even a portion of it.
140
+ * `asserts.raises(ExceptionClass)`
141
+ * `denies.raises(ExceptionClass)`
142
+ * `asserts.raises(ExceptionClass, "Expected message")`
143
+ * `denies.raises(ExceptionClass, "Expected message")`
144
+ * `asserts.raises(ExceptionClass) { "ted mess" }`
145
+ * `denies.raises(ExceptionClass) { "ted mess" }`
146
+
147
+ * **Kind Of**: validates the type of object returned from the assertion block
148
+ * `asserts.kind_of(Class)`
149
+ * `denies.kind_of(Class)`
150
+ * `asserts.kind_of { Class }`
151
+ * `denies.kind_of { Class }`
152
+
153
+ * **Responds To**: checks that the actual object `respond_to?` to a particular message
154
+ * `asserts.respond_to(:foo)`
155
+ * `denies.respond_to(:foo)`
156
+ * `asserts.respond_to { "foo" }`
157
+ * `denies.respond_to { "foo" }`
158
+ * `asserts.responds_to("foo")`
159
+ * `denies.responds_to("foo")`
160
+ * `asserts.responds_to { :foo }`
161
+ * `denies.responds_to { :foo }`
162
+
163
+ * **Includes**: checks for the existence of: a character or sequence of characters in a string, an element in an array, or a key in a hash.
164
+ * `asserts("this string") { "barbie q" }.includes("foo")`
165
+ * `denies("this string") { "barbie q" }.includes("foo")`
166
+ * `asserts("this array") { [1,2,3] }.includes(2)`
167
+ * `denies("this array") { [1,2,3] }.includes(2)`
168
+ * `asserts("this hash") { {:key1 => "foo"} }.includes(:key2)`
169
+ * `denies("this hash") { {:key1 => "foo"} }.includes(:key2)`
170
+ * `asserts.includes { "foo" }`
171
+ * `denies.includes { "foo" }`
172
+ * `asserts.includes { 2 }`
173
+ * `denies.includes { 2 }`
174
+ * `asserts.includes { :key }`
175
+ * `denies.includes { :key }`
176
+
177
+ * **Size**: compares the size of the actual object to the number you provide. Works with anything that responds to `size(Numeric)` (strings, arrays, hashes, etc).
178
+ * `asserts.size(Numeric)`
179
+ * `denies.size(Numeric)`
180
+ * `asserts.size { Numeric }`
181
+ * `denies.size { Numeric }`
182
+
183
+ * **Any**: checks the result of calling `any?` on the actual value. Expects no arguments.
184
+ * `asserts.any`
185
+ * `denies.any`
186
+
187
+ * **Empty**: checks the result of calling `empty?` on the actual value. Expects no arguments.
188
+ * `asserts.empty`
189
+ * `denies.empty`
190
+
191
+ * **Same Elements**: compares actual to expected to see if they contain the same elements. Uses `Set` under-the-hood, just so you know.
192
+ * `asserts.same_elements(Array)`
193
+ * `denies.same_elements(Array)`
194
+ * `asserts.same_elements { Array }`
195
+ * `denies.same_elements { Array }`
196
+
197
+ * **Not!**: Expects no arguments and simply checks that the actual value is non-truthy. This is different than `exists` which only checks that the actual value is not nil. This assertion was added at the inception of Riot to deal with the fact that Riot didn't yet have negative assertions. I am hereby declaring this macro `@deprecated`. Please stop using it (note to self) because it will be removed someday.
198
+ * `asserts("i'm confused") { false }.not!`
199
+ * `denies("i'm not confused?") { true }.not!`
200
+
201
+ ### Setups, Hookups, and Helpers {#setups-hookups}
202
+
203
+ We're not even close to done yet; there's a lot more cool stuff for you to know about. You know about `setup` already; but you may not know that you can call `setup` multiple times within a Context. Well, you can. They run in the order you write them (top-down) and the result of a prior `setup` will be the `topic` for the next setup. In this way you **could** chain together some partitioned setup criteria without ever explicitly setting a variable (instance or local).
204
+
205
+ context "A cheesey order" do
206
+ setup { Cheese.create!(:name => "Blue") }
207
+ setup { Order.create!(:cheese => topic, :purchase_order => "123-abc") }
208
+
209
+ asserts_topic.kind_of(Order) # I love tests that are readable
210
+ end # A cheesey order
211
+
212
+ This notion about a prior `setup` being the `topic` for a latter `setup` is true even when the `setup` is called from a parent Context.
213
+
214
+ More than likely, however, you'll want to modify something about the topic without changing what the topic for the context is. To do this, Riot provides the `hookup` block, which is just like a `setup` block except that `hookup` will always return the `topic` that was provided to it. It's kind of like calling `Object#tap`. Here's a for-instance:
215
+
216
+ context "A Person" do
217
+ setup { Person.new(:name => "Master Blasterr") }
218
+
219
+ denies(:valid?) # :(
220
+
221
+ context "with valid email" do
222
+ hookup { topic.email = "master@blast.err" }
223
+ asserts(:valid?) # Yay!
224
+ end # with valid email
225
+ end # A complex thing
226
+
227
+ If the point didn't smack you in the face there, think about using `setup` instead of `hookup` in the sub-context. Had you written that as a `setup` block, you'd have to return `topic` after setting the email address, or else the new topic would be the actual email address; and you probably don't want to actually be calling `"master@blast.err".valid?` in the assertion.
228
+
229
+ You can also call `hookup` as many times as you like; the great part is that the `topic` never changes.
230
+
231
+ #### Helpers {#helpers}
232
+
233
+ You remember how you used to &mdash; or currently do &mdash; create instance variables to hold some data that you're going to use in your tests? Well, Riot allows you to still do that yucky stuff, but would rather you use a helper to encapsulate it. For instance, you could do this:
234
+
235
+ context "A greedy monkey" do
236
+ setup do
237
+ @a_ripe_banana = Banana.new(:ripe => true)
238
+ Monkey.new
239
+ end
240
+
241
+ hookup { topic.takes(@a_ripe_banana) }
242
+
243
+ asserts(:bananas).size(1)
244
+ end # A greedy monkey
245
+
246
+ Or, you could do this
247
+
248
+ context "A greedy monkey" do
249
+ helper(:a_ripe_banana) { Banana.new(:ripe => true) }
250
+ setup { Monkey.new }
251
+
252
+ hookup { topic.takes(a_ripe_banana) }
253
+
254
+ asserts(:bananas).size(1)
255
+ end # A greedy monkey
256
+
257
+ "So! What's the difference?", you ask. Nothing really. It's all aesthetic; but, it's a better aesthetic for a couple of reasons. Let me tell you why:
258
+
259
+ 1. Helpers are good ways to encapsulate related setup data and give that data namespace
260
+ 2. The act of setting up data does not clutter up your setups or assertions
261
+ 3. I'll argue that it makes the code more readable; ex. how do you verbalize to your friends `@a_banana` and `a_banana`. In the former, I probably say "at a banana" and think "Why do I sound like a muppet when I talk?".
262
+ 3. Being that helpers are blocks, you can actually pass arguments to them
263
+
264
+ What's that about (4)? Yes, helpers are really just over-glorified methods, which means you can pass arguments to them. Which means you can build factories with them. Which means those factories can go away when the context is no longer used and they're no longer cluttering up your object space. You want another for instance, eh?
107
265
 
266
+ context "A greedy monkey" do
267
+ helper(:make_a_banana) do |color|
268
+ Banana.new(:color => color)
269
+ end
270
+
271
+ setup { Monkey.new }
272
+
273
+ hookup do
274
+ topic.takes(make_a_banana("green"))
275
+ topic.takes(make_a_banana("blue"))
276
+ end
277
+
278
+ asserts(:bananas).size(2)
279
+ asserts("green bananas") { topic.bananas.green }.size(1)
280
+ asserts("blue bananas") { topic.bananas.blue }.size(1)
281
+ end # A greedy monkey
282
+
283
+ Or you could `make_many_bananas` or whatever. There are also lots of clever ways to get helpers included into a context which you will hopefully see when you read up on Context Middleware and look through the Recipes. Riot Rails makes liberal use of helpers when [setting up a context](http://github.com/thumblemonks/riot-rails/master/lib/riot/action_controller/context_middleware.rb) to test controllers.
284
+
285
+ Again, you define as many helpers as you like; you can also replace existing helpers by simply defining a helper with the same name (*that's because they're just methods defined within the context instance ... shhh*).
286
+
287
+ ### Running Riot {#running}
108
288
 
109
- ### Testing Rack application
289
+ Running your Riot tests is pretty simple. You can put your test files wherever you want, but it's generally a good idea to put them in a "test" directory. You can run individual test files using the normal ruby command:
110
290
 
111
- Rack applications can easily be tested using [Riot::Rack](http://github.com/dasch/riot-rack),
112
- which integrates [Rack::Test](http://github.com/brynary/rack-test) into Riot.
291
+ !!!plain
292
+ ruby test/units/monkey_test.rb
293
+ # or
294
+ ruby -Itest test/units/monkey_test.rb
295
+
296
+ I like the latter and use it often. It means the test directory is loaded into the load path, which means I don't have to be explicit about where to find my `teststrap.rb` file (which you might have named `test_helper.rb` in other projects even though it's a silly name). In your teststrap file you'll put all your common setup; maybe even including your Riot hacks. An out-of-the-box teststrap might look like this:
113
297
 
298
+ require 'rubygems'
299
+ require '<my-library>'
114
300
  require 'riot'
115
- require 'riot/rack'
116
301
 
117
- context "HelloWorldApp" do
118
- # Specify your app using the #app helper. If you don't specify
119
- # one, Riot::Rack will recursively look for a config.ru file.
120
- app { HelloWorldApp }
302
+ Of course, you probably want to use rake to run your tests. Here's a basic Rakefile that will find our tests in the test directory or its subdirectories if the filename ends in `_test.rb`:
121
303
 
122
- # You can use all the Rack::Test helpers in the setup blocks.
123
- setup { get '/' }
304
+ require 'rubygems'
124
305
 
125
- # You can access the response directly.
126
- asserts(:status).equals(200)
127
- asserts(:body).equals("Hello, World!")
306
+ require 'rake'
307
+ require 'rake/testtask'
308
+
309
+ desc "Run all our tests"
310
+ task :test do
311
+ Rake::TestTask.new do |t|
312
+ t.libs << "test"
313
+ t.pattern = "test/**/*_test.rb"
314
+ t.verbose = false
315
+ end
128
316
  end
129
317
 
318
+ task :default => :test
319
+
320
+ And then on the command line you simply run:
321
+
322
+ !!!plain
323
+ rake
324
+ # or
325
+ rake test
326
+
327
+ ### Mocking {#mocking}
328
+
329
+ Mocking seems to be all the rage this decade. I try very hard to avoid it altogether through judicious use of anonymous classes, but sometimes you just need to mock. For Riot, [RR](http://github.com/btakita/rr) seemed to fit the bill nicely because it's:
330
+
331
+ * Lightweight
332
+ * Laid back
333
+ * Test framework agnostic and easy to integrate with
334
+ * Starts and ends with TWO R's!
335
+
336
+ That second to last point needs to be stressed a bit more. RR's default behavior is to not give a crap about what test framework you use. I can already hear you thinking, *"But wait, aren't you tying Riot to a mock framework?"*
337
+
338
+ Well, not really. Riot is mock framework agnostic; it'll work with any framework that can be worked with. Riot does not implicitly require in any RR support; you have to do that in your test-strapping.
339
+
340
+ However, there are a number of things you expect from a test framework when mocking is involved. Namely, if a mock expectation fails you want the context or assertion to fail, too. Additionally, you don't want to have to ask the mocking framework to validate itself for each assertion; you want Riot to do that for you. And some other things. So, Riot does most of the tedious mock-lifting for you and it suggests you use RR, but doesn't require it.
341
+
342
+ But enough of this hemming and hawing. What's it look like?! In your `teststrap.rb` you need to require in `riot/rr`:
343
+
344
+ # I'm teststrap.rb
345
+
346
+ require 'rubygems'
347
+ require 'riot/rr'
348
+
349
+ Then, in your tests, you use standard RR syntax for all of your mocking needs:
350
+
351
+ require 'teststrap.rb'
352
+
353
+ context "A nice Person" do
354
+ setup do
355
+ Nice::Person.new
356
+ end
357
+
358
+ should("find a nice thing to say") do
359
+ mock(topic).make_network_request { "Nice haircut" }
360
+ topic.say_something_nice
361
+ end.equals("Nice haircut")
362
+
363
+ end # A nice Person
364
+
365
+ So, if `#say_something_nice` never calls `#make_network_request`, that assertion will fail for that reason first. If it does call `#make_network_request`, but for some reason "Nice haircut" is not returned, the tests will fail for that reason instead. It's like catching two birds with one test.
366
+
367
+ This is not an RR guide so you need to get familiar with it's syntax. Needless to say, if you require it in it's methods are available within any assertion, setup, teardown, hookup, and helper.
130
368
 
131
369
  ## Contributing
132
370
 
133
- Riot is still in its infancy, so both the internal and external API may change radically.
134
- That being said, we would love to hear any thoughts and ideas, and bug reports are always
135
- welcome. We hang out in `#riot` on `irc.freenode.net`.
371
+ Riot is slowly solidifying its internal and external API. That being said, we would love to hear any thoughts and ideas, and bug reports are always welcome. We hang out in `#riot` on `irc.freenode.net`.
136
372
 
137
- The code is hosted on [GitHub](http://github.com), and can be fetched with
373
+ Source code is hosted on [GitHub](http://github.com), and can be fetched with
138
374
  [Git](http://git-scm.com) by running:
139
375
 
140
376
  !!!plain
141
377
  git clone git://github.com/thumblemonks/riot.git
142
378
 
379
+ If you want to make changes, please feel free to do so. The best process is to fork, fix, and send a pull request.
143
380
 
144
381
  ## License
145
382
 
146
- Riot is released under the MIT license. See {file:MIT-LICENSE}.
383
+ Riot is released under the MIT license. See [MIT LICENSE](https://github.com/thumblemonks/riot/blob/master/MIT-LICENSE).