summaryrefslogtreecommitdiff
path: root/spec/mspec/lib/mspec/matchers/raise_error.rb
blob: 0ee8953519da49741eb1e53ccefe804b5944f195 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
class RaiseErrorMatcher
  attr_writer :block

  def initialize(exception, message, &block)
    @exception = exception
    @message = message
    @block = block
    @actual = nil
  end

  # This #matches? method is unusual because it doesn't always return a boolean but instead
  # re-raises the original exception if proc.call raises an exception and #matching_exception? is false.
  # The reasoning is the original exception class matters and we don't want to change it by raising another exception,
  # so instead we attach the #failure_message and extract it in ExceptionState#message.
  def matches?(proc)
    @result = proc.call
    return false
  rescue Exception => actual
    @actual = actual

    if matching_exception?(actual)
      # The block has its own expectations and will throw an exception if it fails
      @block[actual] if @block
      return true
    else
      actual.instance_variable_set(:@mspec_raise_error_message, failure_message)
      raise actual
    end
  end

  def matching_class?(exc)
    @exception === exc
  end

  def matching_message?(exc)
    case @message
    when String
      @message == exc.message
    when Regexp
      @message =~ exc.message
    else
      true
    end
  end

  def matching_exception?(exc)
    matching_class?(exc) and matching_message?(exc)
  end

  def exception_class_and_message(exception_class, message)
    if message
      "#{exception_class} (#{message})"
    else
      "#{exception_class}"
    end
  end

  def format_expected_exception
    exception_class_and_message(@exception, @message)
  end

  def format_exception(exception)
    exception_class_and_message(exception.class, exception.message)
  end

  def failure_message
    message = ["Expected #{format_expected_exception}"]

    if @actual
      message << "but got: #{format_exception(@actual)}"
    else
      message << "but no exception was raised (#{MSpec.format(@result)} was returned)"
    end

    message
  end

  def negative_failure_message
    message = ["Expected to not get #{format_expected_exception}", ""]
    unless @actual.class == @exception
      message[1] = "but got: #{format_exception(@actual)}"
    end
    message
  end
end

module MSpecMatchers
  private def raise_error(exception = Exception, message = nil, &block)
    RaiseErrorMatcher.new(exception, message, &block)
  end
end