We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

Understanding Rails test types in RSpec

00:00

Formal Metadata

Title
Understanding Rails test types in RSpec
Title of Series
Part Number
20
Number of Parts
94
Author
License
CC Attribution - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Identifiers
Publisher
Release Date
Language

Content Metadata

Subject Area
Genre
Abstract
Getting started with testing Rails applications can be a frought process. There are a range of different test types that one can write. It's often not clear which type one wants. Without care your tests can begin testing the same behaviour. This is problematic. In this talk we'll cover the most common types of test you'll encounter in your Rails applications: feature, controller and model. We'll also talk about ways you can design your tests to ensure your suite is robust to changes in your system. If you'd love to learn more about RSpec, Rails, and testing this talk will be great for you.
Process (computing)Hand fanCode refactoringTwitterQuicksortOffice suiteSoftware frameworkBasis <Mathematik>DemosceneStatistical hypothesis testingEvent horizonProfil (magazine)Multiplication signCore dumpInformation technology consultingComputer animation
Cartesian coordinate systemSoftware developerMathematicsContent (media)Statistical hypothesis testingCodeSoftwareStatistical hypothesis testingVideo gameAutomationStatement (computer science)Computer animation
MathematicsCodeMultiplication signCartesian coordinate systemReplication (computing)QuicksortGraph (mathematics)Computer animation
Projective planeWordArithmetic meanNumerical analysisComputer animation
FrequencyWritingObject (grammar)Network topologyStatistical hypothesis testingCartesian coordinate systemPhysical systemStatistical hypothesis testingForm (programming)MathematicsLie groupCodeComputer animation
Physical systemForm (programming)MereologyVapor barrierCodeStatistical hypothesis testingCartesian coordinate systemDifferent (Kate Ryan album)Statistical hypothesis testingOrder (biology)Computer animation
Statistical hypothesis testingCartesian coordinate systemStatistical hypothesis testingGame controllerPattern languageDatabaseMereologyWeb browserReplication (computing)Multiplication signCodeLevel (video gaming)TheoryProgramming paradigmState of matterTelecommunicationGroup actionQuicksortFormal verificationData structureProcess modelingEndliche ModelltheorieBlack boxTerm (mathematics)Mobile appComputer animation
Statistical hypothesis testingStatistical hypothesis testingState of matterCartesian coordinate systemContext awarenessHome pageElectronic mailing listDisk read-and-write headMathematicsWeb browserTable (information)DatabaseWeb pagePoint (geometry)EmailGreatest elementSubject indexingOrder (biology)Expected valueTerm (mathematics)Formal languageLine (geometry)Game controllerHash functionEndliche ModelltheorieGroup actionLevel (video gaming)CASE <Informatik>FlagCodeRadical (chemistry)Video gameContent (media)Element (mathematics)HoaxWordModule (mathematics)Network topologyWritingNumerical analysisDeclarative programmingMereologyCycle (graph theory)Single-precision floating-point formatCodeComplete metric spaceBlock (periodic table)Message passingResultantComputer animation
Expected valueOvalSocial classGame controllerVideo gameTable (information)Right angleRow (database)Network topologySystem callOptical disc driveStatistical hypothesis testingNatural numberPoint (geometry)Copula (linguistics)InformationStatistical hypothesis testingStudent's t-testForcing (mathematics)Subject indexingTask (computing)SoftwareEndliche ModelltheorieSimilarity (geometry)Electronic mailing listCycle (graph theory)Group actionDefault (computer science)Symbol tableCartesian coordinate systemComplete metric spaceCodeMathematicsImplementationInstance (computer science)Variable (mathematics)Different (Kate Ryan album)AbstractionView (database)Level (video gaming)Order (biology)DatabaseResultantProcess modelingGraph coloringDescriptive statisticsLine (geometry)Interface (computing)Message passingMobile appComputer clusterArtificial lifePointer (computer programming)WritingOnline helpComputer animation
Physical lawStatistical hypothesis testingCartesian coordinate systemComputing platformBitMultiplication signResultantComputer animation
Roundness (object)Multiplication signCartesian coordinate systemOpen sourceStatistical hypothesis testingCore dumpQuicksortChainNumerical analysisRule of inferenceMeeting/Interview
Multiplication signComputer animation
Multiplication signNumerical analysisComputer animation
GodCASE <Informatik>Computer animation
Data recoveryComputer animation
Transcript: English(auto-generated)
To introduce myself, my name is Sam Phippen, I have fans, I'm Sam Phippen on Twitter,
I'm Sam Phippen on GitHub, you can have a look at my profiles there if you're interested in finding out more about me. If you do look at my GitHub profile, you'll find that I spend most of my time on GitHub committing as a member of the RSpec core team.
And that's really why I'm here today, because I think it's really important that RSpec is well represented in community events like this, and everyone who uses the framework has the ability to talk to someone who works on it on a regular basis and can ask questions, make comments and that sort of thing. So if you see me walking around the conference at all throughout the week, please just come
and grab me and we can talk about RSpec. I'm going to be running office hours at the Heroku community booth on Wednesday during the happy hour, so you can come and find me there as well. I work for a company called Fun and Plausible Solutions, we're a consulting agency that
specializes in data science and refactoring slash testing big complicated Rails apps. If you're at all interested in working together on that sort of thing, please come and have a chat with me afterwards. I prefix all of my talks with the following statement, which is I think it's more interesting
for us to have a discussion than me to necessarily get to the end of my pre-planned content. So I would encourage you to pop your hand up and ask me questions as I'm talking. If you're not quite following what I'm saying or I'm explaining something badly,
it can be really helpful to ask a question that both enables me to clarify my thoughts and also helps everyone understand. If you have a question, it's very likely that somebody else in the room will as well. And I wanted to start this talk by providing one of many possible motivations for why
we actually write automated tests for our applications. Like a lot of people who are beginner developers have been taught that you must test your applications, but oftentimes the reasons that are given are flimsy or easy to argue with. And I'm not saying mine isn't either, but here's one idea that I have that
we can sort of talk about. So my idea is that comments lie. It's not that when you're writing your application and you write a comment that you're doing a disservice to the other developers on your team. You're trying to help them understand the software that you're writing.
Unfortunately, during like high pressure, sudden deadlines and changes happening with the code really quickly, it can be very easy to leave comments alone as they were as we make changes to our application. The purpose of code in your application changes over time. And when that purpose changes, it may be that the label that you've put on it no longer
applies. When your brain is reading a very hairy piece of code, you're sort of scrambling to find any anchor onto which you can grab to help you understand what's going on. And there's nothing worse than a confusing or poorly written comment which causes you
to load assumptions into your brain that may then be dashed. Comments that contain short numbers of words that don't necessarily have all that much meaning can be incredibly frustrating. When you come to a new project, the words that the people that maintain the project
are using may be foreign to you, and it may cause you to fail to understand what's going on. Tests don't lie. Tests provide an objective truth about the system that they're executing on. Tests are literally code, and when you run them, they're poking at a system and providing
you answers about what that system does. That's not to say that you can't write bad tests or tests that are just as confusing as bad comments, but they don't lie. Tests allow you to roll up knowledge that you have about your application into something
that is literally executable and communicate with your team via the form of executable test code. Let's talk about Rails. There were some pretty big announcements about changes in Rails 5 that were going to be happening this morning, and unfortunately, I haven't been able to update everything
that I was going to say about Rails for the new Rails 5 changes, but suffice it to say that I still think it's true that Rails raises the barrier to testing more than it necessarily could in order to help you write your applications. Specifically, one of the reasons why I find beginners struggle with testing Rails
applications is that Rails tests can take many different forms, and those tests have different purposes for testing different parts of the system, and it can be difficult to hold all of those test types in your brain and understand what's going on.
Let's talk about the Rails test types that I think people most commonly test their Rails applications with. There are three that we're going to be talking about today. Model tests, controller tests, and feature tests. These three groups of tests allow you to cover most of the behavior of your application
when you begin developing it, and I certainly find that when I'm working on Rails applications, I reach for one of these three test types before I reach for any of the others. Today we're going to be talking about something called outside-in testing, and this is an idiomatic pattern for writing tests in your application that helps you structure
the tests that you write and also better understand the code that you're going to be testing as you write it. The idea with outside-in testing is that you start by writing a test at the very highest level, describing your application in terms that a user or a browser or an HTTP
client might. Your application literally gets treated as a black box. You don't look inside it, you don't look inside the database, you don't look inside any part of your application. You exercise your Rails app as a user might, and you assert on the behavior that a user
would see. Once you've got outside tests that you're happy with, the outside-in paradigm then encourages you to write tests that are literally inside your application, testing individual controllers or individual models. The idea behind this is that you end up with a sort of two-layered test structure,
one that describes your application in a very user-centric way, and the other in a very code-centric way. When those combine, you get really useful behavior verification, a lot of communication about what your application does, and also it really helps you design how your application works.
To explain the theory of outside-in testing, I'm going to do about 20 minutes of live understanding of what the Rails test types are, and then I'll finish up and take some questions. To switch over to my terminal here, we should be all good, and if I run a browser, I'm
just going to show you a Rails application that I've built here that's going to be the application that we're going to be testing today. This is your basic 101 Rails to-do list application.
It has a single model and a single controller. Todos are very simple models. They have a flag that says whether or not they're done, and a text note which allows us to describe what we actually want to do. I can set a to-do to done by using the Rails admin console, and when I get back
to my application, you can see that that change has been made. So let's imagine that our customer has come to us and asked us to separate the to-do lists out into two different lists, one for to-dos that are complete and one for to-dos that are incomplete, and we've decided that we're going to make this change in an extremely
test-driven way. Switching over to my terminal here, you can see that when I run my tests, I have a single failing feature test, and the feature test is saying I expected to find this header for the completed to-dos which I didn't find.
To go into the test file, let's actually talk about what this test is doing. So this is a Rails feature test. Feature tests are most commonly used to do the outside part of the outside-in testing cycle I was talking about. This test is literally only manipulating a web browser and expecting on the results
that that web browser sees. It doesn't know about the model layer or the controller layer or anything in the application. To write an RSpec feature test, you add this declaration when you do the describe that says type feature, and what that does is it causes RSpec to include a number of
methods into your test that allow you to actually manipulate the fake browser that RSpec uses to drive Rails applications. The rest of this test is structured in a fairly interesting way, and the reason is that the test is not describing what it's doing in terms of simple RSpec methods.
Instead, I've got two methods here called visit home page, and I should see the completed todos header, which are literally just methods that call straight in to RSpec code. The reason that I've done this is that I really like to write feature tests in such
a way that you could imagine a user describing your application in terms of the words that you have in your feature test, and you can see here that we say visit home page, and I should see the completed todos header. These methods are actually implemented inside this module called todo steps, and to just
go into that, we can see that todo steps is just a collection of methods that actually call into RSpec's fake browser and RSpec's expectations to actually write tests. Visit home page just says visit slash, as you might expect, and then I should see
the completed todos header says expect page to have content completed todos. I could have just written both of those lines of code in this test, but there are a couple of advantages gained by not doing that. Firstly, it means that I can reuse this behavior in other tests, and secondly, as I mentioned earlier, it means that the
test is described in terms of user language. In order to make this test pass, we need to satisfy this expectation that the page has content completed todos, and to do that, all I need to do is go into the index.html.erb page and add a header at the bottom which
says completed todos. At this point, if I run my test, it should pass, and it does. Going back to the actual Rails application in the browser, we can see that that header has now been added. That's not a very interesting behavior change, but what we've
done here is we've written a first test that gives us hooks into actually being able to continue to write more tests for the application. Now what we're going to do is we're going to write a test which creates a completed todo and expects it to be in the separate list. To do that, I'm going to go back into my test, and I'm
going to say, RSpec context blocks just allow you to describe different states of your application, and in this case, the state is with a completed todo. In order to build my completed todo, before my test, I'm going to visit the todo form, and I'm
going to create a complete todo with note by milk, and that means that before each test inside this context block, those actions will be run, and the todo will be created. The behavior that I want is it shows the completed todo in the completed todos list,
and then all I need to do now is go back to the home page to make sure that I'm seeing that table, and then say, I should see a completed todo with note by milk. At this point, we have a test for our next piece of behavior that we want, which is actually listing out all of the todos in the application. When I run this test, it
should fail, and the reason that it fails is it says it can't find CSS hash complete todos. What this I should see a completed todo with note is doing is it's looking for an element on the page that has the HTML ID completed todos, and then looking
at the content within that table. All I need to do to make this test pass is copy the table from above, paste it down, give it the ID, typing is hard, and running this test, it should now pass, and it does. Let's look at the todo spec that we've
written so far. We've added a header, and we've got this separate table that we've created with an ID that lists our complete todos, but we're not quite done yet. The reason for that is that writing tests is necessarily adversarial, and all
I've got here is the behavior when there's only one complete todo in the database. We're still listing all of the todos in the new table, and so we need a new context, which is and with an incomplete todo. Similarly, we're actually just going to copy most of
this before block, and we're just going to change complete to incomplete. We're going to copy the eggs, and then our test will be it does not show the incomplete todo in the completed todos table, and then all we need to do is visit home page, and
I should not see a completed todo with no by eggs, and so now we have our opposite expectation. We have an expectation that the completed one shows up in the table, and
the incomplete one doesn't. Running this test should fail because I can't type. Someone help. I need an extra end. Right, and so now we've got the by eggs
in our table of complete notes when we shouldn't, and so at this point, the only way we can make that actually work is to change the way that the instance variables in the controller get passed through to index.html.erb. You can see here that at todos is being
used for our completed todos table, but as we know from our just basic rail scaffolding implementation, that's just going to be todo.all, and so if we actually go into the todo controller, we can see here that that is indeed the behavior, and so before we make a change to this controller, let's write a test that causes it to have the behavior
that we want, and so at this point, I'm just going to actually write a test for the controller, and we're now in our inside step of our outside in testing cycle. We're no longer testing the application as a whole, but instead focusing on a single controller, and because this is a controller test, I need to add the type controller symbols
to this test, and just like a feature test, a controller test labeled type controller has methods included into it that are for testing controllers. We're going to describe get index here, which is the action that we need to add our instance variable to,
and we'll say it returns completed, only the completed todos, and what we'll do in order to get that set up is we're actually going to write, create some models in the database directly, so here I'm going to say todo.create done true note by milk,
and that let will give us a instance variable, sorry, a method in all of our tests called completed todo. We're also going to create an incomplete one which has done set to false
and a different note, and then before our test, we're actually going to create both of these. By default, RSpec does not evaluate lets until they're referenced inside a test, and what we're actually going to have a problem with here is that we're going to make the request to our controller and then expect to see results. In order for
that to work, we need to put the items in the database before we actually run our test, and then all I'm going to say is get index and expect assigns complete todos to an array containing completed todo, and let's talk about what this test is saying.
RSpec controller tests do not actually create HTTP requests, and by default, they do not render views. That means that the interface to your controller test is the instance variables that are assigned during the execution of your controller, and RSpec makes that available
via this assigns method which takes a symbol which will then get referenced to the instance variable. Get index does more or less exactly what you'd expect. It sends the index method to the controller with a faked up Rails HTTP request that has a get method
on it. Running this test, it's going to fail because we haven't assigned the complete todos instance variable, and so by default RSpec gives that a value of nil. Now that we have a failing test, we can go back into the controller. We can actually implement our behavior, and I'm going to say complete todos equals todo dot where
done true, which is what I believe to be the behavior necessary to make this test pass. It does, and now if we run all of our tests again, we'll be reminded which test we need to satisfy next, and the test that we need to satisfy next is our feature test for the todo spec, which we can satisfy just by changing the index dot HTMLB to reference
complete todos. Running all of our tests now, we should be completely green. So at this point, we've implemented the feature our customer asked for. We separated out the completed todos into their own list, and at this point, it would be perfectly
fine to stop writing software and put the Rails app down, but there's a small smell here that I think we can do better with, and it's to do with this todos controller. If you look at how index is actually written, the index method is at two different levels of abstraction on its two different lines. At todos equals todo dot all is
a pretty high level description of what we're doing, but the next line down, complete todos equals todo dot where done true knows much more about the database. It knows that there's a done column, and that when it's true, that means the todos are complete. I think it would be better to write something like this, but in order to do that, we'd
need to add a method to our model, and in order to do that, we should really write a test for our model before we do that. So let's do it. We're just gonna do this, and then like we did for our controller test, this test will be describing the model class, and
it will have type model that's set on it. Just like type controller and type feature, type model gives you RSpec methods in your test for testing models. We're going to describe a class method on todo, which is called complete todos, and the behavior
of this method is going to be very similar to our todo controller spec. If you look, we're actually creating a completed and incompleted todo in our test here, and so we can just copy those into our model test, and then write a very similar test which says it returns only the completed todos, and that will just be expect todo
dot complete todos to complete todo. And the reason that we create both is, again, so that we can be sure that that is the behavior that's only happening. If we run this test, it's gonna fail because the method is undefined. Now we can go back into our todo model and just add it onto the class with def self dot complete todos
is where done true. At this point, if we run all of our tests, they should all pass. I'm bad. This needs a D on the end of it. Now they should all pass. They do.
And now we can go back into our todo controller, and we can drop that method in, where todo completed todos, and now if we run all our tests, we should be green. And this is an example of a really simple refactoring that you can do whenever you're doing this
like test-driven workflow. When you see an active record scope in a controller, it's often a good idea to push it down into the model. So we've now implemented our change, and we can reload our Rails app here, and we can see that only the one that has done true is implemented, is in the completed todos table. And that
concludes the live coding portion of this talk. So, I wanted to finish up with a couple of closing remarks. The first one is that it is not actually a natural result of just like the laws of physics, that it would be possible to test Rails applications
with RSpec. A lot of time and a lot of effort has gone into making it possible to test Rails applications with RSpec. And the thing about Rails is like, it's a bit of a shifting platform. As we heard this morning, they're adding a bunch of quite strange new features, and really, we as a community have two people to mainly thank
for our ability to test Rails applications in RSpec. And those people are Aaron Cromer and Andy Lindemann. So, a long time ago, when Rails 3 came out, Andy was like, let's test some Rails applications with RSpec. And he put like a ton of
effort into maintaining the Rails gem over a number of years, and he has now stepped away from working on RSpec because he's moved on to other things. There's absolutely no shame in that because open source is hard, and working on it is difficult. So, when Andy stepped away, we were in this weird position where actually
nobody on the RSpec core team was working on Rails applications, which is a slightly weird position, but then this guy Aaron came along and was like, hey, let's do this. And so Aaron is now sort of leading the charge on maintaining RSpec Rails. And actually, Andy is in the room, and can we just get a big round of applause? He's right there.
So, as you may be able to tell, I'm not from here. I came over on Sunday from London. The flight is pretty pleasant, nine hours. I'm dead and ill, so that's great.
But I really love coming to America. I always have a great time when I'm here, and as any true Brit who loves America, I can only really take one stance on this country. I came here to take back the colonies, and I think I've worked out how I'm going
to do it this time, because you see, America, the national animal of your country is an eagle, and eagles are pretty cool, don't get me wrong, but the crest of the British Empire has a lion and a unicorn on it, and I'm pretty sure that a lion and a unicorn
could take an eagle in a fight any day of the week. This is not my first time in Atlanta. I've been here a number of times before, and obviously the barbecue here is fantastic. I can't recommend it enough, but there is one thing here that is absolutely terrible.
Sweet tea. No, oh my god, what are you doing? It tastes like sugar and the apocalypse. I hate it. It's so wrong. Tea is supposed to look like this, milky and brown and delicious and recovering. Thank you very much for listening to me yell at you.