Lesson 8 of 15

Advanced RSpec and Cleaning Up The Specs

Duration: 25:44 Watch on YouTube (external link, opens in new tab)

Markdown version

We will now learn new features and advanced ideas to help clean up your RSpec files. Things like Subject, Let, specify, and more.

In this lesson

Roy Osherove refactors the string calculator (external link, opens in new tab) specs, which have become long-winded, with subject, which by default is an instance of the described class, and lets for the fake (external link, opens in new tab) logger and web service that can be overridden in a specific context, such as a logger that throws. He uses specify where the code says enough without a string and defines small methods such as adding so that each action is defined in one place. He notes a trade-off between the readability (external link, opens in new tab) of the documentation report and of the code, and explains that its fits when a single action changes several attributes.

Transcript

2,504 words

[00:03] (external link, opens in new tab) So what we're going to talk about now is a little bit about how to work a bit more effectively with RSpec (external link, opens in new tab) because up until now we just use the most basic aspect of RSpecs, most basic aspects of our spec and our specs are becoming very very long- winded. You have a lot of a lot of text and very little content and it's actually make them making them a bit less readable. So I want to show you some tricks that you can do with RSpec to make the code a bit more readable and hopefully to ease some of the pain.

[00:34] (external link, opens in new tab) So what I'm going to do now is I'm going to try to refactor a little bit of of of the of the current spec that we have. And the first thing that I want to show you is that we we have the idea of something called subject. subject. Subject is a special word in RSpec and it basically defines a shortcut that we can use. By default the subject is whatever we describe

[01:08] (external link, opens in new tab) In the under the describe thing. So if we have used the let so far right we said let calc and then we say string calculator (external link, opens in new tab) new we wanted to do that well subject is kind of like that but it automatically defaults to having this type of code. So we can say subject if we actually just remove this whole line we get it by default.

[01:42] (external link, opens in new tab) So if string calculator didn't have any dependencies subject would there would be a method subject that referenced an instance of already mu up string calculator for each test. Okay. Now why is that important? because the word it and subject can be used synonymously. So you can actually say something like like this

[02:13] (external link, opens in new tab) I can say I can define subject here and say subject let's see maybe we can do something a bit more Okay. So if I decide that subject is at the top I'm going to say subject it can't be just simple new string calculator. So subject is

[02:44] (external link, opens in new tab) A string calculator new with a logger and a web service. Now what's cool about subject is that it it is lazily evaluated just when it's called and we can actually control the values of logger and web service that will be mued up for each subcontext if we want to. So we can go ahead and say that let's let

[03:19] (external link, opens in new tab) Logger to be a fake (external link, opens in new tab) slow logger and that means that the logger in here will actually get its value from here. Okay, we can do the same for web service. Web service will be a fake of

[03:50] (external link, opens in new tab) Web service class. So that means that in these three lines we've defined the subject that is the subject of our test and its two dependencies right there in a very very explicit way. We can try to get rid of this

[04:21] (external link, opens in new tab) We don't need calc anymore but we call it right. So what we can do instead is we can give a name to our subject and say let's call it calc from now on. So now it can either be called through it or through subject do something or through calc do something. they all point kind of to the same thing. So here's a context with a logger. We don't need the mock logger anymore because it's already built in to the string calculator here.

[04:53] (external link, opens in new tab) Okay, we don't need to instantiate the string calculator. We just need to call add. Okay. And we should be able to just call the logger directly and verify. Oh, this is the the fake logger. So, we should be able to say that logger should have received, right?

[05:24] (external link, opens in new tab) And for now, we can say any args. Any ars is a shortcut in LOIUS that allows you to basically have any amount of arguments for example two and if tomorrow it's three it still passes and any value to each to each parameter okay let's see so that becomes a bit shorter and this allows us to let's see

[05:54] (external link, opens in new tab) Okay so we get a name error no such thing as a fake logger because we deleted the fake logger some other test are using it. So that's the error we're getting from another test. It's from line 24. So it's over here. You see this? We don't need fake logger anymore. But this actually passed. Okay. So you see we got rid of a lot of repeating code by having all this happen.

