Nested Logic: Hooks

Boris Paskhaver
A free video tutorial from Boris Paskhaver
Software Engineer | Consultant | Author
4.7 instructor rating • 6 courses • 283,266 students

Lecture description

Hooks gain an additional layer of complexity when describe / context blocks are nested within other describe / context blocks. The before(:context) hooks runs once before all tests in the current context (i.e. the current block). The before(:example) hook runs once before each test in the current context (which includes all nested blocks as well). If there is a before(:example) hook defined at multiple levels, each one will run in sequence, starting from the top-most block and proceeding downwards.

Learn more from the full course

Testing Ruby with RSpec: The Complete Guide

Master the art of test driven development (TDD) in Ruby using the RSpec Gem. No previous testing experience needed!

07:28:36 of on-demand video • Updated September 2020

  • Utilize test-driven development principles to design and implement clean test specs in Ruby
  • Master the syntax and structure of RSpec, the most popular Ruby Gem for testing
  • Reduce dependencies in your test suite by mocking objects with class and instance doubles
  • Explore the wide collection of RSpec matchers available to test your code
English [Auto] In this lesson we'll take a look at how we can use our spec hooks within a nested describe or context block. So let's dive right into it. I'll begin with a very basic example in this high level describe. I'm going to add to before hooks one for the context and one for the example somewhere right before context here. Let's give that a do end. BLOCK And in here I'm just going to put puts before context I'm going to copy this code pasted below. We're gonna make this before example I'm going to output before example here and I'm going to do a simple example here it does basic math. I just need some test right here to run and I'm going to expect that one plus one should be equal to two. They should be pretty familiar by now. If I run this back with our spec spec nested hooks we're gonna see at the very top we have before context appear first. That is because this thing runs once before everything else within the block and the block again is this big do any block that we pass to the to the described method. Afterwards we move to before example which only runs once all runs once before. Every example in our current describe a block but in this case we only have one example so it's only going to run once. So that's going to run and here we have before example. And finally we have the example with the actual basic math being calculated so everything looks proper right now. Well now let's add a little bit more complexity. I'm going to use the context the method which again is just an alias for describe which allows us to describe the condition right a more specific scenario that we're testing. In this case I'm just going to arbitrarily say with condition a we're not actually testing anything real here so this is for the purposes of example we can assume we're testing something in it in a given condition condition. I'm going to provide that context method with a block and I'm actually gonna do is go up here and copy and paste this whole code right here before context and before example but I'm gonna paste it in here and in order to distinguish between them what I'm gonna do is scroll up here and right here before this I'm just gonna write out or before context and outer before example and below I'm just gonna write inner before inner before context and inner before example I'm gonna do some tests in here two tests just to give you a good example here so it lets say it does some more basic math in here I'm going to expect that let's again just do one plus one is going to be equal to two animate a copy that example pasted below and do something like it does subtraction as well. Totally arbitrary here but let's just do five minus three and that should give us two as well. All right. So here's my challenge to you I recommend that you pause the video and before you execute anything in the terminal what I recommend you do is guess or try to figure out logically that's even better of an approach. Try to figure out logically what is going to be output with the put statement and in what order and how is it going to occur. And so take a second to think about it and then I'm going to do is walk you through what's actually gonna happen and then we're going to execute it in the terminal and see what happens. Let's take three seconds to think about it and then I'll rejoin you after you're done. On pausing the video. All right. So we're back. Let's talk about what's going to happen here. The important thing to remember is when we're talking about context we're talking about the current block. So where every you're currently placed where whichever due end construct you're inside that describes your context. So we begin at the most outer level and then we proceed in words in our respect just like we do in most in most programming languages. So in here we start with this higher level block that begins with the described method. And that's this whole thing that captures all of our examples in all of our nested contexts. So that context that whole thing is the beginning of of the top level context. So this line line two to four the outer level context is what's going to run once it's going to turn once a total time because it is the first thing within the current context which is that most higher level one we're dealing with. All right. At that point our respect sees that we have an example right here. And it also sees that we have an above for example. So what's going to happen is our suspect says OK this needs to run before every example in the current scope. So what it's going to do is run this one we're going to see out or before example and then arsenic is going to run this test for us. This is all pretty simple this is what we just showed a couple minutes ago when we ran this before the nested context. The real question is what's going to happen when we dive into here. So here's where it gets really interesting. We're now entering a brand new context now the context method gives it away here. But remember this could also be described so you really shouldn't use the base of the method that's being called what you should really use is the base of the block here. So we have a brand new block it starts right here on line 14 do. And it ends right here on line 30 with. And so this is our second context it's are nested contexts. So what's going to happen is this top level one has already ran in its context but this new context that's defined in line 15 only for conditional has not yet run. And since we're entering now the testing part of that context with condition eight lines 15 17 it needs to run once total before all of the tests in here run right. So it's not going to run multiple times but it's only going to run once in or before context because this is the first time we're evaluating any code within our condition a context. So this is going to run lines 15 to 17 afterwards. Again our Spike is going to see a before example and it's gonna see two examples and you might think that it's going to dive right into this but as it actually turns out what's going to happen is it's actually gonna execute this thing first. And the reason is because all our all outer level before us are also going to be valid in inner level contexts. It's not the other way around. So let me reiterate what that means in this nested scope. This thing is obviously not going to run before this example up here on line 10. However in reverse when we have a higher level scope this example is defined outside of this nested scope that begins with context. This example before hook is going to run before every example in here. What's also going to run is this before hook with every example and here because it's also defined but only within this context. So what's going to happen is this thing is going to run for everything that's nested below it then this thing is going to run. Then we're going to do our first example. That aspect is going to jeopardize a diver climb back up into the higher scope and do before example again. There's no need to do before context because remember that's for this higher level block and that's already been taken care of. There's no reason to do this before context because that's already been taken care of within that context. But we still need that higher level before example and then we're gonna do our internal nested in our example and then we're finally going to finish up with our final example. And it's a lot to take in but obviously this logic will apply the exact same when you're dealing with after hook. So if you do something after example the exact same global local context rules will apply it's defined at a higher level it's going to run after every example at a lower nested level. But if it's an after hook that's defined in a lower a nested level then it's not going to run at a higher level for those examples if that makes sense. So one more time let's just go through this. Just really really quickly we're going to begin with before context then before example for our four outer level example then we're gonna do the example. All right then we're going to dive into our context. We're going to run this before context to what's right the first time or in this context whether they're going to do before example up here before example right here and then we're gonna do this example the very first one in our in our in our nested context right here on lines twenty three twenty five. We then are going to do one more time before example right here before example in the Interscope scope and then the second example and then we're done. Let's take a look at what that's going to look like in the terminal we must scroll up here so one more time we can go through exactly what's happening when they screw up the code and we'll match it with the terminal output hour before context. We're entering our described methods context right here. This do block which means this is the code that interjects in that process steps in and says OK. Now my job to execute something before everything runs starts running within this block. That's gonna be our outer before context. That's what we see right here. We then have one example in the outer scope and the only single hook that applies from that point forward to that is the before example. So we're gonna have outer before example. This is what we see right here. Then ask because it actually do the example right here on lines 10 to 12. Then we move into our nested context. The first thing that needs to happen is our spec has a new before hook that's defined only within that context that only needs to run once within this block that begins on line 14. So we're going to have our inner before context execute that's coming from line 16. And then just as I talked about earlier we're gonna have to before hooks for each example run one is going to be the outer one which is defined in the outer scope and what is going to be the inner one. So for us we're going to do a line six through eight then lines 19 through 21 and then the first example and then that exact same logic is gonna apply outer before example inner before example. And then the final test that we have written here the final example lines twenty seven twenty nine. This is really tough. All right it's. It's a tough thing to wrap your mind around it. And we've only done it by the way two levels or two total levels maybe one level deep is another way of putting it in real professional code base is what you're going to find is that people are gonna go even more levels deep three levels down four levels down. Why would they want to do that. Well the whole point is to essentially refactor and dry up the tests as much as possible. Maybe you're going to have a certain context or a certain condition that you're testing where you want something to run once before all tests or you want something to run once before every single example but you don't want to do the same thing in another context because that's going to be a waste of resources and a waste of computational power. If you successfully organize your tests and you're before and after hooks the goal is that each test will only rely on a hook that provides it's something that it needs to optimally run and and removes all the duplicate code that you would have to run within the actual example itself. Your goal if your goal you're your kind of sign of ultimate progress in our spec is if your examples are super simple like this. If you just haven't it's statement and expectation and there's minimal setup within the example. Which means the before is taking care of it as much as possible. It's not always going to be the case but as much as your examples are super clean and just have an expectation or two. That's a good sign that your spec file isn't in a good shape in a in a ruby like shape. It's very clean and elegant and dried. All right that's a lot to cover in this lesson so if you feel a little bit confused feel free to re watch it. I'm also going to provide you several assignments moving forward that will allow you to really reinforce these concepts and practice with them we might even go an additional level deep just to make sure you have a strong understanding of before and after hooks in an S that concept context. But at the base level it just allows us to write more dried up specs and our spec is simply going to jump around and figure out what it needs to run before every example. Again before context means run once within the current context or within the current block is the right way of thinking about it. And before example means run once before every example but only within that current block. So in this case this before example applies to both this example as well as the one or the two below it because they're still within the same outer level context that this high level before is defined in in comparison before example on lines nineteen twenty one is only defined within this context with condition a. So it only needs to run before these two examples not the ones above its left taken. But this is our very first introduction so feel free to re watch the lesson and I'll give you plenty of practice moving forward with these concepts as well. So I'll see you on the next one.