Transcript from the "Testing and Debugging" Lesson
>> Lukas Ruebbelke: So what I am gonna do is I am gonna talk about testing in Angular, a couple different strategies. And then I am gonna show you how to write a component test. I'll briefly touch on an end-to-end test. But I think that will give you the kind of the ground work, kind of the entry level for how to write Angular test and do that.
[00:00:22] So generally, when I talk to people about testing, especially kind of seasoned developers who have been around, unless they're exceptionally disciplined, most people kind of have this reaction of just like, I should do it, I don't write enough test. Even myself, I would admit, everybody say like, I could write more test.
[00:00:47] And the reason, so well, why don't you don't? Well, it's hard. It's kind of the genera,l I think, implied consensus is that testing is hard. Well for one, that's because writing software is hard. Again, we've talked about this, this is not a trivial activity. But I found that, especially in Angular, as you start to write test, you realize you're doing a lot of the same things over and over.
[00:01:20] And as a result, I realized that once I kind of understood some of these basic patterns, aka the secret handshake, it was a lot easier for me to just get up in running and write the test. And so the problem is, and it's, I think, a challenge with unit testing, it's a huge challenge, with end-to-end testing, is that you spend more time setting up the environment and kind of try to bring it to life.
[00:01:50] All of a sudden you get this Franken test runner that tips over every other time. Selenium, I'm looking at you. That it's just so hard that is not natural to be in a development work flow. And so people forget it, and it goes stale. But once you can kind of streamline some of those pinpoints, that you can focus on kind of these basic patterns, and really, truly be effective.
[00:02:17] So here are some things that I have found that, for me, make it much, much easier to test. I will say this, if you wanna write good test, write good code. When you think, if you approach from a test-first mindset, that you will write good test because you're writing good code.
[00:02:40] There's nothing worse than coming into a project with like a 1,200 line method, I've gotta test this. Well, the person who wrote this clearly did not have the same concept of what a software craftsmanship or software craftsman should look like. So first and foremost, small methods are easier to test.
[00:03:05] The smaller they are, the more precise in what they do, the easier it is to test. Because when you ask yourself, what does this method do? And if you come back and it says, it takes this thing and it does this thing. That's really to, in just articulating it, you can say, it does this thing.
[00:03:28] Well, then it's easy to write an assertion. And a lot of times I will actually talk out loud or verbalize, okay, I'm testing this method, what should it do? Well, I should call this and this should happen. But when you find yourselves saying, what does this method do?
[00:03:42] Well, it takes this and it does this, and then based on this it does this. Stop, that's a recipe, like you start to go down this path of like, my method is doing too much because you have a test that takes this, and it does this, and then based on this it does something else.
[00:03:57] So now you're having to write multiple tests for a single piece of code.
>> Lukas Ruebbelke: Pure methods are easier to test. We talked about this, so having methods that are free from internal states and side effects. So this is basically called referential transparency, meaning you can call a method, and every single time, if you pass in the same parameters, you're going to get the same result every single time.
[00:04:22] Those are pure, and these are ultimately what you should strive for. The more of these you have, the easier it is to test. So for instance, with Redux, everything happens in a reducer. Well, every one of those methods are pure and immutable in nature. And so it's insanely easy to test a reducer because you just know, I'm putting this in, and I'm getting this out.
[00:04:45] It has that clear contract of inputs and outputs. Also, just to clarify, at the break, the question's like well, when you say Redux, what do you mean? Because I often times use Redux and NgRx interchangeably. So there is Redux, the library that works with React, and so I refer to that to uppercase R, Redux.
[00:05:06] But not only is it a library, but it is a pattern. And so when I talk about Redux in the context of this workshop, I'm talking about the pattern. So Redux, the pattern, is what NgRx is based off of, so that is what I'm referring to here. So hopefully that clears up a little bit of the confusion, it's slightly overloaded, Redux the library, Redux the pattern.
[00:05:26] So pure methods are easier to test.
>> Lukas Ruebbelke: And don't use real services, so one of the big things that I've seen is you have a service that's actually making the server side call. And you're testing a component that does that. Well, this is where dependency injection comes in in a big way.
[00:05:51] Instead of using the real service, use a test double, so when you inject something into a component, just send in a test double, something to stand in place of that real service. In a lot cases, depending on the nature of my component, is I will simply pass in an empty class and that will suffice, I've satisfied the dependency.
[00:06:18] Or if it does some kind of a call, I'm explicitly calling on the service, then I just mock that out. But the point is focus on the thing you're testing and isolate it. And so any dependencies, use test doubles, so don't use real services or dependencies. So you can use a stub, this is just a small generic object that you can put in in its place, you can also you a spy.
[00:06:49] So occasionally, I will inject the service, but I'll actually intercept the call and overwrite it with my own response. Even what I'll do is if I'm testing a component that has a service that makes a call, is I'll just spy on the service call. And so what that means is when this method happens on the component, it calls this method on the service with this parameter.
[00:07:26] So what I can do is I can say, okay, I'm gonna spy on this service method and assert that it was indeed called with this value. So I'm not testing the service, but I'm spying on what we're calling that service with.
>> Lukas Ruebbelke: And so faking and spying, they're both great options.
[00:07:47] In fact, start with what is easiest. So generally, for me, the first thing I do is I'll start with a stub or a mock. I'll just say, this is this thing, I'm gonna just create an empty object, and I will start to build it out as I need to.
[00:08:05] In fact, they're both great options, and usually use both. So a lot of times I will do a mock, and then I will then spy on the methods. So I'll even mock out the method, but then I'll spy on it to make sure that it's been called.
>> Lukas Ruebbelke: So in Angular, here is kind of the basic structure.
[00:08:29] So we've been talking about this, this is the third day now, so you have basically a class that's wrapped in some metadata. So Angular is kinda this candy coated shell, and then inside you have this milk chocolate imitation, if we're being honest. I don't even think they can call it milk chocolate anymore.
[00:08:48] So we have two approaches based on this. We can use the Angular testing utilities and test it in the context of an Angular application. Or we can just say, we're gonna bypass this angular wrapper, and we're going to test the class itself in isolation. Also valid for services, and pipes, and different things that are just classes, oftentimes this is the easiest thing to do.
[00:09:14] So generally, what I'll do is I will do components using the Angular testing utilities, but then services, and pipes, and different things, is I'll just test those in isolation.
>> Lukas Ruebbelke: And so kind of the testing big picture is our test runner is Karma, which shifts with angular CLI.
[00:09:39] And we use Jasmine as our assertion library, which shifts with the CLI. And then we have out testing utilities, which shifts with the CLI. And then we have our code, if only that shifted with the CLI, my life would be so easy. Like your recipe thing would be done by now, if we could just feed it the mock and-
>> Speaker 2: Next week.
>> Lukas Ruebbelke: Yeah, next week, I'll talk to Mike about that.