0
require File.join(File.dirname(__FILE__), 'proc_extensions')
0
- # = context and should blocks
0
+ attr_accessor :current_context
0
+ # Should statements are just syntactic sugar over normal Test::Unit test methods. A should block
0
+ # contains all the normal code and assertions you're used to seeing, with the added benefit that
0
+ # they can be wrapped inside context blocks (see below).
0
+ # class UserTest << Test::Unit::TestCase
0
+ # @user = User.new("John", "Doe")
0
+ # should "return its full name"
0
+ # assert_equal 'John Doe', @user.full_name
0
+ # ...will produce the following test:
0
+ # * <tt>"test: User should return its full name. "</tt>
0
+ # Note: The part before <tt>should</tt> in the test name is gleamed from the name of the Test::Unit class.
0
+ def should(name, &blk)
0
+ if Shoulda.current_context
0
+ Shoulda.current_context.should(name, &blk)
0
+ context_name = self.name.gsub(/Test/, "")
0
+ context = Thoughtbot::Shoulda::Context.new(context_name, self) do
0
+ # Just like should, but never runs, and instead prints an 'X' in the Test::Unit output.
0
+ def should_eventually(name, &blk)
0
+ context_name = self.name.gsub(/Test/, "")
0
+ context = Thoughtbot::Shoulda::Context.new(context_name, self) do
0
+ should_eventually(name, &blk)
0
- # A context block groups should statements under a common set
up/teardown method.
0
+ # A context block groups should statements under a common set
of setup/teardown methods.
0
# Context blocks can be arbitrarily nested, and can do wonders for improving the maintainability
0
# and readability of your test code.
0
# A context block can contain setup, should, should_eventually, and teardown blocks.
0
# class UserTest << Test::Unit::TestCase
0
- # context "
a User instance" do
0
+ # context "
A User instance" do
0
# @user = User.find(:first)
0
@@ -24,13 +74,13 @@ module Thoughtbot
0
- # This code will produce the method <tt>"test
a User instance should return its full name"</tt>.
0
+ # This code will produce the method <tt>"test
: A User instance should return its full name. "</tt>.
0
- # Contexts may be nested. Nested contexts run their setup blocks from out to in before each test.
0
- # They then run their teardown blocks from in to out after each test.
0
+ # Contexts may be nested. Nested contexts run their setup blocks from out to in before each
0
+ # should statement. They then run their teardown blocks from in to out after each should statement.
0
# class UserTest << Test::Unit::TestCase
0
- # context "
a User instance" do
0
+ # context "
A User instance" do
0
# @user = User.find(:first)
0
@@ -52,104 +102,130 @@ module Thoughtbot
0
# This code will produce the following methods
0
- # * <tt>"test: a User instance should return its full name."</tt>
0
- # * <tt>"test: a User instance with a profile should return true when sent :has_profile?."</tt>
0
- # <b>A context block can exist next to normal <tt>def test_the_old_way; end</tt> tests</b>,
0
- # meaning you do not have to fully commit to the context/should syntax in a test file.
0
+ # * <tt>"test: A User instance should return its full name. "</tt>
0
+ # * <tt>"test: A User instance with a profile should return true when sent :has_profile?. "</tt>
0
+ # <b>Just like should statements, a context block can exist next to normal <tt>def test_the_old_way; end</tt>
0
+ # tests</b>. This means you do not have to fully commit to the context/should syntax in a test file.
0
+ def context(name, &blk)
0
+ if Shoulda.current_context
0
+ Shoulda.current_context.context(name, &blk)
0
+ context = Thoughtbot::Shoulda::Context.new(name, self, &blk)
0
+ class Context # :nodoc:
0
+ attr_accessor :name # my name
0
+ attr_accessor :parent # may be another context, or the original test::unit class.
0
+ attr_accessor :subcontexts # array of contexts nested under myself
0
+ attr_accessor :setup_block # block given via a setup method
0
+ attr_accessor :teardown_block # block given via a teardown method
0
+ attr_accessor :shoulds # array of hashes representing the should statements
0
+ attr_accessor :should_eventuallys # array of hashes representing the should eventually statements
0
+ def initialize(name, parent, &blk)
0
+ Shoulda.current_context = self
0
+ self.setup_block = nil
0
+ self.teardown_block = nil
0
+ self.should_eventuallys = []
0
- def self.included(other) # :nodoc:
0
- @@teardown_blocks = []
0
- # Defines a test method. Can be called either inside our outside of a context.
0
- # Optionally specify <tt>:unimplimented => true</tt> (see should_eventually).
0
- # class UserTest << Test::Unit::TestCase
0
- # should "return first user on find(:first)"
0
- # assert_equal users(:first), User.find(:first)
0
- # Would create a test named
0
- # 'test: should return first user on find(:first)'
0
- def should(name, opts = {}, &should_block)
0
- test_name = ["test:", @@context_names, "should", "#{name}. "].flatten.join(' ').to_sym
0
- name_defined = eval("self.instance_methods.include?('#{test_name.to_s.gsub(/['"]/, '\$1')}')", should_block.binding)
0
- raise ArgumentError, "'#{test_name}' is already defined" and return if name_defined
0
- setup_blocks = @@setup_blocks.dup
0
- teardown_blocks = @@teardown_blocks.dup
0
- if opts[:unimplemented]
0
- define_method test_name do |*args|
0
- # XXX find a better way of doing this.
0
- STDOUT.putc "X" # Tests for this model are missing.
0
- define_method test_name do |*args|
0
- setup_blocks.each {|b| b.bind(self).call }
0
- should_block.bind(self).call(*args)
0
- teardown_blocks.reverse.each {|b| b.bind(self).call }
0
+ Shoulda.current_context = nil
0
+ def context(name, &blk)
0
+ subcontexts << Context.new(name, self, &blk)
0
+ Shoulda.current_context = self
0
+ self.setup_block = blk
0
+ self.teardown_block = blk
0
+ def should(name, &blk)
0
+ self.shoulds << { :name => name, :block => blk }
0
+ def should_eventually(name, &blk)
0
+ self.should_eventuallys << { :name => name, :block => blk }
0
+ parent_name = parent.full_name if am_subcontext?
0
+ return [parent_name, name].join(" ").strip
0
+ parent.is_a?(self.class) # my parent is the same class as myself.
0
+ am_subcontext? ? parent.test_unit_class : parent
0
+ def create_test_from_should_hash(should)
0
+ test_name = ["test:", full_name, "should", "#{should[:name]}. "].flatten.join(' ').to_sym
0
+ if test_unit_class.instance_methods.include?(test_name.to_s)
0
+ puts "'#{test_name}' is already defined"
0
+ #raise ArgumentError, "'#{test_name}' is already defined"
0
+ test_unit_class.send(:define_method, test_name) do |*args|
0
+ context.run_all_setup_blocks(self)
0
+ should[:block].bind(self).call
0
+ context.run_all_teardown_blocks(self)
0
- # Creates a context block with the given name.
0
- def context(name, &context_block)
0
- saved_setups = @@setup_blocks.dup
0
- saved_teardowns = @@teardown_blocks.dup
0
- saved_contexts = @@context_names.dup
0
- @@setup_defined = false
0
- @@context_names << name
0
- context_block.bind(self).call
0
+ def run_all_setup_blocks(binding)
0
+ self.parent.run_all_setup_blocks(binding) if am_subcontext?
0
+ setup_block.bind(binding).call if setup_block
0
- @@context_names = saved_contexts
0
- @@setup_blocks = saved_setups
0
- @@teardown_blocks = saved_teardowns
0
+ def run_all_teardown_blocks(binding)
0
+ teardown_block.bind(binding).call if teardown_block
0
+ self.parent.run_all_teardown_blocks(binding) if am_subcontext?
0
- # Run before every should block in the current context.
0
- # If a setup block appears in a nested context, it will be run after the setup blocks
0
- # in the parent contexts.
0
- def setup(&setup_block)
0
- raise RuntimeError, "Either you have two setup blocks in one context, " +
0
- "or a setup block outside of a context. Both are equally bad."
0
+ def print_should_eventuallys
0
+ should_eventuallys.each do |should|
0
+ test_name = [full_name, "should", "#{should[:name]}. "].flatten.join(' ')
0
+ puts " * DEFERRED: " + test_name
0
- @@setup_defined = true
0
- @@setup_blocks << setup_block
0
+ subcontexts.each { |context| context.print_should_eventuallys }
0
- # Run after every should block in the current context.
0
- # If a teardown block appears in a nested context, it will be run before the teardown
0
- # blocks in the parent contexts.
0
- def teardown(&teardown_block)
0
- @@teardown_blocks << teardown_block
0
+ shoulds.each do |should|
0
+ create_test_from_should_hash(should)
0
+ subcontexts.each { |context| context.build }
0
+ print_should_eventuallys
0
- # Defines a specification that is not yet implemented.
0
- # Will be displayed as an 'X' when running tests, and failures will not be shown.
0
- # This is equivalent to:
0
- # should(name, {:unimplemented => true}, &block)
0
- def should_eventually(name, &block)
0
- should("eventually #{name}", {:unimplemented => true}, &block)
0
+ def method_missing(method, *args, &blk)
0
+ test_unit_class.send(method, *args, &blk)
0
module Test # :nodoc: all
0
- include Thoughtbot::Shoulda::ClassMethods
0
+ extend Thoughtbot::Shoulda
Comments
No one has commented yet.