Rspec: Expect VS Expect With Block - What's the Difference

Rspec: expect vs expect with block - what's the difference?

As has been mentioned:

expect(4).to eq(4)

This is specifically testing the value that you've sent in as the parameter to the method. When you're trying to test for raised errors when you do the same thing:

expect(raise "fail!").to raise_error

Your argument is evaluated immediately and that exception will be thrown and your test will blow up right there.

However, when you use a block (and this is basic ruby), the block contents isn't executed immediately - it's execution is determined by the method you're calling (in this case, the expect method handles when to execute your block):

expect{raise "fail!"}.to raise_error

We can look at an example method that might handle this behavior:

def expect(val=nil)
if block_given?
begin
yield
rescue
puts "Your block raised an error!"
end
else
puts "The value under test is #{val}"
end
end

You can see here that it's the expect method that is manually rescuing your error so that it can test whether or not errors are raised, etc. yield is a ruby method's way of executing whatever block was passed to the method.

Rspec expect( ) vs expect { }

The expect(...) syntax is used to expect that the return value of the statement in the parentheses matches a certain condition, like:

expect(result).to eq(3)
expect(list).not_to be_empty
expect(string).to match(/regexp/)
expect(1..10).to cover(3)

The expect { ... } runs the block and doesn't really care about the return value of the block, but instead about the side-effects of running the code in the block. Like that another value changes by running the block or that an exception is raised.

expect { api_request }.to raise_error(NotFoundError)
expect { object.action }.to change(object, :value).from(old).to(new)
expect { actual }.to output("some output").to_stdout

Find more examples in the RSpec docs

In your example

expect { array << 1 }.to change { array.count }.by(1)

because a side-effect of pushing a value into an array is that the count of elements in the array changes. But

expect(array << 1).to change { arr.count }.by(1)

does not work, because the return value of expect(array << 1) is [1, 2, 3, 1] and this syntax does not support the change matcher.

RSpec: difference between is_expect and expect

is_expected is defined simply as expect(subject) and is designed for when you are using rspec-expectations with its newer expect-based syntax.

https://relishapp.com/rspec/rspec-core/docs/subject/one-liner-syntax

As the object under test is house not subject, I assume that subject is not initialized, therefore set to defaul (described_class.new). is_expected calls expectation on this default subject.

To use is_expected, initialize subject:

describe House do
subject { House.new(...) } # or build :house if you use FactoryBot
# ...
end

RSpec allow/expect vs just expect/and_return

See the classic article Mocks Aren't Stubs. allow makes a stub while expect makes a mock. That is allow allows an object to return X instead of whatever it would return unstubbed, and expect is an allow plus an expectation of some state or event. When you write

allow(Foo).to receive(:bar).with(baz).and_return(foobar_result)

... you're telling the spec environment to modify Foo to return foobar_result when it receives :bar with baz. But when you write

expect(Foo).to receive(:bar).with(baz).and_return(foobar_result) 

... you're doing the same, plus telling the spec to fail unless Foo receives :bar with baz.

To see the difference, try both in examples where Foo does not receive :bar with baz.

Is there a difference between 'expect' and 'should' in RSpec?

Quoting from https://github.com/rspec/rspec-expectations/blob/master/Should.md:

From the beginning RSpec::Expectations provided should and
should_not methods to define expectations on any object. In version
2.11 expect method was introduced which is now the recommended way to define expectations on an object.

The next paragraph in that document describe the difference in implementation and why expect is now the recommended approach.

At this point, pretty much anything you can do with should and should_not can be done with expect. See http://rubydoc.info/gems/rspec-expectations/frames for details and expect usage.

As for your specific code, the reason the first example is failing is that the error is occurring before RSpec has a chance to get involved. You can still use should to check for the raising of an error, but you have to give RSpec a chance to work. The syntax is lambda {...}.should raise_error where ... is the code you're testing.

What's the difference between RSpec's subject and let? When should they be used or not?

Summary: RSpec's subject is a special variable that refers to the object being tested. Expectations can be set on it implicitly, which supports one-line examples. It is clear to the reader in some idiomatic cases, but is otherwise hard to understand and should be avoided. RSpec's let variables are just lazily instantiated (memoized) variables. They aren't as hard to follow as the subject, but can still lead to tangled tests so should be used with discretion.

The subject

How it works

The subject is the object being tested. RSpec has an explicit idea of the subject. It may or may not be defined. If it is, RSpec can call methods on it without referring to it explicitly.

By default, if the first argument to an outermost example group (describe or context block) is a class, RSpec creates an instance of that class and assigns it to the subject. For example, the following passes:

class A
end

describe A do
it "is instantiated by RSpec" do
expect(subject).to be_an(A)
end
end

You can define the subject yourself with subject:

describe "anonymous subject" do
subject { A.new }
it "has been instantiated" do
expect(subject).to be_an(A)
end
end

You can give the subject a name when you define it:

describe "named subject" do
subject(:a) { A.new }
it "has been instantiated" do
expect(a).to be_an(A)
end
end

Even if you name the subject, you can still refer to it anonymously:

describe "named subject" do
subject(:a) { A.new }
it "has been instantiated" do
expect(subject).to be_an(A)
end
end

You can define more than one named subject. The most recently defined named subject is the anonymous subject.

However the subject is defined,

  1. It's instantiated lazily. That is, the implicit instantiation of the described class or the execution of the block passed to subject doesn't happen until subject or the named subject is referred to in an example. If you want your explict subject to be instantiated eagerly (before an example in its group runs), say subject! instead of subject.

  2. Expectations can be set on it implicitly (without writing subject or the name of a named subject):

    describe A do
    it { is_expected.to be_an(A) }
    end

    The subject exists to support this one-line syntax.

