Join Kevin Skoglund for an in-depth discussion in this video Hooks: before, after, and around, part of Ruby: Testing with RSpec.
- In this movie, we'll learn how to use before, after, and around hooks to make your tests more efficient. In programming, there's a fundamental principle called, Don't Repeat Yourself or DRY for short. It's a good programming practice that says, whenever possible, try to write code one time and then refer to that code whenever you need it again, instead of writing it all over. It keeps your code cleaner, clearer, and reduces the opportunities for mistakes. That's part of the idea behind hooks. Write code one time and then tell RSpec to run that code either before, after, or around our examples.
Each of these RSpec methods is going to take an argument, which is a symbol indicating the scope in which it should be run. Let's look at before hooks as an example, so we can understand scope. In the first example, suite indicates the block of code should be run once before the suite of tests. That means all of your tests. Typically, you would put this in your spec helper file inside the RSpec config block, and that's why I've begun it with the config. in front of before.
This ensures that whether you're running one test or all of tests, this code still runs one time at the very start. It's used rarely. Mostly it's used to do any setup that our entire test suite needs right at the start. In the second example, we are using the context scope. This is more common. This runs the block of code once before any of the examples in an example group are executed. And you'll remember, an example group is defined by describe or context. So before it runs anything inside that context, it'll run that block of code.
In the third example, we are using the example scope. This runs the block of code once before each example. If you have 10 examples in an example group, then the before example code will run 10 times. The before example code, then the example, the before example code, then the example, and so on. In previous versions of RSpec, these were labeled suite, all, and each, and all and each are still aliases for context and example, but all and each became a little bit confusing.
So they tried to clarify it by using context and example, and I think those are superior, so that's what we're going to be using, instead of the older all and each forms. Now if you happen to have all three of these in place, then they run in the order shown. The before suite runs first, then before context, and then example, and then your example runs. After your example is done, it runs any after hooks that you have in the reverse order, after example, after context, after suite.
Together, before hooks and after hooks are going to address most of what you're going to need, but there are some rare occasions when you're going to need to use around, and around works a little bit differently. We have around example, and then you'll notice that we have example declared as a block variable after that. That's so that then we can call example.run in the third line of code there. So you can see I can do whatever business I want to do before I do that. When I'm finally ready, I say example.run, and then I can do any business that I want to take care of after the example.
It's very similar to having a before example and an after example code block. Here we're just doing it all in one step. Nine times out of 10, before and after are probably going to be better alternatives for you than around. And in case you were wondering, if you happen to use both, around executes its before before any before hooks take place, and then after any after hooks are done, then the around code takes place. So it wraps everything, including your before hooks and your after hooks.
Now, there's only one other thing that's important to know about hooks, and that is that you need to use instance variables in them in order to make objects and values available to your examples. So, for example, if we did some searching in the database and we find a customer. We assign it to the variable customer. That's a local variable that will not be available. But @customer is an instance variable, and it will be available to all of our examples. So just make sure that you're always using instance variables in order to access information between your hooks and your examples.
Let's try one so you can see how it works. So in my car spec that we've written previously, if you take a look here at our attributes, you'll see something that's common to all of these. You'll see that we are repeating ourselves for each and every one of these. The first thing that we're doing is we're creating a new car. Every single one is creating a new instance of the car class. So instead, what we can do here is at the top of this describe block, we can put before, and then it's up to us whether we want to use context or example.
I think probably example is what we want here, and I'll tell you why in just a second, but let's put our block, and then let's just take that code, cut it, put it up here, but remember, it's got to be an instance variable. It won't work unless we change all of these to be instance variables, all right? So now, this is available. Car.new is available to me, and it'll be right here waiting for me inside this example. Same thing here. I could take that out, turn this into an instance variable. Let's take this one out.
Once again, refer to that instance variable, and let's take this one, same thing here, and that's a pending test that we never finished. So there you go. You can see we removed a few lines of code in the process, and now we're just creating one car right at the start. Now, why did I use before example? Well because I'm setting some values here, and I just want to make sure that setting those values is not going to cause problems in my other examples, right? Because if I just do it one time, that car object is still around.
It's still what's going to be referred to. By you calling before example, I'm going to be creating a new instance of it each time. I'm essentially refreshing it with a new instance. That's not a particularly expensive operation, so that's okay. I'll go ahead and do that here. Let's try it. Let's come back over here, and let's run our specs. Rspec spec, and there you go. You see that all of our tests passed. We just have our one pending test still there. So we were able to use our before example hook. Now we can look at our other code down here and see if there's anything common to it, but I don't think there is.
I think here we're creating a car that's got specific attributes. It's a Honda. Here we're creating a car, and we actually have the same one here, but because it's so different, because it's so far down here, I'm not going to go to the trouble of pulling up that same car again. I could. I could have one that just said new car or something like that, make it clear, and I could move this example group then up here, right? And then it would be available to all of the group, not just my attributes but to all of the different tests. But I'm not going to do that. I'm going to just leave it inside the attributes so that it's referred to there.
So, it's fairly straightforward, once you just know the way that the scopes work, you know the difference between suite, context, and example, and once you remember that you have to use instance variables in order to have access to those objects and values inside your examples.
- Installing and configuring RSpec
- Writing and running examples
- Defining expectations using matchers
- Using helper methods, before/after hooks, and shared examples
- Creating test doubles using mocks and stubs
- Testing Ruby on Rails with RSpec
- Putting test-driven development into practice