We’ve discussed how methods such as
context are used to set
up a structure for our tests, and how
it adds an actual test (“example”) to
What about the implementation of the test, though?
Let’s look at our code again:
user = User.new("Francisca", "2000-01-01") expect(user.born_in_leap_year?).to eq true
expect do, exactly? And what’s the deal with
These also are methods that RSpec defines for us, so we can describe our
expectations in a readable way. That is to say, these methods are RSpec’s equivalent to
assert and friends) in Minitest.
Some people feel they read much better than Minitest’s assertion methods
assert_equal(one, other)), because they express more clearly what’s the
actual value, how to compare, and what’s the expected value. Almost like
an English sentence.
expect returns an object that responds to the method
to expects to be passed an object that is a so-called matcher. It will
then call the matcher to see if it … well, matches.
Remember that in Ruby you can omit parentheses when calling a method (as long as this doesn’t make your code ambiguous). So we could just as well add them:
Or we could make more visible what the matcher is (if we wanted):
match = eq(true) expect(user.born_in_leap_year?).to(match)
eq returns an RSpec matcher that simply tests if the object passed
expect is equal to the object passed to
eq. This may sound more
complicated than it is.
If you have a look at the documentation there are lots and lots of matchers pre-defined, and RSpec makes it easy to define your own matchers, too (we’ll get to that later).
expect(10).to be > 5 expect([1, 2, 3]).to include 1 expect("Ruby Monstas").to start_with "Ruby"
No matter exactly how the code that implements these methods
and, for example,
start_with works: the purpose is to allow for
code that kinda reads like an English sentence. You’ll get used to these pretty soon, once
you’ve started writing some RSpec tests.
Just try to remember (or look it up here) that, essentially, you start with
expect(whatever_thing_to_test).to, and then you find a matcher that works.
eq always is a good start. So you end up with:
expect(whatever_thing_to_test).to eq whatever_you_expect
expect(your_object.some_method_to_test).to eq "the concrete value that you expect to be returned"
Does that make sense?
Cool. Let’s have a look at another badass feature RSpec comes with.
RSpec also allows you to use matchers that depend on the methods defined on the object passed.
Here’s a simple example:
be_nil expects the method
nil? to be defined on the object
under test, i.e. the object
nil. As a matter of fact, the method
defined on every object in Ruby. And in our case,
nil.nil? returns true, of
course, so the test would pass.
This test, however, would not pass:
true.nil? returns false.
User instances respond to the method
RSpec allows us to use a matcher
user = User.new("Francisca", "2000-01-01") expect(user).to be_born_in_leap_year
RSpec sees that we’re calling the method
be_born_in_leap_year and it figures
“Ok, that must mean that the call
user.born_in_leap_year? must return true.”
Such “magic” methods are another meta-programming technique that RSpec leverages here. Usually they’re pretty debatable, and often not a great choice. However, in this case, they allow adding this very cool feature to RSpec.
What if we want to specify that a user is not born in a leap year, though? In other words, if we want to negate our expectation?
RSpec allows us to simply invert a matcher by using the method
expect(user).to be_born_in_leap_year # vs expect(user).not_to be_born_in_leap_year
This works for all other matchers, too, of course — and
to_not can be used interchangeably with
expect(1).to eq 1 expect(2).not_to eq 1 expect(true).to be true expect(false).not_to be true expect([1, 2, 3]).to include 1 expect([1, 2, 3]).to_not include 9 expect("Ruby Monstas").to start_with "Ruby" expect("Ruby Monstas").to_not start_with "Java"
And so on.
If all this matcher business seems too complicated to you, don’t worry. You’re not the first programmer feeling that way.
For now you can always fall back on comparing actual and expected values like so:
user = User.new("Francisca", "2000-01-01") actual = user.name expected = "Francisca" expect(actual).to eq expected
user = User.new("Francisca", "2000-01-01") actual = user.born_in_leap_year? expected = true expect(actual).to eq expected
That’s some more code to type, but doing that sometimes helps RSpec beginners to understand what’s going on better.