When to use it

An implicit subject (inferred from the example group) is hard to understand because

  • It's instantiated behind the scenes.
  • Whether it's used implicitly (by calling is_expected without an explicit receiver) or explicitly (as subject), it gives the reader no information about the role or nature of the object on which the expectation is being called.
  • The one-liner example syntax doesn't have an example description (the string argument to it in the normal example syntax), so the only information the reader has about the purpose of the example is the expectation itself.

Therefore, it's only helpful to use an implicit subject when the context is likely to be well understood by all readers and there is really no need for an example description. The canonical case is testing ActiveRecord validations with shoulda matchers:

describe Article do
it { is_expected.to validate_presence_of(:title) }
end

An explict anonymous subject (defined with subject without a name) is a little better, because the reader can see how it's instantiated, but

  • it can still put the instantiation of the subject far from where it's used (e.g. at the top of an example group with many examples that use it), which is still hard to follow, and
  • it has the other problems that the implicit subject does.

A named subject provides an intention-revealing name, but the only reason to use a named subject instead of a let variable is if you want to use the anonymous subject some of the time, and we just explained why the anonymous subject is hard to understand.

So, legitimate uses of an explicit anonymous subject or a named subject are very rare.

let variables

How they work

let variables are just like named subjects except for two differences:

  • they're defined with let/let! instead of subject/subject!
  • they do not set the anonymous subject or allow expectations to be called on it implicitly.

When to use them

It's completely legitimate to use let to reduce duplication among examples. However, do so only when it doesn't sacrifice test clarity. The safest time to use let is when the let variable's purpose is completely clear from its name (so that the reader doesn't have to find the definition, which could be many lines away, to understand each example) and it is used in the same way in every example. If either of those things isn't true, consider defining the object in a plain old local variable or calling a factory method right in the example.

let! is risky, because it's not lazy. If someone adds an example to the example group that contains the let!, but the example doesn't need the let! variable,

  • that example will be hard to understand, because the reader will see the let! variable and wonder whether and how it affects the example
  • the example will be slower than it needs to be, because of the time taken to create the let! variablle

So use let!, if at all, only in small, simple example groups where it's less likely that future example writers will fall into that trap.

The single-expectation-per-example fetish

There is a common overuse of subjects or let variables that's worth discussing separately. Some people like to use them like this:

describe 'Calculator' do
describe '#calculate' do
subject { Calculator.calculate }
it { is_expected.to be >= 0 }
it { is_expected.to be <= 9 }
end
end

(This is a simple example of a method that returns a number for which we need two expectations, but this style can have many more examples/expectations if the method returns a more complicated value that needs many expectations and/or has many side effects that all need expectations.)

People do this because they've heard that one should have only one expectation per example (which is mixed up with the valid rule that one should only test one method call per example) or because they're in love with RSpec trickiness. Don't do it, whether with an anonymous or named subject or a let variable! This style has several problems:

  • The anonymous subject isn't the subject of the examples — the method is the subject. Writing the test this way screws up the language, making it harder to think about.
  • As always with one-line examples, there isn't any room to explain the meaning of the expectations.
  • The subject has to be constructed for each example, which is slow.

Instead, write a single example:

describe 'Calculator' do
describe '#calculate' do
it "returns a single-digit number" do
result = Calculator.calculate
expect(result).to be >= 0
expect(result).to be <= 9
end
end
end

When to use curly braces vs parenthesis in expect Rspec method?

In response to OP's comment, I've edited and completely rewritten my answer. I realize that my original answer was oversimplified, so much so that it could be considered incorrect.

Your question was actually addressed somewhat by this other StackOverflow question.

One poster, Peter Alfvin, makes a good point when he says:

As for rules, you pass a block or a Proc if you're trying to test
behavior (e.g. raising errors, changing some value). Otherwise, you
pass a "conventional" argument, in which case the value of that
argument is what is tested.

The reason you're encountering the phenomenon you're seeing has to do with the raising of errors. When you pass @parser.parse('adsadasdas') as an argument (use parentheses) to expect, you are essentially telling ruby:

  1. Evaluate @parser.parse('adsadasdas') first.
  2. Take the result and pass this to expect.
  3. expect should see if this result matches my expectation (that is, that Errno:ENOENT will be raised).

But, what happens is: when ruby evaluates @parser.parse('adsadasdas'), an error is raised right then and there. Ruby doesn't even get a chance to pass the result on to expect. (For all we care, you could have passed @parser.parse('adsadasdas') as an argument to any function... like multiply() or capitalize()) The error is raised, and expect never even gets a chance to do its work.

But when you pass @parser.parse('adsadasdas') as a proc (a code block) to expect using curly braces, what you are telling ruby is this:

  1. expect, get ready to do some work.
  2. expect, I would like you to keep track of what happens as we evaluate @parser.parse('adsadasdas').
  3. Ok, expect, did the code block that was just evaluated raise a Errno:ENOENT error? I was expecting that it would.

When you pass a code block to expect, you are telling expect that you want it to examine the resulting behavior, the changes, made by your code block's execution, and then to let you know if it meets up to the expectations that you provide it.

When you pass an argument to expect, you are telling ruby to evaluate that argument to come to some value before expect even gets involved, and then you are passing that value to expect to see if it meets up to some expectation.

Rspec tests, difference between should vs expect

Instead of

expect { new_permission.valid? }.to be_false

use

expect(new_permission.valid?).to be_false

the block form can used for Expecting and Yielding. You can read more about the difference between should and expect in the docs.



Related Topics



Leave a reply



Submit