 To introduce myself, my name is Sam Fiffin. I have fans. I'm Sam Fiffin on Twitter. I'm Sam Fiffin 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. 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, so I 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 of the 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 coding, and if it all goes well, we should gain an 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. To-dos 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 our 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 like simple RSpec methods. Instead, I've got two methods here called visit home page, and I should see the completed to-dos header, which are literally just methods that call straight into 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 to-dos header. These methods are actually implemented inside this module called to-do steps, and to just go into that, we can see that to-do 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 to-dos header says expect page to have content completed to-dos. 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 to-dos, 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 to-dos. 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 our header has now been added. Now, that's not a very interesting behavior change, but what we've done here is we've like written a first test that gives us hooks into actually being able to continue to write more tests for the application. And now what we're going to do is we're going to write a test which creates a completed to-do and expects it to be in the separate list. So to do that, I'm going to go back into my test and I'm going to add a context block. In our spec, context blocks just allow you to describe different states of your application, and in this case, the state is with a completed to-do. In order to build my completed to-do, before my test, I'm going to visit the to-do form and I'm going to create a complete to-do with note by milk. And that means that before each test inside this context block, those actions will be run and the to-do will be created. The behavior that I want is it shows the completed to-do in the completed to-dos list. And then all I need to do now is go back to the home page and make sure that I'm seeing that table and then say, I should see a completed to-do with note by milk. And at this point, we have a test for our next piece of behavior that we want which is actually listing out all of the to-dos 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 to-dos. What this I should see a completed to-do with note is doing is it's looking for an element on the page that has the HTML ID completed to-dos 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. So let's look at the to-do spec that we've written so far. Oh, my God. To-do spec 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 to-dos. But we're not quite done yet. And 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 to-do in the database. We're still listing all of the to-dos in the new table and so we need a new context which is and with an incomplete to-do. And 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 add by eggs and then our test will be it does not show the completed to- incomplete to-do in the completed to-dos table. And then all we need to do is visit home page, visit home page and I should not see a completed to-do with no by eggs. And so now we have our opposite expectation, right? We have an expectation that the completed one shows up in the table and the incomplete one doesn't. Planning 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 to-dos is being used for our completed to-dos table but as we know from our just like basic rail scaffolding implementation, that's just going to be to-do.all. And so if we actually go into the to-do 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, the 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 to-dos. 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 to-do.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 to-do. 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, our spec does not evaluate lets until they're referenced inside a test and what we're actually going to have a problem with here is 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 to-dos to an array containing completed to-do and let's talk about what this test is saying. Our spec 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 our spec makes that available via this assigns method which takes a symbol which will then get reference 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 to-dos instance variable and so by default, our spec gives that a value of nil. Now that we have a failing test, we can go back into the controller and we can actually implement our behavior and I'm going to say complete to-dos equals to-do.wear 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 to-do spec which we can satisfy just by changing the index.hmlb to reference complete to-dos. 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've separated out the completed to-dos 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 to-dos 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 to-dos equals to-dos.all is a pretty high level description of what we're doing but the next line down, complete to-dos equals to-dos.wear 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 to-dos 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 our spec methods in your test for testing models, we're going to describe a class method on to-dos, which is called complete to-dos, and the behavior of this method is going to be very similar to our to-dos controller spec. If you look, we're actually creating a completed and incomplete to-do in our test here. So we can just copy those into our model test and then write a very similar test which says it returns only the completed to-dos and that would just be expect to-do.complete to-dos to-ec complete to-do. 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 going to fail because the method is undefined. Now we can go back into our to-do model and just add it onto the class with def self.complete to-dos 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 to-do controller and we can drop that method in. Where to-do completed to-dos. 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 to-dos 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 Chroma and Andy Linderman. 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 obviously nobody on the RSpec core team was working on Rails applications which is a slightly weird position but then this guy Aaron came along 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 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. And 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's 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. You have to look like this, milky and brown and delicious and recovering. Thank you very much... for listening to me yell at you.