Tip:
Highlight text to annotate it
X
In retrospect, I realized that it would have been more fun to
call this section Great Expectations in keeping with the theme
of classic novels, but I didn't, so whatever. Expectations it
is. Let's just take a quick recap of where we've been and where
we're going here in terms of our outside in development. We're
trying to write expectations that drive the development of our
code. We already showed a simple example of that in the
controller method. We discovered that even when we have to
collaborate with other methods and other classes, even if those
methods don't technically exist, we can set up expectations
around them and essentially create a stub that is going to be
called and satisfy the property of my controller code did what
it should do. The idea, which we're now going to see other
examples of, is that we're trying to break a dependency between
the thing that we're testing and other things that it depends on
or works with, generally called it's collaborators. The key
weapon in our arsenal to fight this is a seam, which is a way
that you can affect the app's behavior without editing the
source code of the app. If we continue our example, we said, "
What should the controller method do when it receives the search
form?" We said, " It should call a method that we haven't
written yet that will call TMDb and search for the movie." The
good news is we got that to pass. Notice that we got it to pass
by doing minimal work. We didn't actually write the method, we
just verified that if the method existed, the controller action
would have called it. Let's move on to the next bit, which is
less trivial. It says, " Assuming that a match is found, it
should select a new view called Search Results, which we haven't
created yet, in order to display the matches that it found."
What would do with that? We're going to keep concentrating on
the happy path here. Step three is a sad path step, but we're
still working on the happy path. It should select the Search
Results view to display the match. What is that actually mean in
practice? It really means two separate things. One thing is, it
should decide that the correct template to use for rendering is
the search results template. Remember in the last segment, I
created an empty view for the search results. It doesn't have
anything in it. It was just an empty Haml file so that there
would be some view that could be rendered at the end of that
action. You might think in this case, the whole idea by the end
of a controller action, you're going to look for a view and it
has a certain name and stuff, isn't that just a built- in part
of Rails? Why would you write a test case for something that is
supposed to just be part of the framework? Indeed that will be
correct, but you also remember that, even in our simple Rotten
Potatoes example from a couple of lectures ago, sometimes the
action doesn't finish by rendering its own views. Sometimes it
finishes by redirecting you to some place else. It's easy to
imagine an action that if it succeeds, it renders a certain
view, but if it fails or depending on what else might go wrong,
it might render a different view or it might redirect to another
action. The idea that part of what you're testing on a
controller method is, does it end up by doing the right thing?
That's a legitimate thing to want to test. In this case, because
we're only talking about the happy path right now, the happy
path is it does whatever Rails would normally do, which is to
look for a view. We don't need to belabor the point that it's
going to call or it's going to select the correct template,
because we'd just be testing the framework. The reason to
understand how these kinds of test will work is, in your own
apps, it's usually much more complicated. The controller action,
depending on what happens during its execution, may choose one
of a couple of different templates to render. The second thing
we're saying is that assuming that that template does exist,
then a list of what the matching movies were should be available
to that template so that it can display them to the user in some
way. That's the whole goal of this, was to try to find matching
movies in TMDb. This is really two different specs and we could
take the same approach we did before. Each spec can get its own
it block and we're going to see what expectation constructs we
can use. Just like where you should receive to express the
concept that it should call a certain method in another class,
we're going to now use a new expectation construct called
should, which is basically ... it's based on a concept of a
matcher. I'm going to have an object and I'm going to establish
that some property should be true about that object. There's a
number of built- in properties that I can check, and again, if
you looked at the homework that comes with its own test cases,
simple properties are like the object should equal something. If
the object is a string, you can compare it to another string. If
the object is a number, you can see if it's greater than or less
than some other number. Those are kinds of simple examples of an
object having a property, but as we'll see, objects can have
many other interesting properties as well, and you can define
your own custom matchers. Just like as you're writing your
Cucumber tests, you define a domain language that make sense for
your app. As you'll see when you're writing your functional and
unit test in RSpec, you can make your own matchers that
correspond to the models and controllers that are in your app,
and overall that makes your test cases less cumbersome to write.
Let's take a look at the example of should and should not. The
idea is that a matcher applies a certain test to the receiver
and there's a number of examples that we can look, some of which
have already appeared in the homework. A simple one is, we have
this object count. We could say, " Count should equal five."
What is should actually doing? It's looking at this object and
it's applying the result of this block to the object. It's
basically sending count to the equals message with five as an
argument. We also saw an example early on about if we use
syntactic sugar and leaving out parens and desugaring things
like this call to less than, we can write, " Five should be less
than seven which really is punctuated this way." Five should be
odd, how does that work? One of the conventions in RSpec is that
if the matching condition begins with BE_, it will look for a
method name odd question mark that can be called on the original
receiver. Basically in this case, it's calling the odd question
mark method on five and expecting that the result of that will
be true. This is another... a nice use of method missing because
it allows you to write test cases that express, in quasi-
stilted program or English, what you're actually expecting to
happen. For those of you around the world, feel free to use
quasi- stilted program or national language of your choice. Mine
is English. We can also say things like, " Result should
include." Who handles the call to include? As we saw, include is
one of the many methods given to you in the enumerable module. A
reasonable assumption is that the programmer expects that result
is something that mixes in enumerable or else defines include on
itself. Again, it's going to call include question mark on
whatever the receiver is. I felt in the spirit of the government
shutdown, I had to include an example like this. This could be
syntactically legal. If, in fact, my application involved the
concept of modeling relationships and cooperation, I could
define my own custom RSpec matcher that can be applied to a
receiver and that can take an argument and do whatever testing
it needs to do to check my definition of cooperate with. In that
case, the matcher will be called ... by the way, it will
probably fail in this case. I'm so sorry to be so cynical, but
it is what it is. The idea is that rather than expressing some
underlying condition that would be tested by this, I can create
a cooperates with matcher that encapsulates all that logic. Why
would I do that? Because now the intention of the test is self-
describing. I can just look at the code that's in the test case
and infer what the developer meant to do, what's the thing that
they're trying to establish is true about these objects? Some of
these simpler forms up here, if you look through the homework
specs, you'll see examples of all of these. There's also a
should not, which inverts the sense of the test. As we'll see,
there's also additional matchers that RSpec adds where you're
using it with Rail specifically. There's a few basic ones like
these top ones up here. These are just part of the basic RSpec
core, but there are also matchers, one of which we're going to
see in a moment, that are there just to make life easier if
you're doing a Rails development specifically. The first one of
those we're going to see is this one. Result should render
template. What does this mean? It means that if you call a
controller action, the result of calling that controller action,
it's saying it should find and select a template called search
TMDb and attempt to render that template. It doesn't actually
matter if we have created the template or not. Again, the only
thing we're concerned about here is whether the controller is
doing its job, so we only care about whether the controller
decides to make the right choice. Whether the rendering happens
correctly, that's up to the view subsystem. That's outside the
scope of this particular test. We're just checking that the
controller action does what it's supposed to do. After we do our
post search TMDb, we can look at the response method, which is
another nice RSpec add on that comes when you install RSpec for
Rails, which returns the controller's response object. The
response object is the thing that encapsulates what is it that I
am about to do to deliver the response to this request? One of
the things that you can call on the response object is render
template. That's what the render template matcher is intended to
use. Now, we have expanded. Here is the first spec that we
developed in the previous lecture segment, and the only thing
we're checking here is that it calls the model method. Here,
we're checking an additional thing, which is that it should
select the Search Results template for rendering. What am I
doing here? What is this stub thing? This is confusing. Up here,
I said movie should receive find in TMDb. I'm asserting that it
would be an error if that method did not get called. Down here,
stub is actually just a way of saying, " If somebody tries to
call this method, just don't do anything, return nil, don't
check the arguments. Just don't give an error. Just ignore it.
Pretend it never happened." Why is it that in this test, it's
okay for me to do that, whereas in the previous test, I was
adamant that if it's not done, that that would be an error?
Again, I'm exaggerating a little bit to prove a pedagogical
point here, which is that each test case, each spec which is an
it block, should be testing exactly one thing. The idea that
we're testing that it should call the model method, we already
did that. This test is responsible for that. In this test, we're
checking that it selects the search results template for
rendering. Even though we know in scare quotes that it really
does need to call find in TMDb, technically, this test doesn't
care about that fact. This test only cares about this line here,
which is that once we get a successful response, the response
should indicate that we're going to render the right template.
Again, I'm nitpicking about what we have to check versus what we
don't have to check in each individual test, but the underlying
concept as an important one, which is you want to focus each
test on one behavior. Tests are cheap. You're going to have
thousands of them even for a simple app, so don't combine
multiple checks for different kinds of behaviors into the same
test. That's what it looks like. At this case, the view at least
has to exist as an empty file. It doesn't have to do anything in
it. Because the view has to at least be ... we have to have a
place holder for it which we already created, that's the reason
that we think of a controller test as a functional test. Because
when you think about it, when we did that simulated post from
within the test, that's exercising the route subsystem because
it's got to figure out how to map the route to an action. It's
exercising the subsystem that takes the data and makes it
available in params, which arguably is part of Rails but still,
it has to get exercised. Because the controller claims to call a
model, it exercises enough of the controller method to make sure
that something else is going to get called and it exercises the
controller's ability to figure out which view is going to be
rendered. It's actually not just a unit test. It actually
touches more than just the lines of code in the controller
action. We think of it as a functional test, a test that tests a
couple of modules, goes across a couple of boundaries, but is
still generally quite limited in scope as to what is being
tested. This lets us add another thing that we know how to do in
test. We already knew should receive. Now, we have this idea
that an object should match some condition and there are some
basic conditions that are built in to Rspec, but there's also
some Rail specific ones, like the idea that there is a response
object on which you can match conditions like render template.
That allows us to get to the point where we can ask another
question, after which I'll do a fun thing. Which of these looks
like it might be a not valid use of should or should not? We
could say something like, " Result should not be empty." We
could say, " Five should be space strip result. Result should
not match this regular expression," or is it the case that these
are all reasonable, these are all valid uses of calling should
or should not? Should and should not are interchangeable for the
purposes of this question.