riot 0.12.1 → 0.12.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/.yardopts +6 -0
- data/CHANGELOG +58 -46
- data/Gemfile +4 -0
- data/README.markdown +322 -85
- data/Rakefile +3 -38
- data/lib/riot.rb +74 -11
- data/lib/riot/assertion.rb +32 -1
- data/lib/riot/assertion_macro.rb +57 -10
- data/lib/riot/assertion_macros/any.rb +4 -2
- data/lib/riot/assertion_macros/assigns.rb +18 -4
- data/lib/riot/assertion_macros/empty.rb +2 -0
- data/lib/riot/assertion_macros/equals.rb +4 -0
- data/lib/riot/assertion_macros/equivalent_to.rb +5 -1
- data/lib/riot/assertion_macros/exists.rb +4 -2
- data/lib/riot/assertion_macros/includes.rb +5 -1
- data/lib/riot/assertion_macros/kind_of.rb +5 -1
- data/lib/riot/assertion_macros/matches.rb +5 -1
- data/lib/riot/assertion_macros/nil.rb +3 -1
- data/lib/riot/assertion_macros/not_borat.rb +6 -0
- data/lib/riot/assertion_macros/raises.rb +13 -7
- data/lib/riot/assertion_macros/respond_to.rb +5 -1
- data/lib/riot/assertion_macros/same_elements.rb +6 -2
- data/lib/riot/assertion_macros/size.rb +5 -1
- data/lib/riot/context.rb +58 -10
- data/lib/riot/context_helpers.rb +20 -4
- data/lib/riot/context_options.rb +14 -4
- data/lib/riot/message.rb +87 -6
- data/lib/riot/middleware.rb +69 -4
- data/lib/riot/reporter.rb +71 -110
- data/lib/riot/reporter/dot_matrix.rb +49 -0
- data/lib/riot/reporter/io.rb +85 -0
- data/lib/riot/reporter/pretty_dot_matrix.rb +38 -0
- data/lib/riot/reporter/silent.rb +18 -0
- data/lib/riot/reporter/story.rb +52 -0
- data/lib/riot/rr.rb +28 -4
- data/lib/riot/runnable.rb +53 -0
- data/lib/riot/situation.rb +45 -0
- data/lib/riot/version.rb +4 -0
- data/riot.gemspec +14 -155
- data/test/core/assertion_macros/any_test.rb +10 -10
- data/test/core/assertion_macros/assigns_test.rb +7 -7
- data/test/core/assertion_macros/equivalent_to_test.rb +3 -3
- data/test/core/assertion_macros/exists_test.rb +4 -4
- data/test/core/assertion_macros/includes_test.rb +2 -2
- data/test/core/assertion_macros/kind_of_test.rb +3 -3
- data/test/core/assertion_macros/matches_test.rb +2 -2
- data/test/core/assertion_macros/nil_test.rb +2 -2
- data/test/core/assertion_macros/raises_test.rb +10 -10
- data/test/core/assertion_macros/respond_to_test.rb +2 -2
- data/test/core/assertion_macros/same_elements_test.rb +4 -4
- data/test/core/assertion_macros/size_test.rb +6 -6
- data/test/core/context/asserts_with_arguments_test.rb +12 -0
- data/test/core/context/using_describe_in_a_test.rb +1 -1
- data/test/core/report_test.rb +9 -5
- data/test/core/runnable/message_test.rb +10 -6
- data/test/teststrap.rb +0 -6
- metadata +20 -33
- data/TODO.markdown +0 -14
- data/VERSION +0 -1
- data/test.watchr +0 -70
- data/test/benchmark/colorize.rb +0 -39
data/.gitignore
ADDED
data/.yardopts
ADDED
data/CHANGELOG
CHANGED
@@ -1,22 +1,34 @@
|
|
1
|
-
|
1
|
+
== 0.12.2
|
2
2
|
|
3
|
-
|
3
|
+
==== RDoc'ed the hell out of everything [jaknowlden]
|
4
4
|
|
5
|
-
|
5
|
+
==== Deprecating the not! assertion macro. It may just be gone by 0.13.0 [jaknowlden]
|
6
6
|
|
7
|
-
|
7
|
+
==== Remove ANSI-color dependency [achiu]
|
8
8
|
|
9
|
-
|
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
|
-
|
25
|
+
== 0.11.4
|
14
26
|
|
15
|
-
|
27
|
+
==== [skade] Passing Proc's instead of lambdas to `instance_eval` to comply with ruby 1.9.2.
|
16
28
|
|
17
|
-
|
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.
|
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
|
-
|
45
|
+
== 0.11.3
|
34
46
|
|
35
|
-
|
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
|
-
|
53
|
+
== 0.11.2
|
42
54
|
|
43
|
-
|
55
|
+
==== [jaknowlden] [ISSUE] Options were not nesting. Now fixed.
|
44
56
|
|
45
|
-
|
57
|
+
== 0.11.1
|
46
58
|
|
47
|
-
|
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
|
-
|
73
|
+
== 0.11.0
|
62
74
|
|
63
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
128
|
+
== 0.10.13
|
117
129
|
|
118
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
169
|
+
== 0.10.12
|
158
170
|
|
159
|
-
|
171
|
+
==== [vandrijevik] Recognizing file and line number of an assertion declaration on failure
|
160
172
|
|
161
|
-
|
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
|
-
|
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
|
-
|
192
|
+
==== [jaknowlden] Added responds_to as a respond_to alias
|
181
193
|
|
182
|
-
|
194
|
+
==== [jaknowlden] Added the equivalent_to macro to compare case equality (===). equals is now (==)
|
183
195
|
|
184
|
-
|
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
|
-
|
201
|
+
== 0.10.11
|
190
202
|
|
191
|
-
|
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
|
-
|
208
|
+
==== [gabrielg, jaknowlden] Added not! assertion macro
|
197
209
|
|
198
210
|
setup { User.new(:funny? => false) }
|
199
211
|
asserts(:funny?).not!
|
200
212
|
|
201
|
-
|
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
|
-
|
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
|
-
|
231
|
+
==== [gabrielg, jaknowlden] Returning non-zero status at-exit when tests don't pass
|
220
232
|
|
221
|
-
|
233
|
+
== 0.10.10
|
222
234
|
|
223
|
-
|
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
|
-
|
241
|
+
==== [jaknowlden] Removing Context#extend_assertions and related code
|
230
242
|
|
231
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
276
|
+
== 0.10.9 and before
|
265
277
|
|
266
|
-
See
|
278
|
+
See the commit log: http://github.com/thumblemonks/riot/commits/master
|
data/Gemfile
ADDED
data/README.markdown
CHANGED
@@ -1,146 +1,383 @@
|
|
1
1
|
# Riot
|
2
2
|
|
3
|
-
A fast, expressive and
|
4
|
-
|
3
|
+
A fast, expressive, and contextual ruby unit testing framework. Protest the slow test.
|
5
4
|
|
6
5
|
## Installation
|
7
6
|
|
8
|
-
|
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
|
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
|
-
|
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 — 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
|
-
|
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
|
-
###
|
39
|
+
### Assertions {#assertions}
|
56
40
|
|
57
|
-
|
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
|
-
|
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
|
-
|
45
|
+
Let's take this little for instance:
|
64
46
|
|
65
|
-
|
66
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
77
|
-
|
57
|
+
context "Yummy things" do
|
58
|
+
setup { ["cookies", "donuts"] }
|
78
59
|
|
79
|
-
|
80
|
-
|
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
|
-
|
63
|
+
Now that's real yummy. Want some more? Perhaps you just want to test the topic itself — not a method or attribute of it. You could do this:
|
85
64
|
|
86
|
-
|
87
|
-
|
65
|
+
context "Yummy things" do
|
66
|
+
setup { ["cookies", "donuts"] }
|
88
67
|
|
89
|
-
|
90
|
-
|
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
|
-
|
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
|
-
|
73
|
+
context "Yummy things" do
|
74
|
+
setup { ["cookies", "donuts"] }
|
102
75
|
|
103
|
-
|
76
|
+
asserts_topic.size(2)
|
77
|
+
end # Yummy things
|
104
78
|
|
105
|
-
|
106
|
-
|
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 — or currently do — 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
|
-
|
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
|
-
|
112
|
-
|
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
|
-
|
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
|
-
|
123
|
-
setup { get '/' }
|
304
|
+
require 'rubygems'
|
124
305
|
|
125
|
-
|
126
|
-
|
127
|
-
|
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
|
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
|
-
|
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
|
383
|
+
Riot is released under the MIT license. See [MIT LICENSE](https://github.com/thumblemonks/riot/blob/master/MIT-LICENSE).
|