rspec-given 2.2.1 → 2.3.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +7 -0
- data/Gemfile.lock +19 -0
- data/README.md +199 -21
- data/Rakefile +12 -12
- data/examples/failing/natural_failing_spec.rb +36 -0
- data/examples/failing/sample_spec.rb +7 -0
- data/examples/integration/then_spec.rb +9 -0
- data/lib/rspec/given.rb +2 -7
- data/lib/rspec/given/configure.rb +2 -0
- data/lib/rspec/given/core.rb +9 -0
- data/lib/rspec/given/extensions.rb +57 -18
- data/lib/rspec/given/module_methods.rb +8 -0
- data/lib/rspec/given/natural_assertion.rb +178 -0
- data/lib/rspec/given/version.rb +4 -2
- metadata +43 -9
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,8 +1,19 @@
|
|
1
1
|
GEM
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
|
+
bluecloth (2.2.0)
|
4
5
|
diff-lcs (1.1.3)
|
6
|
+
flog (3.2.1)
|
7
|
+
ruby_parser (~> 3.1, > 3.1.0)
|
8
|
+
sexp_processor (~> 4.0)
|
9
|
+
ghpreview (0.0.2)
|
10
|
+
httpclient
|
11
|
+
listen
|
12
|
+
rb-fsevent
|
13
|
+
httpclient (2.3.0.1)
|
14
|
+
listen (0.6.0)
|
5
15
|
rake (0.9.2.2)
|
16
|
+
rb-fsevent (0.9.2)
|
6
17
|
rspec (2.12.0)
|
7
18
|
rspec-core (~> 2.12.0)
|
8
19
|
rspec-expectations (~> 2.12.0)
|
@@ -11,10 +22,18 @@ GEM
|
|
11
22
|
rspec-expectations (2.12.0)
|
12
23
|
diff-lcs (~> 1.1.3)
|
13
24
|
rspec-mocks (2.12.0)
|
25
|
+
ruby_parser (3.1.1)
|
26
|
+
sexp_processor (~> 4.1)
|
27
|
+
sexp_processor (4.1.3)
|
28
|
+
sorcerer (0.3.7)
|
14
29
|
|
15
30
|
PLATFORMS
|
16
31
|
ruby
|
17
32
|
|
18
33
|
DEPENDENCIES
|
34
|
+
bluecloth
|
35
|
+
flog
|
36
|
+
ghpreview
|
19
37
|
rake (>= 0.9.2.2)
|
20
38
|
rspec (>= 2.12)
|
39
|
+
sorcerer
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# rspec-given
|
2
2
|
|
3
|
-
Covering rspec-given, version
|
3
|
+
Covering rspec-given, version 3.0.0.beta.1.
|
4
4
|
|
5
5
|
rspec-given is an RSpec extension to allow Given/When/Then notation in
|
6
6
|
RSpec specifications. It is a natural extension of the experimental
|
@@ -232,7 +232,7 @@ Let me repeat that: <b>_Then_ clauses should not have any side
|
|
232
232
|
effects!</b> _Then_ clauses with side effects are erroneous. _Then_
|
233
233
|
clauses need to be idempotent, so that running them once, twice, a
|
234
234
|
hundred times, or never does not change the state of the program. (The
|
235
|
-
same is true of _And_ clauses).
|
235
|
+
same is true of _And_ and _Invariant_ clauses).
|
236
236
|
|
237
237
|
In RSpec terms, a _Then_ clause forms a RSpec Example that runs in the
|
238
238
|
context of an Example Group (defined by a describe or context clause).
|
@@ -261,7 +261,7 @@ The _And_ clause is similar to _Then_, but does not form its own RSpec
|
|
261
261
|
example. This means that _And_ clauses reuse the setup from a sibling
|
262
262
|
_Then_ clause. Using a single _Then_ an multiple _And_ clauses in an
|
263
263
|
example group means the setup for that group is run only once (for the
|
264
|
-
_Then_ clause) and reused for all the
|
264
|
+
_Then_ clause) and reused for all the _And_ clauses. This can be a
|
265
265
|
significant speed savings where the setup for an example group is
|
266
266
|
expensive.
|
267
267
|
|
@@ -325,6 +325,194 @@ Notes:
|
|
325
325
|
represented in the RSpec formatted output (e.g. the '--format html'
|
326
326
|
option).
|
327
327
|
|
328
|
+
## Natural Assertions
|
329
|
+
|
330
|
+
**NOTE:** <em>Natural assertions are an experimental feature of
|
331
|
+
RSpec/Given. They are currently disabled by default.</em>
|
332
|
+
|
333
|
+
RSpec/Given now supports the use of "natural assertions" in _Then_,
|
334
|
+
_And_, and _Invariant_ blocks. Natural assertions are just Ruby
|
335
|
+
conditionals, without the _should_ or _expect_ methods that RSpec
|
336
|
+
provides. Here are the Then/And examples from above, but written using
|
337
|
+
natural assertions:
|
338
|
+
|
339
|
+
```ruby
|
340
|
+
Then { pop_result == :top_item }
|
341
|
+
And { stack.top == :second_item }
|
342
|
+
And { stack.depth == original_depth - 1 }
|
343
|
+
```
|
344
|
+
|
345
|
+
Natural assertions must be enabled, either globally or on a per
|
346
|
+
context basis, to be recognized.
|
347
|
+
|
348
|
+
### Failure Messages with Natural Assertions
|
349
|
+
|
350
|
+
Since natural assertions do not depend upon matchers, you don't get
|
351
|
+
customized error messages from them. What you _do_ get is a complete
|
352
|
+
analsysis of the expression that failed.
|
353
|
+
|
354
|
+
For example, given the following failing specification:
|
355
|
+
|
356
|
+
```ruby
|
357
|
+
RSpec::Given.use_natural_assertions
|
358
|
+
|
359
|
+
describe "Natural Assertions" do
|
360
|
+
Given(:foo) { 1 }
|
361
|
+
Given(:bar) { 2 }
|
362
|
+
Then { foo + bar == 2 }
|
363
|
+
end
|
364
|
+
```
|
365
|
+
|
366
|
+
You would get:
|
367
|
+
|
368
|
+
```
|
369
|
+
1) Natural Assertions
|
370
|
+
Failure/Error: Then { foo + bar == 2 }
|
371
|
+
Then expression failed at /Users/jim/working/git/rspec-given/examples/failing/sample_spec.rb:6
|
372
|
+
expected: 3
|
373
|
+
to equal: 2
|
374
|
+
false <- foo + bar == 2
|
375
|
+
3 <- foo + bar
|
376
|
+
1 <- foo
|
377
|
+
2 <- bar
|
378
|
+
# ./examples/failing/sample_spec.rb:6:in `block in Then'
|
379
|
+
```
|
380
|
+
|
381
|
+
Notice how the failing expression "<code>foo+bar == 2</code>" was
|
382
|
+
broken down into subexpressions and values for each subexpression.
|
383
|
+
This gives you all the information you need to figure out exactly what
|
384
|
+
part of the expression is causing the failure.
|
385
|
+
|
386
|
+
Natural expressions will give additional information (e.g. "expected:
|
387
|
+
3 to equal: 2") for top level expressions involving any of the
|
388
|
+
comparison operators (==, !=, <, <=, >, >=) or matching operators (=~,
|
389
|
+
!~).
|
390
|
+
|
391
|
+
### Caveats on Natural Assertions
|
392
|
+
|
393
|
+
Keep the following in mind when using Natural Assertions.
|
394
|
+
|
395
|
+
* Only a single assertion per _Then_. Only the final expression of the
|
396
|
+
_Then_ block will be considered when determining pass/fail for the
|
397
|
+
assertion. If you _want_ to express a complex condition for the
|
398
|
+
_Then_, you need to use ||, && or some other logical operation to
|
399
|
+
join the conditions into a single expression (and the failure
|
400
|
+
message will breakdown the values for each part).
|
401
|
+
|
402
|
+
* Natural assertions must be **idempotent** (that means they don't
|
403
|
+
change anything). Since the natural assertion error message contains
|
404
|
+
the values of all the subexpressions, the expression and its
|
405
|
+
subexpressions will be evaluated multiple times. If the block is not
|
406
|
+
idempotent, you will get changing answers as the subexpressions are
|
407
|
+
evaluated.
|
408
|
+
|
409
|
+
That last point is important. If you write code like this:
|
410
|
+
|
411
|
+
```ruby
|
412
|
+
# DO NOT WRITE CODE LIKE THIS
|
413
|
+
context "Incorrect non-idempotent conditions" do
|
414
|
+
Given(:ary) { [1, 2, 3] }
|
415
|
+
Then { ary.delete(1) == nil }
|
416
|
+
end
|
417
|
+
```
|
418
|
+
|
419
|
+
Then the assertion will fail (because <code>ary.delete(1)</code> will
|
420
|
+
initially return 1). But when the error message is formated, the
|
421
|
+
system reports that <code>ary.delete(1)</code> returns nil. You will
|
422
|
+
scratch your head over that for a good while.
|
423
|
+
|
424
|
+
Instead, move the state changing code into a When block, then just
|
425
|
+
assert what you need about the result from the when block. Something
|
426
|
+
like this is good:
|
427
|
+
|
428
|
+
```ruby
|
429
|
+
context "Correct idempotent conditions" do
|
430
|
+
Given(:ary) { [1, 2, 3] }
|
431
|
+
When(:result) { ary.delete(1) }
|
432
|
+
Then { result == nil }
|
433
|
+
end
|
434
|
+
```
|
435
|
+
|
436
|
+
### Mixing Natural Assertions and RSpec Assertions
|
437
|
+
|
438
|
+
Natural assertions and RSpec assertions for the most part can be
|
439
|
+
intermixed in a single test suite, even within a single context.
|
440
|
+
Because there are a few corner cases that might cause problems, they
|
441
|
+
must be explicitly enabled before they will be considered.
|
442
|
+
|
443
|
+
To enable natural assertions in a context, call the
|
444
|
+
_use_natural_assertions_ method in that context. For example:
|
445
|
+
|
446
|
+
```ruby
|
447
|
+
context "Outer" do
|
448
|
+
use_natural_assertions
|
449
|
+
|
450
|
+
context "Inner" do
|
451
|
+
end
|
452
|
+
|
453
|
+
context "Disabled" do
|
454
|
+
use_natural_assertions false
|
455
|
+
end
|
456
|
+
end
|
457
|
+
```
|
458
|
+
|
459
|
+
Both the _Outer_ and _Inner_ contexts will use natural assertions. The
|
460
|
+
_Disabled_ context overrides the setting inherited from _Outer_ and
|
461
|
+
will not process natural assertions.
|
462
|
+
|
463
|
+
See the **configuration** section below to see how to enable natural
|
464
|
+
assertions project wide.
|
465
|
+
|
466
|
+
### Processing Natural Assertions
|
467
|
+
|
468
|
+
When natural assertions are enabled, they are only used if:
|
469
|
+
|
470
|
+
1. The block does not throw an RSpec assertion failure (or any other
|
471
|
+
exception for that matter).
|
472
|
+
|
473
|
+
1. The block returns false (blocks that return true pass the
|
474
|
+
assertion and don't need a failure message).
|
475
|
+
|
476
|
+
1. The block does not directly use RSpec's _should_ or _expect_
|
477
|
+
method.
|
478
|
+
|
479
|
+
Detecting that last point (the use of _should_ and _expect_) is done
|
480
|
+
by examining the source of the block. However it is not always
|
481
|
+
possible to correctly determine if _should_ and _expect_ are used
|
482
|
+
merely from the source. Fortunately you can always override the
|
483
|
+
automatic detection by either forcing natural assertions (with
|
484
|
+
:always) or disabling them (with false).
|
485
|
+
|
486
|
+
```ruby
|
487
|
+
# Then uses a non-RSpec version of "should" on
|
488
|
+
context "sample" do
|
489
|
+
use_natural_assertions :always
|
490
|
+
Then { obj.should } # Non-rspec should is called
|
491
|
+
end
|
492
|
+
```
|
493
|
+
|
494
|
+
```ruby
|
495
|
+
# Then does not call should directly, so disable natural assertions
|
496
|
+
context "sample" do
|
497
|
+
use_natural_assertions false
|
498
|
+
Then { check_validations(obj) } # check_validations calls "should" internally
|
499
|
+
end
|
500
|
+
|
501
|
+
# You can also just not use a Then for that condition.
|
502
|
+
context "another sample" do
|
503
|
+
it "checks validations" do
|
504
|
+
check_validations(obj)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
```
|
508
|
+
|
509
|
+
### Further Reading
|
510
|
+
|
511
|
+
Natural assertions were inspired by the [wrong assertion
|
512
|
+
library](http://rubygems.org/gems/wrong) by [Alex
|
513
|
+
Chaffee](http://rubygems.org/profiles/alexch) and [Steve
|
514
|
+
Conover](http://rubygems.org/profiles/sconoversf).
|
515
|
+
|
328
516
|
## Configuration
|
329
517
|
|
330
518
|
Just require 'rspec/given' in the spec helper of your project and it
|
@@ -340,28 +528,18 @@ unconditionally, then add the following line to your spec helper file:
|
|
340
528
|
RSpec::Given.source_caching_disabled = true
|
341
529
|
```
|
342
530
|
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
tests are much more like specifications when I use it. However, I'm
|
347
|
-
not entirely happy with it.
|
348
|
-
|
349
|
-
I would like to remove the need for the ".should" in all the _Then_
|
350
|
-
clauses. In other words, instead of saying:
|
531
|
+
Natural assertions are disabled by default. To globally configure
|
532
|
+
natural assertions, add one of the following lines to your spec_helper
|
533
|
+
file:
|
351
534
|
|
352
535
|
```ruby
|
353
|
-
|
536
|
+
RSpec::Given.use_natural_assertions # Enable natural assertions
|
537
|
+
RSpec::Given.use_natural_assertions true # Same as above
|
538
|
+
RSpec::Given.use_natural_assertions false # Disable natural assertions
|
539
|
+
RSpec::Given.use_natural_assertions :always # Always process natural assertions
|
540
|
+
# ... even when should/expect are detected
|
354
541
|
```
|
355
542
|
|
356
|
-
we could say:
|
357
|
-
|
358
|
-
```ruby
|
359
|
-
Then { x == y }
|
360
|
-
```
|
361
|
-
|
362
|
-
I think the [wrong assertion library](http://rubygems.org/gems/wrong)
|
363
|
-
has laid some groundwork in this area.
|
364
|
-
|
365
543
|
# Links
|
366
544
|
|
367
545
|
* Github: [https://github.com/jimweirich/rspec-given](https://github.com/jimweirich/rspec-given)
|
data/Rakefile
CHANGED
@@ -66,10 +66,19 @@ task :examples1 => [:verify_rspec1] do
|
|
66
66
|
sh "spec examples/stack/stack_spec1.rb"
|
67
67
|
end
|
68
68
|
|
69
|
+
EXAMPLES = FileList['examples/**/*_spec.rb'].exclude('examples/failing/*.rb')
|
70
|
+
FAILING_EXAMPLES = FileList['examples/failing/**/*_spec.rb']
|
71
|
+
|
69
72
|
desc "Run the examples in RSpec 2"
|
70
73
|
task :examples2 => [:verify_rspec2] do
|
71
74
|
puts "Running examples (with RSpec2)"
|
72
|
-
sh "rspec
|
75
|
+
sh "rspec #{EXAMPLES}"
|
76
|
+
end
|
77
|
+
|
78
|
+
desc "Run failing examples"
|
79
|
+
task :failing => [:verify_rspec2] do
|
80
|
+
puts "Running failing examples (with RSpec2)"
|
81
|
+
sh "rspec #{FAILING_EXAMPLES}"
|
73
82
|
end
|
74
83
|
|
75
84
|
task :verify_rspec1 do
|
@@ -89,17 +98,8 @@ end
|
|
89
98
|
directory 'html'
|
90
99
|
|
91
100
|
desc "Display the README file"
|
92
|
-
task :readme => "
|
93
|
-
sh "
|
94
|
-
end
|
95
|
-
|
96
|
-
desc "format the README file"
|
97
|
-
task "html/README.html" => ['html', 'README.md'] do
|
98
|
-
open("README.md") do |source|
|
99
|
-
open('html/README.html', 'w') do |out|
|
100
|
-
out.write(BlueCloth.new(source.read).to_html)
|
101
|
-
end
|
102
|
-
end
|
101
|
+
task :readme => ["README.md"] do
|
102
|
+
sh "ghpreview README.md"
|
103
103
|
end
|
104
104
|
|
105
105
|
desc "Generate an RDoc README"
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rspec/given'
|
2
|
+
require 'rspec/given/natural_assertion'
|
3
|
+
|
4
|
+
describe "Natural Assertions" do
|
5
|
+
use_natural_assertions
|
6
|
+
|
7
|
+
Given(:foo) { 1 }
|
8
|
+
Given(:expected) { 2 }
|
9
|
+
Given(:ary) { [1] }
|
10
|
+
Given(:empty) { [] }
|
11
|
+
Given(:null) { nil }
|
12
|
+
Then { foo+foo+2*foo == expected }
|
13
|
+
Then { nil == "HI" && true && :symbol && 1}
|
14
|
+
Then { foo.should == 2 }
|
15
|
+
Then { foo != 1 }
|
16
|
+
Then { foo.should_not == 1 }
|
17
|
+
Then { foo.should be_nil }
|
18
|
+
Then { ary.empty? }
|
19
|
+
Then { !null.nil? }
|
20
|
+
Then { fail "OUCH" }
|
21
|
+
Then { ! empty.empty? }
|
22
|
+
Then {
|
23
|
+
(puts "Ha ha world", ! true)
|
24
|
+
}
|
25
|
+
|
26
|
+
context "Incorrect non-idempotent conditions" do
|
27
|
+
Given(:ary) { [1, 2, 3] }
|
28
|
+
Then { ary.delete(1) == nil }
|
29
|
+
end
|
30
|
+
|
31
|
+
context "Correct idempotent conditions" do
|
32
|
+
Given(:ary) { [1, 2, 3] }
|
33
|
+
When(:result) { ary.delete(1) }
|
34
|
+
Then { result == nil }
|
35
|
+
end
|
36
|
+
end
|
data/lib/rspec/given.rb
CHANGED
@@ -12,12 +12,7 @@ end
|
|
12
12
|
if RSpec::Given.using_old_rspec?
|
13
13
|
require 'rspec/given/rspec1_given'
|
14
14
|
else
|
15
|
-
require 'rspec/given/
|
16
|
-
require 'rspec/given/module_methods'
|
17
|
-
require 'rspec/given/file_cache'
|
18
|
-
require 'rspec/given/line_extractor'
|
19
|
-
require 'rspec/given/extensions'
|
20
|
-
require 'rspec/given/configure'
|
21
|
-
require 'rspec/given/failure'
|
15
|
+
require 'rspec/given/core'
|
22
16
|
require 'rspec/given/have_failed'
|
17
|
+
require 'rspec/given/configure'
|
23
18
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rspec/given/failure'
|
2
2
|
require 'rspec/given/module_methods'
|
3
|
+
require 'rspec/given/natural_assertion'
|
3
4
|
|
4
5
|
module RSpec
|
5
6
|
module Given
|
@@ -9,25 +10,47 @@ module RSpec
|
|
9
10
|
# implementation-specific.
|
10
11
|
module InstanceExtensions # :nodoc:
|
11
12
|
|
13
|
+
# List of containing contexts in order from innermost to
|
14
|
+
# outermost.
|
15
|
+
def _rg_inner_contexts # :nodoc:
|
16
|
+
self.class.ancestors.select { |context|
|
17
|
+
context.respond_to?(:_rg_givens)
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
12
21
|
# List of containing contexts in order from outermost to
|
13
22
|
# innermost.
|
14
23
|
def _rg_contexts # :nodoc:
|
15
|
-
|
16
|
-
|
17
|
-
|
24
|
+
_rg_inner_contexts.reverse
|
25
|
+
end
|
26
|
+
|
27
|
+
def _rg_info(keyword)
|
28
|
+
_rg_inner_contexts.each do |context|
|
29
|
+
h = context._rg_context_info
|
30
|
+
if h.has_key?(keyword)
|
31
|
+
return h[keyword]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def _rg_natural_assertions?(nassert)
|
38
|
+
info_value = _rg_info(:natural_assertions_enabled)
|
39
|
+
use_na = info_value.nil? ? RSpec::Given.natural_assertions_enabled? : info_value
|
40
|
+
(! nassert.using_rspec_assertion? && use_na) || (use_na == :always)
|
18
41
|
end
|
19
42
|
|
20
43
|
# Establish all the Given preconditions the current and
|
21
44
|
# surrounding describe/context blocks, starting with the
|
22
45
|
# outermost context.
|
23
46
|
def _rg_establish_givens # :nodoc:
|
24
|
-
return if defined?(@_rg_ran)
|
47
|
+
return if defined?(@_rg_ran) && @_rg_ran
|
48
|
+
@_rg_ran = true
|
25
49
|
_rg_contexts.each do |context|
|
26
50
|
context._rg_givens.each do |block|
|
27
51
|
instance_eval(&block)
|
28
52
|
end
|
29
53
|
end
|
30
|
-
@_rg_ran = true
|
31
54
|
end
|
32
55
|
|
33
56
|
# Check all the invariants in the current and surrounding
|
@@ -35,7 +58,7 @@ module RSpec
|
|
35
58
|
def _rg_check_invariants # :nodoc:
|
36
59
|
_rg_contexts.each do |context|
|
37
60
|
context._rg_invariants.each do |block|
|
38
|
-
|
61
|
+
_rg_evaluate(block)
|
39
62
|
end
|
40
63
|
end
|
41
64
|
end
|
@@ -43,7 +66,7 @@ module RSpec
|
|
43
66
|
def _rg_check_ands # :nodoc:
|
44
67
|
return if self.class._rg_context_info[:and_ran]
|
45
68
|
self.class._rg_and_blocks.each do |block|
|
46
|
-
|
69
|
+
_rg_evaluate(block)
|
47
70
|
end
|
48
71
|
self.class._rg_context_info[:and_ran] = true
|
49
72
|
end
|
@@ -52,9 +75,18 @@ module RSpec
|
|
52
75
|
def _rg_then(&block) # :nodoc:
|
53
76
|
_rg_establish_givens
|
54
77
|
_rg_check_invariants
|
55
|
-
|
78
|
+
_rg_evaluate(block)
|
56
79
|
_rg_check_ands
|
57
80
|
end
|
81
|
+
|
82
|
+
def _rg_evaluate(block)
|
83
|
+
unless instance_eval(&block)
|
84
|
+
nassert = NaturalAssertion.new(block, binding, self.class._rg_lines)
|
85
|
+
if ! nassert.using_rspec_assertion? && nassert.has_content?
|
86
|
+
::RSpec::Expectations.fail_with nassert.message
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
58
90
|
end
|
59
91
|
|
60
92
|
module ClassExtensions
|
@@ -76,7 +108,7 @@ module RSpec
|
|
76
108
|
end
|
77
109
|
|
78
110
|
def _rg_context_info
|
79
|
-
@
|
111
|
+
@_rg_context_info ||= {}
|
80
112
|
end
|
81
113
|
|
82
114
|
def _rg_lines
|
@@ -106,8 +138,7 @@ module RSpec
|
|
106
138
|
# Scenario "a scenario description" do ... end
|
107
139
|
#
|
108
140
|
def Scenario(description, &block)
|
109
|
-
line = eval("__LINE__", block.binding)
|
110
|
-
file = eval("__FILE__", block.binding)
|
141
|
+
file, line = eval("[__LINE__, __FILE__]", block.binding)
|
111
142
|
puts "WARNING: Scenario is deprecated, please use either describe or context (#{file}:#{line})"
|
112
143
|
context(description, &block)
|
113
144
|
end
|
@@ -119,8 +150,8 @@ module RSpec
|
|
119
150
|
# every time the specification is executed.
|
120
151
|
#
|
121
152
|
# :call-seq:
|
122
|
-
# Given(:name
|
123
|
-
# Given
|
153
|
+
# Given(:name) { ... code ... }
|
154
|
+
# Given { ... code ... }
|
124
155
|
#
|
125
156
|
def Given(*args, &block)
|
126
157
|
if args.first.is_a?(Symbol)
|
@@ -136,6 +167,7 @@ module RSpec
|
|
136
167
|
#
|
137
168
|
# :call-seq:
|
138
169
|
# Given!(:name) { ... code ... }
|
170
|
+
#
|
139
171
|
def Given!(name, &block)
|
140
172
|
let!(name, &block)
|
141
173
|
_rg_givens << _rg_trigger_given(name)
|
@@ -144,8 +176,8 @@ module RSpec
|
|
144
176
|
# Declare the code that is under test.
|
145
177
|
#
|
146
178
|
# :call-seq:
|
147
|
-
# When(:named_result
|
148
|
-
# When
|
179
|
+
# When(:named_result) { ... code_under_test ... }
|
180
|
+
# When { ... code_under_test ... }
|
149
181
|
#
|
150
182
|
def When(*args, &block)
|
151
183
|
if args.first.is_a?(Symbol)
|
@@ -170,10 +202,13 @@ module RSpec
|
|
170
202
|
# Then supplies an assertion that should be true after all the
|
171
203
|
# Given and When blocks have been run. All invariants in scope
|
172
204
|
# will be checked before the Then block is run.
|
205
|
+
#
|
206
|
+
# :call-seq:
|
207
|
+
# Then { ... assertion ... }
|
208
|
+
#
|
173
209
|
def Then(&block)
|
174
|
-
|
175
|
-
file = eval "__FILE__",
|
176
|
-
line = eval "__LINE__", b
|
210
|
+
env = block.binding
|
211
|
+
file, line = eval "[__FILE__, __LINE__]", env
|
177
212
|
description = _rg_lines.line(file, line) unless RSpec::Given.source_caching_disabled
|
178
213
|
if description
|
179
214
|
cmd = "it(description)"
|
@@ -194,6 +229,10 @@ module RSpec
|
|
194
229
|
fail "And defined without a Then" unless _rg_context_info[:then_defined]
|
195
230
|
_rg_and_blocks << block
|
196
231
|
end
|
232
|
+
|
233
|
+
def use_natural_assertions(enabled=true)
|
234
|
+
_rg_context_info[:natural_assertions_enabled] = enabled
|
235
|
+
end
|
197
236
|
end
|
198
237
|
end
|
199
238
|
end
|
@@ -12,5 +12,13 @@ module RSpec
|
|
12
12
|
format_active = c.formatters.any? { |f| f.class.name !~ /ProgressFormatter/ }
|
13
13
|
RSpec::Given.source_caching_disabled = ! format_active
|
14
14
|
end
|
15
|
+
|
16
|
+
def self.use_natural_assertions(enabled=true)
|
17
|
+
@natural_assertions_enabled = enabled
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.natural_assertions_enabled?
|
21
|
+
@natural_assertions_enabled
|
22
|
+
end
|
15
23
|
end
|
16
24
|
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'ripper'
|
2
|
+
require 'sorcerer'
|
3
|
+
|
4
|
+
module RSpec
|
5
|
+
module Given
|
6
|
+
|
7
|
+
InvalidThenError = Class.new(StandardError)
|
8
|
+
|
9
|
+
class EvalErr
|
10
|
+
def initialize(str)
|
11
|
+
@string = str
|
12
|
+
end
|
13
|
+
def size
|
14
|
+
inspect.size
|
15
|
+
end
|
16
|
+
def to_s
|
17
|
+
@string
|
18
|
+
end
|
19
|
+
def inspect
|
20
|
+
@string
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class NaturalAssertion
|
25
|
+
|
26
|
+
def initialize(block, env, line_extractor)
|
27
|
+
@block = block
|
28
|
+
@env = env
|
29
|
+
@line_extractor = line_extractor
|
30
|
+
set_file_and_line
|
31
|
+
end
|
32
|
+
|
33
|
+
VOID_SEXP = [:void_stmt]
|
34
|
+
|
35
|
+
def using_rspec_assertion?
|
36
|
+
using_should? || using_expect?
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_content?
|
40
|
+
assertion_sexp != VOID_SEXP
|
41
|
+
end
|
42
|
+
|
43
|
+
def message
|
44
|
+
@output = "Then expression failed at #{source_line}\n"
|
45
|
+
explain_failure
|
46
|
+
display_pairs(expression_value_pairs)
|
47
|
+
@output << "\n"
|
48
|
+
@output
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def using_should?
|
54
|
+
source =~ /\.\s*should(_not)?\b/
|
55
|
+
end
|
56
|
+
|
57
|
+
def using_expect?
|
58
|
+
source =~ /\bexpect\s*[({].*[)}]\s*\.\s*(not_)?to\b/
|
59
|
+
end
|
60
|
+
|
61
|
+
BINARY_EXPLAINATIONS = {
|
62
|
+
:== => "to equal",
|
63
|
+
:!= => "to not equal",
|
64
|
+
:< => "to be less than",
|
65
|
+
:<= => "to be less or equal to",
|
66
|
+
:> => "to be greater than",
|
67
|
+
:>= => "to be greater or equal to",
|
68
|
+
:=~ => "to match",
|
69
|
+
:!~ => "to not match",
|
70
|
+
}
|
71
|
+
|
72
|
+
def explain_failure
|
73
|
+
if assertion_sexp.first == :binary && msg = BINARY_EXPLAINATIONS[assertion_sexp[2]]
|
74
|
+
@output << explain_expected("expected", assertion_sexp[1], msg, assertion_sexp[3])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def explain_expected(expect_msg, expect_sexp, got_msg, got_sexp)
|
79
|
+
width = [expect_msg.size, got_msg.size].max
|
80
|
+
sprintf("%#{width}s: %s\n%#{width}s: %s\n",
|
81
|
+
expect_msg, eval_sexp(expect_sexp),
|
82
|
+
got_msg, eval_sexp(got_sexp))
|
83
|
+
end
|
84
|
+
|
85
|
+
def expression_value_pairs
|
86
|
+
assertion_subexpressions.map { |exp|
|
87
|
+
[exp, eval_in(exp, @env)]
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def assertion_subexpressions
|
92
|
+
Sorcerer.subexpressions(assertion_sexp).reverse.uniq.reverse
|
93
|
+
end
|
94
|
+
|
95
|
+
def assertion_sexp
|
96
|
+
@assertion_sexp ||= extract_test_expression(Ripper::SexpBuilder.new(source).parse)
|
97
|
+
end
|
98
|
+
|
99
|
+
def source
|
100
|
+
@line_extractor.line(@code_file, @code_line)
|
101
|
+
end
|
102
|
+
|
103
|
+
def set_file_and_line
|
104
|
+
@code_file, @code_line = eval "[__FILE__, __LINE__]", @block.binding
|
105
|
+
@code_line = @code_line.to_i
|
106
|
+
end
|
107
|
+
|
108
|
+
def extract_test_expression(sexp)
|
109
|
+
brace_block = extract_brace_block(sexp)
|
110
|
+
extract_first_statement(brace_block)
|
111
|
+
end
|
112
|
+
|
113
|
+
def extract_brace_block(sexp)
|
114
|
+
unless then_block?(sexp)
|
115
|
+
source = Sorcerer.source(sexp)
|
116
|
+
fail InvalidThenError, "Unexpected code at #{source_line}\n#{source}"
|
117
|
+
end
|
118
|
+
sexp[1][2][2]
|
119
|
+
end
|
120
|
+
|
121
|
+
def then_block?(sexp)
|
122
|
+
sexp.first == :program &&
|
123
|
+
sexp[1].first == :stmts_add &&
|
124
|
+
sexp[1][2].first == :method_add_block &&
|
125
|
+
(sexp[1][2][2].first == :brace_block || sexp[1][2][2].first == :do_block)
|
126
|
+
end
|
127
|
+
|
128
|
+
def extract_first_statement(block_sexp)
|
129
|
+
unless contains_one_statement?(block_sexp)
|
130
|
+
source = Sorcerer.source(block_sexp)
|
131
|
+
fail InvalidThenError, "Multiple statements in Then block at #{source_line}\n#{source}"
|
132
|
+
end
|
133
|
+
extract_statement_from_block(block_sexp)
|
134
|
+
end
|
135
|
+
|
136
|
+
def contains_one_statement?(block_sexp)
|
137
|
+
block_sexp[2].first == :stmts_add &&
|
138
|
+
block_sexp[2][1].first == :stmts_new
|
139
|
+
end
|
140
|
+
|
141
|
+
def extract_statement_from_block(block_sexp)
|
142
|
+
block_sexp[2][2]
|
143
|
+
end
|
144
|
+
|
145
|
+
def eval_sexp(sexp)
|
146
|
+
expr = Sorcerer.source(sexp)
|
147
|
+
eval_in(expr, @env)
|
148
|
+
end
|
149
|
+
|
150
|
+
def eval_in(exp, binding)
|
151
|
+
eval(exp, binding).inspect
|
152
|
+
rescue StandardError => ex
|
153
|
+
EvalErr.new("#{ex.class}: #{ex.message}")
|
154
|
+
end
|
155
|
+
|
156
|
+
WRAP_WIDTH = 20
|
157
|
+
|
158
|
+
def display_pairs(pairs)
|
159
|
+
width = suggest_width(pairs)
|
160
|
+
pairs.each do |x, v|
|
161
|
+
fmt = (v.size > WRAP_WIDTH) ?
|
162
|
+
" %-#{width+2}s\n #{' '*(width+2)} <- %s\n" :
|
163
|
+
" %-#{width+2}s <- %s\n"
|
164
|
+
@output << sprintf(fmt, v, x)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def suggest_width(pairs)
|
169
|
+
pairs.map { |x,v| v.size }.select { |n| n < WRAP_WIDTH }.max || 10
|
170
|
+
end
|
171
|
+
|
172
|
+
def source_line
|
173
|
+
"#{@code_file}:#{@code_line}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
end
|
data/lib/rspec/given/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-given
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
5
|
-
prerelease:
|
4
|
+
version: 2.3.0.beta.1
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Jim Weirich
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-12-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -27,6 +27,22 @@ dependencies:
|
|
27
27
|
- - ! '>'
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: 1.2.8
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: sorcerer
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.3.7
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.3.7
|
30
46
|
- !ruby/object:Gem::Dependency
|
31
47
|
name: bluecloth
|
32
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,6 +75,22 @@ dependencies:
|
|
59
75
|
- - ! '>'
|
60
76
|
- !ruby/object:Gem::Version
|
61
77
|
version: 2.4.2
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: ghpreview
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 0.0.1
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 0.0.1
|
62
94
|
description: ! 'Given is an RSpec extension that allows explicit definition of the
|
63
95
|
|
64
96
|
pre and post-conditions for code under test.
|
@@ -76,20 +108,25 @@ files:
|
|
76
108
|
- README.md
|
77
109
|
- lib/rspec-given.rb
|
78
110
|
- lib/rspec/given/configure.rb
|
111
|
+
- lib/rspec/given/core.rb
|
79
112
|
- lib/rspec/given/extensions.rb
|
80
113
|
- lib/rspec/given/failure.rb
|
81
114
|
- lib/rspec/given/file_cache.rb
|
82
115
|
- lib/rspec/given/have_failed.rb
|
83
116
|
- lib/rspec/given/line_extractor.rb
|
84
117
|
- lib/rspec/given/module_methods.rb
|
118
|
+
- lib/rspec/given/natural_assertion.rb
|
85
119
|
- lib/rspec/given/rspec1_given.rb
|
86
120
|
- lib/rspec/given/version.rb
|
87
121
|
- lib/rspec/given.rb
|
88
122
|
- examples/example_helper.rb
|
123
|
+
- examples/failing/natural_failing_spec.rb
|
124
|
+
- examples/failing/sample_spec.rb
|
89
125
|
- examples/integration/and_spec.rb
|
90
126
|
- examples/integration/focused_line_spec.rb
|
91
127
|
- examples/integration/given_spec.rb
|
92
128
|
- examples/integration/invariant_spec.rb
|
129
|
+
- examples/integration/then_spec.rb
|
93
130
|
- examples/other/line_example.rb
|
94
131
|
- examples/stack/stack.rb
|
95
132
|
- examples/stack/stack_spec.rb
|
@@ -111,16 +148,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
111
148
|
requirements:
|
112
149
|
- - ! '>='
|
113
150
|
- !ruby/object:Gem::Version
|
114
|
-
version:
|
115
|
-
segments:
|
116
|
-
- 0
|
117
|
-
hash: 3954842768326946335
|
151
|
+
version: 1.9.2
|
118
152
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
153
|
none: false
|
120
154
|
requirements:
|
121
|
-
- - ! '
|
155
|
+
- - ! '>'
|
122
156
|
- !ruby/object:Gem::Version
|
123
|
-
version:
|
157
|
+
version: 1.3.1
|
124
158
|
requirements: []
|
125
159
|
rubyforge_project: given
|
126
160
|
rubygems_version: 1.8.24
|