TL;DR: use expect(exp)
to specify something about the value of exp
and use expect { exp }
to specify a side effect that occurs when exp
is executed.
Let's unpack this a bit. Most of RSpec's matchers are value matchers. They match (or not) against any ruby object. In contrast, a handful of RSpec's matchers can only be matched against a block, because they have to observe the block while it's running in order to operate properly. These matchers concern side effects that take place (or not) while the block executes. The matcher would have no way to tell if the named side effect had occurred unless it is passed a block to execute. Let's consider the built-in block matchers (as of RSpec 3.1) one-by-one:
raise_error
Consider that one can return an exception from a method, and that is different than raising the exception. Raising an exception is a side effect, and can only be observed by the matcher by it executing the block with an appropriate rescue
clause. Thus, this matcher must receive a block to work properly.
throw_symbol
Throwing symbols is similar to raising errors -- it causes a stack jump and is a side effect that can only be observed by running a block inside an appropriate catch
block.
change
Mutation to state is a side effect. The matcher can only tell if there was a change to some state by checking the state before hand, running the block, then checking the state after.
output
I/O is a side effect. For the output
matcher to work, it has to replace the appropriate stream ($stdout
or $stderr
) with a new StringIO, execute the block, restore the stream to its original value, and then check the contents of the
StringIO`.
yield_control
/yield_with_args
/yield_with_no_args
/yield_with_successive_args
These matchers are a bit different. Yielding isn't really a side effect (it's really just syntactic sugar for calling another function provided by the caller), but yielding can't be observed by looking at the return value of the expression. For the yield matchers to work, they provide a probe
object that you pass on to the method-under-test as a block using the &probe
syntax:
expect { |probe| [1, 2, 3].each(&probe) }.to yield_with_successive_args(1, 2, 3)
What do all these matchers have in common? None of them can work on simple ruby values. Instead, they all have to wrap a block in an appropriate context (i.e. rescuing, catching or checking before/after values).
Note that in RSpec 3, we added some logic to provide users clear errors when they use the wrong expect
form with a given matcher. However, in the specific case of expect(do_something).to raise_error
, there's nothing we can do to provide you a clear explanation there -- if do_something
raises an error (as you expect it to...), then the error is raised before ruby evaluates the to
argument (the raise_error
matcher) so RSpec has no way to check with the matcher to see if supports value or block expectations.