shoulda-context 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/CONTRIBUTION_GUIDELINES.rdoc +10 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +20 -0
- data/MIT-LICENSE +22 -0
- data/README.rdoc +53 -0
- data/Rakefile +44 -0
- data/bin/convert_to_should_syntax +42 -0
- data/lib/shoulda-context.rb +12 -0
- data/lib/shoulda/context/assertions.rb +81 -0
- data/lib/shoulda/context/autoload_macros.rb +46 -0
- data/lib/shoulda/context/context.rb +446 -0
- data/lib/shoulda/context/proc_extensions.rb +14 -0
- data/lib/shoulda/context/tasks.rb +3 -0
- data/lib/shoulda/context/tasks/list_tests.rake +29 -0
- data/lib/shoulda/context/tasks/yaml_to_shoulda.rake +28 -0
- data/lib/shoulda/context/version.rb +5 -0
- data/rails/init.rb +4 -0
- data/test/fake_rails_root/test/shoulda_macros/custom_macro.rb +6 -0
- data/test/fake_rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +6 -0
- data/test/fake_rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +6 -0
- data/test/shoulda/autoload_macro_test.rb +18 -0
- data/test/shoulda/context_test.rb +368 -0
- data/test/shoulda/convert_to_should_syntax_test.rb +63 -0
- data/test/shoulda/helpers_test.rb +124 -0
- data/test/shoulda/should_test.rb +271 -0
- data/test/test_helper.rb +11 -0
- metadata +101 -0
@@ -0,0 +1,10 @@
|
|
1
|
+
We're using GitHub[http://github.com/thoughtbot/shoulda-context], and we've been getting any combination of github pull requests, tickets, patches, emails, etc. We need to normalize this workflow to make sure we don't miss any fixes.
|
2
|
+
|
3
|
+
* Make sure you're accessing the source from the {official repository}[http://github.com/thoughtbot/shoulda-context].
|
4
|
+
* We prefer git branches over patches, but we can take either.
|
5
|
+
* If you're using git, please make a branch for each separate contribution. We can cherry pick your commits, but pulling from a branch is easier.
|
6
|
+
* If you're submitting patches, please cut each fix or feature into a separate patch.
|
7
|
+
* There should be an issue[http://github.com/thoughtbot/shoulda-context/issues] for any submission. If you've found a bug and want to fix it, open a new ticket at the same time.
|
8
|
+
* Please <b>don't send pull requests</b> Just update the issue with the url for your fix (or attach the patch) when it's ready. The github pull requests pretty much get dropped on the floor until someone with commit rights notices them in the mailbox.
|
9
|
+
* Contributions without tests won't be accepted. The file <tt>/test/README</tt> explains the testing system pretty thoroughly.
|
10
|
+
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
columnize (0.3.2)
|
5
|
+
linecache (0.43)
|
6
|
+
mocha (0.9.10)
|
7
|
+
rake
|
8
|
+
rake (0.8.7)
|
9
|
+
ruby-debug (0.10.4)
|
10
|
+
columnize (>= 0.1)
|
11
|
+
ruby-debug-base (~> 0.10.4.0)
|
12
|
+
ruby-debug-base (0.10.4)
|
13
|
+
linecache (>= 0.3)
|
14
|
+
|
15
|
+
PLATFORMS
|
16
|
+
ruby
|
17
|
+
|
18
|
+
DEPENDENCIES
|
19
|
+
mocha
|
20
|
+
ruby-debug
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2007, Tammer Saleh, Thoughtbot, Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
= shoulda-context
|
2
|
+
|
3
|
+
{Official Documentation}[http://rubydoc.info/github/thoughtbot/shoulda-context/master/frames]
|
4
|
+
|
5
|
+
Shoulda's contexts make it easy to write understandable and maintainable tests for Test::Unit.
|
6
|
+
It's fully compatible with your existing tests in Test::Unit, and requires no retooling to use.
|
7
|
+
|
8
|
+
Refer to the {shoulda}[https://github.com/thoughtbot/shoulda] gem if you want to know more
|
9
|
+
about using shoulda with Rails or RSpec.
|
10
|
+
|
11
|
+
== Contexts
|
12
|
+
|
13
|
+
Instead of writing Ruby methods with lots_of_underscores, shoulda-context adds
|
14
|
+
context, setup, and should blocks...
|
15
|
+
|
16
|
+
class CalculatorTest < Test::Unit::TestCase
|
17
|
+
context "a calculator" do
|
18
|
+
setup do
|
19
|
+
@calculator = Calculator.new
|
20
|
+
end
|
21
|
+
|
22
|
+
should "add two numbers for the sum" do
|
23
|
+
assert_equal 4, @calculator.sum(2, 2)
|
24
|
+
end
|
25
|
+
|
26
|
+
should "multiply two numbers for the product" do
|
27
|
+
assert_equal 10, @calculator.product(2, 5)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
... which combine to produce the following test methods:
|
33
|
+
|
34
|
+
"test: A User instance should return its full name."
|
35
|
+
"test: A User instance with a profile should return true when sent #has_profile?."
|
36
|
+
|
37
|
+
== Assertions
|
38
|
+
|
39
|
+
It also has two additional Test::Unit assertions for working with Ruby's Array:
|
40
|
+
|
41
|
+
assert_same_elements([:a, :b, :c], [:c, :a, :b])
|
42
|
+
assert_contains(['a', '1'], /\d/)
|
43
|
+
assert_contains(['a', '1'], 'a')
|
44
|
+
|
45
|
+
= Credits
|
46
|
+
|
47
|
+
Shoulda is maintained and funded by {thoughtbot}[http://thoughtbot.com/community].
|
48
|
+
Thank you to all the {contributors}[https://github.com/thoughtbot/shoulda-context/contributors].
|
49
|
+
|
50
|
+
= License
|
51
|
+
|
52
|
+
Shoulda is Copyright © 2006-2010 thoughtbot, inc.
|
53
|
+
It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
|
8
|
+
$LOAD_PATH.unshift("lib")
|
9
|
+
load 'tasks/shoulda.rake'
|
10
|
+
|
11
|
+
test_files_pattern = 'test/**/*_test.rb'
|
12
|
+
Rake::TestTask.new do |t|
|
13
|
+
t.libs << 'lib' << 'test'
|
14
|
+
t.pattern = test_files_pattern
|
15
|
+
t.verbose = false
|
16
|
+
end
|
17
|
+
|
18
|
+
Rake::RDocTask.new { |rdoc|
|
19
|
+
rdoc.rdoc_dir = 'doc'
|
20
|
+
rdoc.title = "shoulda-context -- Context framework for Test::Unit"
|
21
|
+
rdoc.options << '--line-numbers'
|
22
|
+
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
|
23
|
+
rdoc.rdoc_files.include('README.rdoc', 'CONTRIBUTION_GUIDELINES.rdoc', 'lib/**/*.rb')
|
24
|
+
}
|
25
|
+
|
26
|
+
desc "Run code-coverage analysis using rcov"
|
27
|
+
task :coverage do
|
28
|
+
rm_rf "coverage"
|
29
|
+
files = Dir[test_files_pattern]
|
30
|
+
system "rcov --rails --sort coverage -Ilib #{files.join(' ')}"
|
31
|
+
end
|
32
|
+
|
33
|
+
eval("$specification = begin; #{IO.read('shoulda-context.gemspec')}; end")
|
34
|
+
Rake::GemPackageTask.new $specification do |pkg|
|
35
|
+
pkg.need_tar = true
|
36
|
+
pkg.need_zip = true
|
37
|
+
end
|
38
|
+
|
39
|
+
desc "Clean files generated by rake tasks"
|
40
|
+
task :clobber => [:clobber_rdoc, :clobber_package]
|
41
|
+
|
42
|
+
desc 'Default: run tests'
|
43
|
+
task :default => [:test]
|
44
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'fileutils'
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
TMP = Dir::tmpdir
|
6
|
+
|
7
|
+
def usage(msg = nil)
|
8
|
+
puts "Error: #{msg}" if msg
|
9
|
+
puts if msg
|
10
|
+
puts "Usage: #{File.basename(__FILE__)} normal_test_file.rb"
|
11
|
+
puts
|
12
|
+
puts "Will convert an existing test file with names like "
|
13
|
+
puts
|
14
|
+
puts " def test_should_do_stuff"
|
15
|
+
puts " ..."
|
16
|
+
puts " end"
|
17
|
+
puts
|
18
|
+
puts "to one using the new syntax: "
|
19
|
+
puts
|
20
|
+
puts " should \"be super cool\" do"
|
21
|
+
puts " ..."
|
22
|
+
puts " end"
|
23
|
+
puts
|
24
|
+
puts "A copy of the old file will be left under #{TMP} in case\nthis script just seriously screws up"
|
25
|
+
puts
|
26
|
+
exit (msg ? 2 : 0)
|
27
|
+
end
|
28
|
+
|
29
|
+
usage("Wrong number of arguments.") unless ARGV.size == 1
|
30
|
+
usage("Temp directory '#{TMP}' is not valid. Set TMPDIR environment variable to a writeable directory.") unless File.directory?(TMP) && File.writable?(TMP)
|
31
|
+
|
32
|
+
file = ARGV.shift
|
33
|
+
tmpfile = File.join(TMP, File.basename(file))
|
34
|
+
usage("File '#{file}' doesn't exist") unless File.exists?(file)
|
35
|
+
|
36
|
+
FileUtils.cp(file, tmpfile)
|
37
|
+
contents = File.read(tmpfile)
|
38
|
+
contents.gsub!(/def test_should_(\S+)/) {|line| "should \"#{$1.tr('_', ' ')}\" do"}
|
39
|
+
contents.gsub!(/def test_(\S+)/) {|line| "should \"RENAME ME: test #{$1.tr('_', ' ')}\" do"}
|
40
|
+
File.open(file, 'w') { |f| f.write(contents) }
|
41
|
+
|
42
|
+
puts "File '#{file}' has been converted to 'should' syntax. Old version has been stored in '#{tmpfile}'"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'shoulda/context/version'
|
3
|
+
require 'shoulda/context/proc_extensions'
|
4
|
+
require 'shoulda/context/assertions'
|
5
|
+
require 'shoulda/context/context'
|
6
|
+
require 'shoulda/context/autoload_macros'
|
7
|
+
|
8
|
+
class Test::Unit::TestCase
|
9
|
+
include Shoulda::Context::Assertions
|
10
|
+
include Shoulda::Context::InstanceMethods
|
11
|
+
extend Shoulda::Context::ClassMethods
|
12
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module Context
|
3
|
+
module Assertions
|
4
|
+
# Asserts that two arrays contain the same elements, the same number of times. Essentially ==, but unordered.
|
5
|
+
#
|
6
|
+
# assert_same_elements([:a, :b, :c], [:c, :a, :b]) => passes
|
7
|
+
def assert_same_elements(a1, a2, msg = nil)
|
8
|
+
[:select, :inject, :size].each do |m|
|
9
|
+
[a1, a2].each {|a| assert_respond_to(a, m, "Are you sure that #{a.inspect} is an array? It doesn't respond to #{m}.") }
|
10
|
+
end
|
11
|
+
|
12
|
+
assert a1h = a1.inject({}) { |h,e| h[e] = a1.select { |i| i == e }.size; h }
|
13
|
+
assert a2h = a2.inject({}) { |h,e| h[e] = a2.select { |i| i == e }.size; h }
|
14
|
+
|
15
|
+
assert_equal(a1h, a2h, msg)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Asserts that the given collection contains item x. If x is a regular expression, ensure that
|
19
|
+
# at least one element from the collection matches x. +extra_msg+ is appended to the error message if the assertion fails.
|
20
|
+
#
|
21
|
+
# assert_contains(['a', '1'], /\d/) => passes
|
22
|
+
# assert_contains(['a', '1'], 'a') => passes
|
23
|
+
# assert_contains(['a', '1'], /not there/) => fails
|
24
|
+
def assert_contains(collection, x, extra_msg = "")
|
25
|
+
collection = [collection] unless collection.is_a?(Array)
|
26
|
+
msg = "#{x.inspect} not found in #{collection.to_a.inspect} #{extra_msg}"
|
27
|
+
case x
|
28
|
+
when Regexp
|
29
|
+
assert(collection.detect { |e| e =~ x }, msg)
|
30
|
+
else
|
31
|
+
assert(collection.include?(x), msg)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Asserts that the given collection does not contain item x. If x is a regular expression, ensure that
|
36
|
+
# none of the elements from the collection match x.
|
37
|
+
def assert_does_not_contain(collection, x, extra_msg = "")
|
38
|
+
collection = [collection] unless collection.is_a?(Array)
|
39
|
+
msg = "#{x.inspect} found in #{collection.to_a.inspect} " + extra_msg
|
40
|
+
case x
|
41
|
+
when Regexp
|
42
|
+
assert(!collection.detect { |e| e =~ x }, msg)
|
43
|
+
else
|
44
|
+
assert(!collection.include?(x), msg)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Asserts that the given matcher returns true when +target+ is passed to #matches?
|
49
|
+
def assert_accepts(matcher, target, options = {})
|
50
|
+
if matcher.respond_to?(:in_context)
|
51
|
+
matcher.in_context(self)
|
52
|
+
end
|
53
|
+
|
54
|
+
if matcher.matches?(target)
|
55
|
+
assert_block { true }
|
56
|
+
if options[:message]
|
57
|
+
assert_match options[:message], matcher.negative_failure_message
|
58
|
+
end
|
59
|
+
else
|
60
|
+
assert_block(matcher.failure_message) { false }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Asserts that the given matcher returns false when +target+ is passed to #matches?
|
65
|
+
def assert_rejects(matcher, target, options = {})
|
66
|
+
if matcher.respond_to?(:in_context)
|
67
|
+
matcher.in_context(self)
|
68
|
+
end
|
69
|
+
|
70
|
+
unless matcher.matches?(target)
|
71
|
+
assert_block { true }
|
72
|
+
if options[:message]
|
73
|
+
assert_match options[:message], matcher.failure_message
|
74
|
+
end
|
75
|
+
else
|
76
|
+
assert_block(matcher.negative_failure_message) { false }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
# Call autoload_macros when you want to load test macros automatically in a non-Rails
|
3
|
+
# project (it's done automatically for Rails projects).
|
4
|
+
# You don't need to specify ROOT/test/shoulda_macros explicitly. Your custom macros
|
5
|
+
# are loaded automatically when you call autoload_macros.
|
6
|
+
#
|
7
|
+
# The first argument is the path to you application's root directory.
|
8
|
+
# All following arguments are directories relative to your root, which contain
|
9
|
+
# shoulda_macros subdirectories. These directories support the same kinds of globs as the
|
10
|
+
# Dir class.
|
11
|
+
#
|
12
|
+
# Basic usage (from a test_helper):
|
13
|
+
# Shoulda.autoload_macros(File.dirname(__FILE__) + '/..')
|
14
|
+
# will load everything in
|
15
|
+
# - your_app/test/shoulda_macros
|
16
|
+
#
|
17
|
+
# To load vendored macros as well:
|
18
|
+
# Shoulda.autoload_macros(APP_ROOT, 'vendor/*')
|
19
|
+
# will load everything in
|
20
|
+
# - APP_ROOT/vendor/*/shoulda_macros
|
21
|
+
# - APP_ROOT/test/shoulda_macros
|
22
|
+
#
|
23
|
+
# To load macros in an app with a vendor directory laid out like Rails':
|
24
|
+
# Shoulda.autoload_macros(APP_ROOT, 'vendor/{plugins,gems}/*')
|
25
|
+
# or
|
26
|
+
# Shoulda.autoload_macros(APP_ROOT, 'vendor/plugins/*', 'vendor/gems/*')
|
27
|
+
# will load everything in
|
28
|
+
# - APP_ROOT/vendor/plugins/*/shoulda_macros
|
29
|
+
# - APP_ROOT/vendor/gems/*/shoulda_macros
|
30
|
+
# - APP_ROOT/test/shoulda_macros
|
31
|
+
#
|
32
|
+
# If you prefer to stick testing dependencies away from your production dependencies:
|
33
|
+
# Shoulda.autoload_macros(APP_ROOT, 'vendor/*', 'test/vendor/*')
|
34
|
+
# will load everything in
|
35
|
+
# - APP_ROOT/vendor/*/shoulda_macros
|
36
|
+
# - APP_ROOT/test/vendor/*/shoulda_macros
|
37
|
+
# - APP_ROOT/test/shoulda_macros
|
38
|
+
def self.autoload_macros(root, *dirs)
|
39
|
+
dirs << File.join('test')
|
40
|
+
complete_dirs = dirs.map{|d| File.join(root, d, 'shoulda_macros')}
|
41
|
+
all_files = complete_dirs.inject([]){ |files, dir| files + Dir[File.join(dir, '*.rb')] }
|
42
|
+
all_files.each do |file|
|
43
|
+
require file
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,446 @@
|
|
1
|
+
module Shoulda
|
2
|
+
module Context
|
3
|
+
class << self
|
4
|
+
attr_accessor :contexts
|
5
|
+
def contexts # :nodoc:
|
6
|
+
@contexts ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
def current_context # :nodoc:
|
10
|
+
self.contexts.last
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_context(context) # :nodoc:
|
14
|
+
self.contexts.push(context)
|
15
|
+
end
|
16
|
+
|
17
|
+
def remove_context # :nodoc:
|
18
|
+
self.contexts.pop
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
# == Should statements
|
24
|
+
#
|
25
|
+
# Should statements are just syntactic sugar over normal Test::Unit test
|
26
|
+
# methods. A should block contains all the normal code and assertions
|
27
|
+
# you're used to seeing, with the added benefit that they can be wrapped
|
28
|
+
# inside context blocks (see below).
|
29
|
+
#
|
30
|
+
# === Example:
|
31
|
+
#
|
32
|
+
# class UserTest < Test::Unit::TestCase
|
33
|
+
#
|
34
|
+
# def setup
|
35
|
+
# @user = User.new("John", "Doe")
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# should "return its full name"
|
39
|
+
# assert_equal 'John Doe', @user.full_name
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# ...will produce the following test:
|
45
|
+
# * <tt>"test: User should return its full name. "</tt>
|
46
|
+
#
|
47
|
+
# Note: The part before <tt>should</tt> in the test name is gleamed from the name of the Test::Unit class.
|
48
|
+
#
|
49
|
+
# Should statements can also take a Proc as a <tt>:before </tt>option. This proc runs after any
|
50
|
+
# parent context's setups but before the current context's setup.
|
51
|
+
#
|
52
|
+
# === Example:
|
53
|
+
#
|
54
|
+
# context "Some context" do
|
55
|
+
# setup { puts("I run after the :before proc") }
|
56
|
+
#
|
57
|
+
# should "run a :before proc", :before => lambda { puts("I run before the setup") } do
|
58
|
+
# assert true
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# Should statements can also wrap matchers, making virtually any matcher
|
63
|
+
# usable in a macro style. The matcher's description is used to generate a
|
64
|
+
# test name and failure message, and the test will pass if the matcher
|
65
|
+
# matches the subject.
|
66
|
+
#
|
67
|
+
# === Example:
|
68
|
+
#
|
69
|
+
# should validate_presence_of(:first_name).with_message(/gotta be there/)
|
70
|
+
#
|
71
|
+
|
72
|
+
def should(name_or_matcher, options = {}, &blk)
|
73
|
+
if Shoulda::Context.current_context
|
74
|
+
Shoulda::Context.current_context.should(name_or_matcher, options, &blk)
|
75
|
+
else
|
76
|
+
context_name = self.name.gsub(/Test/, "")
|
77
|
+
context = Shoulda::Context::Context.new(context_name, self) do
|
78
|
+
should(name_or_matcher, options, &blk)
|
79
|
+
end
|
80
|
+
context.build
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Allows negative tests using matchers. The matcher's description is used
|
85
|
+
# to generate a test name and negative failure message, and the test will
|
86
|
+
# pass unless the matcher matches the subject.
|
87
|
+
#
|
88
|
+
# === Example:
|
89
|
+
#
|
90
|
+
# should_not set_the_flash
|
91
|
+
def should_not(matcher)
|
92
|
+
if Shoulda::Context.current_context
|
93
|
+
Shoulda::Context.current_context.should_not(matcher)
|
94
|
+
else
|
95
|
+
context_name = self.name.gsub(/Test/, "")
|
96
|
+
context = Shoulda::Context::Context.new(context_name, self) do
|
97
|
+
should_not(matcher)
|
98
|
+
end
|
99
|
+
context.build
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# == Before statements
|
104
|
+
#
|
105
|
+
# Before statements are should statements that run before the current
|
106
|
+
# context's setup. These are especially useful when setting expectations.
|
107
|
+
#
|
108
|
+
# === Example:
|
109
|
+
#
|
110
|
+
# class UserControllerTest < Test::Unit::TestCase
|
111
|
+
# context "the index action" do
|
112
|
+
# setup do
|
113
|
+
# @users = [Factory(:user)]
|
114
|
+
# User.stubs(:find).returns(@users)
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# context "on GET" do
|
118
|
+
# setup { get :index }
|
119
|
+
#
|
120
|
+
# should respond_with(:success)
|
121
|
+
#
|
122
|
+
# # runs before "get :index"
|
123
|
+
# before_should "find all users" do
|
124
|
+
# User.expects(:find).with(:all).returns(@users)
|
125
|
+
# end
|
126
|
+
# end
|
127
|
+
# end
|
128
|
+
# end
|
129
|
+
def before_should(name, &blk)
|
130
|
+
should(name, :before => blk) { assert true }
|
131
|
+
end
|
132
|
+
|
133
|
+
# Just like should, but never runs, and instead prints an 'X' in the Test::Unit output.
|
134
|
+
def should_eventually(name, options = {}, &blk)
|
135
|
+
context_name = self.name.gsub(/Test/, "")
|
136
|
+
context = Shoulda::Context::Context.new(context_name, self) do
|
137
|
+
should_eventually(name, &blk)
|
138
|
+
end
|
139
|
+
context.build
|
140
|
+
end
|
141
|
+
|
142
|
+
# == Contexts
|
143
|
+
#
|
144
|
+
# A context block groups should statements under a common set of setup/teardown methods.
|
145
|
+
# Context blocks can be arbitrarily nested, and can do wonders for improving the maintainability
|
146
|
+
# and readability of your test code.
|
147
|
+
#
|
148
|
+
# A context block can contain setup, should, should_eventually, and teardown blocks.
|
149
|
+
#
|
150
|
+
# class UserTest < Test::Unit::TestCase
|
151
|
+
# context "A User instance" do
|
152
|
+
# setup do
|
153
|
+
# @user = User.find(:first)
|
154
|
+
# end
|
155
|
+
#
|
156
|
+
# should "return its full name"
|
157
|
+
# assert_equal 'John Doe', @user.full_name
|
158
|
+
# end
|
159
|
+
# end
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# This code will produce the method <tt>"test: A User instance should return its full name. "</tt>.
|
163
|
+
#
|
164
|
+
# Contexts may be nested. Nested contexts run their setup blocks from out to in before each
|
165
|
+
# should statement. They then run their teardown blocks from in to out after each should statement.
|
166
|
+
#
|
167
|
+
# class UserTest < Test::Unit::TestCase
|
168
|
+
# context "A User instance" do
|
169
|
+
# setup do
|
170
|
+
# @user = User.find(:first)
|
171
|
+
# end
|
172
|
+
#
|
173
|
+
# should "return its full name"
|
174
|
+
# assert_equal 'John Doe', @user.full_name
|
175
|
+
# end
|
176
|
+
#
|
177
|
+
# context "with a profile" do
|
178
|
+
# setup do
|
179
|
+
# @user.profile = Profile.find(:first)
|
180
|
+
# end
|
181
|
+
#
|
182
|
+
# should "return true when sent :has_profile?"
|
183
|
+
# assert @user.has_profile?
|
184
|
+
# end
|
185
|
+
# end
|
186
|
+
# end
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# This code will produce the following methods
|
190
|
+
# * <tt>"test: A User instance should return its full name. "</tt>
|
191
|
+
# * <tt>"test: A User instance with a profile should return true when sent :has_profile?. "</tt>
|
192
|
+
#
|
193
|
+
# <b>Just like should statements, a context block can exist next to normal <tt>def test_the_old_way; end</tt>
|
194
|
+
# tests</b>. This means you do not have to fully commit to the context/should syntax in a test file.
|
195
|
+
|
196
|
+
def context(name, &blk)
|
197
|
+
if Shoulda::Context.current_context
|
198
|
+
Shoulda::Context.current_context.context(name, &blk)
|
199
|
+
else
|
200
|
+
context = Shoulda::Context::Context.new(name, self, &blk)
|
201
|
+
context.build
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Returns the class being tested, as determined by the test class name.
|
206
|
+
#
|
207
|
+
# class UserTest; described_type; end
|
208
|
+
# # => User
|
209
|
+
def described_type
|
210
|
+
@described_type ||= self.name.
|
211
|
+
gsub(/Test$/, '').
|
212
|
+
split('::').
|
213
|
+
inject(Object) { |parent, local_name| parent.const_get(local_name) }
|
214
|
+
end
|
215
|
+
|
216
|
+
# Sets the return value of the subject instance method:
|
217
|
+
#
|
218
|
+
# class UserTest < Test::Unit::TestCase
|
219
|
+
# subject { User.first }
|
220
|
+
#
|
221
|
+
# # uses the existing user
|
222
|
+
# should validate_uniqueness_of(:email)
|
223
|
+
# end
|
224
|
+
def subject(&block)
|
225
|
+
@subject_block = block
|
226
|
+
end
|
227
|
+
|
228
|
+
def subject_block # :nodoc:
|
229
|
+
@subject_block
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
module InstanceMethods
|
234
|
+
# Returns an instance of the class under test.
|
235
|
+
#
|
236
|
+
# class UserTest
|
237
|
+
# should "be a user" do
|
238
|
+
# assert_kind_of User, subject # passes
|
239
|
+
# end
|
240
|
+
# end
|
241
|
+
#
|
242
|
+
# The subject can be explicitly set using the subject class method:
|
243
|
+
#
|
244
|
+
# class UserTest
|
245
|
+
# subject { User.first }
|
246
|
+
# should "be an existing user" do
|
247
|
+
# assert !subject.new_record? # uses the first user
|
248
|
+
# end
|
249
|
+
# end
|
250
|
+
#
|
251
|
+
# The subject is used by all macros that require an instance of the class
|
252
|
+
# being tested.
|
253
|
+
def subject
|
254
|
+
@shoulda_subject ||= construct_subject
|
255
|
+
end
|
256
|
+
|
257
|
+
def subject_block # :nodoc:
|
258
|
+
(@shoulda_context && @shoulda_context.subject_block) || self.class.subject_block
|
259
|
+
end
|
260
|
+
|
261
|
+
def get_instance_of(object_or_klass) # :nodoc:
|
262
|
+
if object_or_klass.is_a?(Class)
|
263
|
+
object_or_klass.new
|
264
|
+
else
|
265
|
+
object_or_klass
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def instance_variable_name_for(klass) # :nodoc:
|
270
|
+
klass.to_s.split('::').last.underscore
|
271
|
+
end
|
272
|
+
|
273
|
+
private
|
274
|
+
|
275
|
+
def construct_subject
|
276
|
+
if subject_block
|
277
|
+
instance_eval(&subject_block)
|
278
|
+
else
|
279
|
+
get_instance_of(self.class.described_type)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
class Context # :nodoc:
|
285
|
+
|
286
|
+
attr_accessor :name # my name
|
287
|
+
attr_accessor :parent # may be another context, or the original test::unit class.
|
288
|
+
attr_accessor :subcontexts # array of contexts nested under myself
|
289
|
+
attr_accessor :setup_blocks # blocks given via setup methods
|
290
|
+
attr_accessor :teardown_blocks # blocks given via teardown methods
|
291
|
+
attr_accessor :shoulds # array of hashes representing the should statements
|
292
|
+
attr_accessor :should_eventuallys # array of hashes representing the should eventually statements
|
293
|
+
attr_accessor :subject_block
|
294
|
+
|
295
|
+
def initialize(name, parent, &blk)
|
296
|
+
Shoulda::Context.add_context(self)
|
297
|
+
self.name = name
|
298
|
+
self.parent = parent
|
299
|
+
self.setup_blocks = []
|
300
|
+
self.teardown_blocks = []
|
301
|
+
self.shoulds = []
|
302
|
+
self.should_eventuallys = []
|
303
|
+
self.subcontexts = []
|
304
|
+
|
305
|
+
merge_block(&blk)
|
306
|
+
Shoulda::Context.remove_context
|
307
|
+
end
|
308
|
+
|
309
|
+
def merge_block(&blk)
|
310
|
+
blk.bind(self).call
|
311
|
+
end
|
312
|
+
|
313
|
+
def context(name, &blk)
|
314
|
+
self.subcontexts << Context.new(name, self, &blk)
|
315
|
+
end
|
316
|
+
|
317
|
+
def setup(&blk)
|
318
|
+
self.setup_blocks << blk
|
319
|
+
end
|
320
|
+
|
321
|
+
def teardown(&blk)
|
322
|
+
self.teardown_blocks << blk
|
323
|
+
end
|
324
|
+
|
325
|
+
def should(name_or_matcher, options = {}, &blk)
|
326
|
+
if name_or_matcher.respond_to?(:description) && name_or_matcher.respond_to?(:matches?)
|
327
|
+
name = name_or_matcher.description
|
328
|
+
blk = lambda { assert_accepts name_or_matcher, subject }
|
329
|
+
else
|
330
|
+
name = name_or_matcher
|
331
|
+
end
|
332
|
+
|
333
|
+
if blk
|
334
|
+
self.shoulds << { :name => name, :before => options[:before], :block => blk }
|
335
|
+
else
|
336
|
+
self.should_eventuallys << { :name => name }
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def should_not(matcher)
|
341
|
+
name = matcher.description
|
342
|
+
blk = lambda { assert_rejects matcher, subject }
|
343
|
+
self.shoulds << { :name => "not #{name}", :block => blk }
|
344
|
+
end
|
345
|
+
|
346
|
+
def should_eventually(name, &blk)
|
347
|
+
self.should_eventuallys << { :name => name, :block => blk }
|
348
|
+
end
|
349
|
+
|
350
|
+
def subject(&block)
|
351
|
+
self.subject_block = block
|
352
|
+
end
|
353
|
+
|
354
|
+
def subject_block
|
355
|
+
return @subject_block if @subject_block
|
356
|
+
parent.subject_block
|
357
|
+
end
|
358
|
+
|
359
|
+
def full_name
|
360
|
+
parent_name = parent.full_name if am_subcontext?
|
361
|
+
return [parent_name, name].join(" ").strip
|
362
|
+
end
|
363
|
+
|
364
|
+
def am_subcontext?
|
365
|
+
parent.is_a?(self.class) # my parent is the same class as myself.
|
366
|
+
end
|
367
|
+
|
368
|
+
def test_unit_class
|
369
|
+
am_subcontext? ? parent.test_unit_class : parent
|
370
|
+
end
|
371
|
+
|
372
|
+
def test_methods
|
373
|
+
@test_methods ||= Hash.new { |h,k|
|
374
|
+
h[k] = Hash[k.instance_methods.map { |n| [n, true] }]
|
375
|
+
}
|
376
|
+
end
|
377
|
+
|
378
|
+
def create_test_from_should_hash(should)
|
379
|
+
test_name = ["test:", full_name, "should", "#{should[:name]}. "].flatten.join(' ').to_sym
|
380
|
+
|
381
|
+
if test_methods[test_unit_class][test_name.to_s] then
|
382
|
+
warn " * WARNING: '#{test_name}' is already defined"
|
383
|
+
end
|
384
|
+
|
385
|
+
test_methods[test_unit_class][test_name.to_s] = true
|
386
|
+
|
387
|
+
context = self
|
388
|
+
test_unit_class.send(:define_method, test_name) do
|
389
|
+
@shoulda_context = context
|
390
|
+
begin
|
391
|
+
context.run_parent_setup_blocks(self)
|
392
|
+
should[:before].bind(self).call if should[:before]
|
393
|
+
context.run_current_setup_blocks(self)
|
394
|
+
should[:block].bind(self).call
|
395
|
+
ensure
|
396
|
+
context.run_all_teardown_blocks(self)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
def run_all_setup_blocks(binding)
|
402
|
+
run_parent_setup_blocks(binding)
|
403
|
+
run_current_setup_blocks(binding)
|
404
|
+
end
|
405
|
+
|
406
|
+
def run_parent_setup_blocks(binding)
|
407
|
+
self.parent.run_all_setup_blocks(binding) if am_subcontext?
|
408
|
+
end
|
409
|
+
|
410
|
+
def run_current_setup_blocks(binding)
|
411
|
+
setup_blocks.each do |setup_block|
|
412
|
+
setup_block.bind(binding).call
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def run_all_teardown_blocks(binding)
|
417
|
+
teardown_blocks.reverse.each do |teardown_block|
|
418
|
+
teardown_block.bind(binding).call
|
419
|
+
end
|
420
|
+
self.parent.run_all_teardown_blocks(binding) if am_subcontext?
|
421
|
+
end
|
422
|
+
|
423
|
+
def print_should_eventuallys
|
424
|
+
should_eventuallys.each do |should|
|
425
|
+
test_name = [full_name, "should", "#{should[:name]}. "].flatten.join(' ')
|
426
|
+
puts " * DEFERRED: " + test_name
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
def build
|
431
|
+
shoulds.each do |should|
|
432
|
+
create_test_from_should_hash(should)
|
433
|
+
end
|
434
|
+
|
435
|
+
subcontexts.each { |context| context.build }
|
436
|
+
|
437
|
+
print_should_eventuallys
|
438
|
+
end
|
439
|
+
|
440
|
+
def method_missing(method, *args, &blk)
|
441
|
+
test_unit_class.send(method, *args, &blk)
|
442
|
+
end
|
443
|
+
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|