Class: Scope::TestCase

Inherits:
MiniTest::Unit::TestCase
  • Object
show all
Defined in:
lib/scope.rb

Overview

A test case class which provides nested contexts. Subclasses will have the “setup”, “teardown”, and “should” methods available as class methods.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.context(name, &block) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/scope.rb', line 34

def self.context(name, &block)
  context_focused = false
  if @focus_enabled && @focus_next_test_or_context
    @focus_next_test_or_context = false
    @inside_focused_context = true
    context_focused = true
  end
  parent = @contexts.last
  new_context = Context.new(name, parent)
  parent.tests_and_subcontexts << new_context
  @contexts << new_context
  block.call
  @contexts.pop
  @inside_focused_context = false if context_focused
end

.context_for_testObject

A map of test name => Context.



8
# File 'lib/scope.rb', line 8

def self.context_for_test() @context_for_test end

.focusObject

“Focuses” the next test or context that’s defined after this method is called, ensuring that only that test/context is run.



75
76
77
78
79
80
81
82
83
# File 'lib/scope.rb', line 75

def self.focus
  # Since we're focusing only the next test/context, remove any tests which were already defined.
  context_for_test.values.uniq.each do |context|
    context.tests_and_subcontexts.reject! { |test| test.is_a?(String) }
  end
  @focus_enabled = true
  @focus_next_test_or_context = true
  @inside_focused_context = false
end

.inherited(subclass) ⇒ Object



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/scope.rb', line 10

def self.inherited(subclass)
  # Calling Unit::TestCase's inherited() method is important, as that's how it registers test suites.
  super

  subclass.instance_eval do
    # Pretend the whole test is wrapped in a context, so we can always code as if tests are in contexts.
    @contexts = [Context.new("")]
    @context_for_test = {}

    # The tests defined in this test case. MiniTest::Unit::TestCase sorts these methods randomly or
    # alphabetically. Let's run them in the order they were defined, as that's least surprising.
    def test_methods()
      tests = []
      stack = [@contexts.first]
      until stack.empty? do
        item = stack.pop
        stack += item.tests_and_subcontexts.reverse if item.is_a?(Context)
        tests << item if item.is_a?(String)
      end
      tests
    end
  end
end

.setup(&block) ⇒ Object



65
# File 'lib/scope.rb', line 65

def self.setup(&block) @contexts.last.setup= block end

.setup_once(&block) ⇒ Object

setup_once blocks are run just once for a context, and not on a per-test basis. They are useful for integration tests with costly setup.



70
# File 'lib/scope.rb', line 70

def self.setup_once(&block) @contexts.last.setup_once = block end

.should(name, &block) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/scope.rb', line 50

def self.should(name, &block)
  # When focus_enabled is true, we'll only be running the next should() block that gets defined.
  if @focus_enabled
    return unless @focus_next_test_or_context || @inside_focused_context
    @focus_next_test_or_context &&= false
  end

  context_name = @contexts[1..-1].map { |context| context.name }.join(" ")
  context_name += " " unless context_name.empty?
  test_method_name = "#{context_name}should #{name}"
  define_method test_method_name, block
  @contexts.last.tests_and_subcontexts << test_method_name
  @context_for_test[test_method_name] = @contexts.last
end

.teardown(&block) ⇒ Object



66
# File 'lib/scope.rb', line 66

def self.teardown(&block) @contexts.last.teardown = block end

.teardown_once(&block) ⇒ Object



71
# File 'lib/scope.rb', line 71

def self.teardown_once(&block) @contexts.last.teardown_once = block end

Instance Method Details

#run(test_runner) ⇒ Object

run() is called by the MiniTest framework. This TestCase class is instantiated once per test method defined, and then run() is called on each test case instance.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/scope.rb', line 87

def run(test_runner)
  test_name = self.__name__
  context = self.class.context_for_test[test_name]
  result = nil
  begin
    begin
      # Prevent super from calling the minitest setup after the Scope setup blocks have already run. The
      # motivation is that RR puts hooks into setup to reset stubs and mocks and this needs to happen
      # *before* the test setup occurs.
      self.setup
      old_setup = self.method(:setup)
      def self.setup; end
      # Unit::TestCase's implementation of run() invokes the test method with exception handling.
      context.run_setup_and_teardown(self, test_name) { result = super(test_runner) }
    ensure
      def self.setup; old_setup; end
    end
  rescue *MiniTest::Unit::TestCase::PASSTHROUGH_EXCEPTIONS
    raise
  rescue Exception => error
    result = test_runner.puke(self.class, self.__name__, error)
  end
  result
end