[06:27] (external link, opens in new tab) Now if we wanted to we can say let with call this a band and this basically tells RSpec that this the thing inside the curly braces gets executed like the before each test if you want to run a new instance before each test because by default the the value is lazily evaluated once and then saved.

[06:57] (external link, opens in new tab) Memoise it's called. It saves the value and then it gives that value to each new test but the state of the value is the same. It's not it doesn't change over tests. It's almost like it's serializing the value and then giving a value of that serialization to each spec. Anyway, we don't need the fake logger but we do want to configure the logger here to throw an exception. Right? So, how do we do that? Well, we can just use another let

[07:32] (external link, opens in new tab) And here we can just reset the logger to have to be called. So let logger to be a fake slow logger with the right method. Having a proc that raises a bam

[08:02] (external link, opens in new tab) Script. So what's going to happen now is that this overrides the global logger that we've set and will be injected into this line. So the logger from here is going to be using the logger from here at the context of this. So for that specific context we can just override the specific inputs into what is basically a function.

[08:36] (external link, opens in new tab) Okay. So we don't need to tell it that it will throw. We don't need to say new. We can just say cal.add add and we can say that web service should have received

[09:06] (external link, opens in new tab) Was it notify notify with any arcs. That's taken care of. So now we have this

[09:37] (external link, opens in new tab) Belongs here like this. You can say with the logger that throws we can put the left right up here so that it's connected to the context. right or web service and this is from line 26. Let's try that again. Line 26 in code spec.

[10:24] (external link, opens in new tab) Let's see. So undefined method write for web service. This is in the rescue part. I might have multiple web service classes here because of the demos. It's catching the wrong one. But if we only have one, it's usually much easier. Now what about this context of negative numbers? Let's try to deal a bit with this. Can we make this a bit more readable?

[10:57] (external link, opens in new tab) What if we said that? What if we said all this for negative numbers? Instead of doing that, we can say specify specify that you expect

[11:52] (external link, opens in new tab) So when we don't want to describe with a string when the string just feels like it's not needed because the text pretty much exemplifies what we want to have, we can just use specify as a short. Okay. And the same here we can just do that three times and have minus one 2 - one - 2 and get rid of all these.

[12:24] (external link, opens in new tab) So that's starting to look kind of nice, right? I hope. you write zero. >> Sorry, >> how the output is >> it's the same output basically. >> It's the same output. It's the same exact thing when you say it and then a string. It's the same as specifying it without a string.

[12:56] (external link, opens in new tab) But the expectation is that caulk add was supposed to raise an error. Okay. should raise exception. That's what it looks like. So, it looks a bit less nice. >> It definitely looks less nice in terms of a report. And that and and there's always a trade-off with these things. When you start stop using the strings, you start relying on the framework to start to parse what it is you're trying to do. There's always a trade-off between the readability (external link, opens in new tab) of the doc report and the read readability of the code.

[13:30] (external link, opens in new tab) Unfortunately, one thing we'll see later in this course is that we can use higher level constructs like cucumber to have the documentation that we want and still drive these kinds of tests with different ways. So but I want you to to practice to in making things a bit more readable and maintainable (external link, opens in new tab) in the specs. Now we're not done yet. This is just an example. Okay, what do we do with a single number?

[14:03] (external link, opens in new tab) Remember I said that we have that expect that method, right? What if we said that we can say what if we we can do subject just for this context and say the subject is

[14:48] (external link, opens in new tab) And sorry, what if we said this def

[15:24] (external link, opens in new tab) Or specify adding two five. What happens? See, expected five but got two. I can now have a much more readable way of saying

[15:54] (external link, opens in new tab) That with one set expect one. I can al of course specify calc add to should equal to but I find that this is one step nicer and I can get rid of that stuff and and now suddenly context of adding a single number or adding I can define adding at the top of the describe because it's reusable then I can use that

[16:25] (external link, opens in new tab) Expect adding like this I can use that also I can reuse use that. So I can define this at a higher level, move it away and then reuse that. So things are more are easier. Then I can of course do the same things like here handling two numbers. Then I can specify one and two should equal three. One, two, three should equal six.

[17:08] (external link, opens in new tab) And suddenly the hierarchy is starting to make a bit of sense. So one thing I like doing is that when I describe adding I define a method called adding just below it. Okay. And I define what the action looks like. If tomorrow the action changes I just need to change it to one place. Okay. But now I can actually say adding one and logger should receive. Same thing here.

[17:38] (external link, opens in new tab) I can do all these things. Okay, it's when you add one, you should get one. Now, this is actually a more interesting scenario that we haven't discussed yet, and I want you to actually do this.

[18:13] (external link, opens in new tab) I wanted to add a feature that has a calculator. Sum. So the feature is that it returns the last sum from the last operation made or form the last operation made. So I add one and then I check last sum. Okay. Now if I am using subject correctly, I can make things much much simpler or much much more readable. I I might be able to do something like this.

[18:45] (external link, opens in new tab) I can define the subject. Actually the subject is already calculated right. So I can say not sure. Okay. I can say that the subject

[19:18] (external link, opens in new tab) So here's what I'm trying to do. So I'm thinking out loud how to do this. What I would like to do is to use the special way of using it. So I can write something like this. It or its should equal one. Now this is a special construct because it is the subject.

[19:50] (external link, opens in new tab) Whatever is in the subject I can then assert on things on attributes of the subject. So I can say that subject do then I can say

[20:21] (external link, opens in new tab) Calc add one return the calculator that would make sense for this one but then I wouldn't be able to have multiple verifications of multiple attributes. I would have to repeat the subject in multiple cases. So it doesn't make sense to use it in this type. But when does it make sense? When a single action changes multiple attributes, right? You create a new person like the subject is creating a new person. Then you might say multiple its.

[20:52] (external link, opens in new tab) Its first name should equal George. Its last name should equal blah blah blah. And each one of them is running in its own little delegate universe. So if one of them fails, the other one still run. That's one of the coolest things about it. Okay. So maybe we can just try and do this specify that

[21:23] (external link, opens in new tab) Adding let's see I don't I I need to find a way to do this so that it there is only one action to do. So I can say let's define a method that says add and get last sum. It says count addut

[22:13] (external link, opens in new tab) So now I can say that add and get less sum should and get the sum of one equal one. Then I can do the same thing and say when you get back one and two and the last action sum should be three. Make sense? I can also say but what's the default?

[22:46] (external link, opens in new tab) Specify that well the default I can say actually it's last sum should by default equal zero because no action was performed make sense I can get rid of these things so it's just an example of how you would get around and get rid of things that you don't like.

[23:26] (external link, opens in new tab) Okay, here I can say that specify adding an empty string should equal zero. Whoa, I probably messed something up there. Not enough. We'll figure that out.

[24:04] (external link, opens in new tab) Come on. Okay, never mind. I think you get the idea. I won't bore you with any kind of running errors, but the point is that I want you to try to u start to use the idea of subject and let and overriding lets in different places. And what you end up with hopefully is something that is much much more readable. And instead of having the its you can you can well you can actually use the its here when they make sense.

[24:34] (external link, opens in new tab) It calls the web service and then the web service should have received notify. We can do many different things. For example, we can put the web service in the subject and say that it should have received notify. Okay, as an example, here's what it might look like. This is a different file context with a logger that throws. So let logger do something that throws bam

[25:05] (external link, opens in new tab) Make the subject the web service and then it should have received notify. Okay questions? Okay. So before moving on with the rest of the exercises from yesterday, I want you to try to clean up and see basically play like it's u like play-doh with the RSpec syntax and see what you end up with, what you like and where you don't like things, which constructs you like and see if you can clean it up a bit.

[25:42] (external link, opens in new tab) Okay.