Check out a free preview of the full Production-Grade Angular course
The "Testing" Lesson is part of the full, Production-Grade Angular course featured in this preview video. Here's what you'd learn in this lesson:
Lukas walks students through a testing example, and explains how a test is written. A test starts with a test module, then a test fixture or environment is added to the component it is living in. How to trigger change detection through the debug element is also discussed in this segment.
Transcript from the "Testing" Lesson
[00:00:00]
>> If you can believe it, everything that I've been talking about today has been about testing. Like every pattern, everything that we went through, that ultimately it's designed to make testing easier. So I really absolutely believe it is impossible to write good tests for bad code. What are we've been talking about all day?
[00:00:31]
Good code, good angular applications, high quality angular applications, and really, I've been challenging the kind of the premises in which we think about writing large scale apps for production. And a major portion of that is, where are we putting the complexity? And so what I want to start out with, with just a bit of commentary is, let's look at some code.
[00:01:05]
So let's go with, The, Widgets list component. How do we test this? Like, how would we write a unit test for this? I don't really have a good answer. I mean, I could think of some weird ways to do it. But this is like comparing one of those old platter of hard drives to a solid state drive.
[00:01:54]
There's no moving pieces here. This is the equivalent of a solid state hard drive. It's very stable. How this would break, is if angular broke? But here's the secret. I know because I've written some. I know for a fact that angular has a ton of unit tests. And so the idea that angular would just blatantly break, like their binding would be like, it would be humorous at this point, like it would be so implausible.
[00:02:25]
And so now when you look at this, you realize, we've written code that without really having this as being the primary focus that we don't really have to test. We've reduced the surface area at the component layer, at least at this component. So let's go and let's look at another component.
[00:03:00]
Same thing. How do we test this? Well, you could, You could possibly test this setter, but, Compared to like an effect, this is a pretty low value, low risk target. Let's go a little bit further. Let's look at our widgets component. And this is where this gets interesting.
[00:03:41]
How would we test this function right here? So let's take a stab at this. It's helpful when you're writing a test to verbally explain what it does. And then in your explanation, you're going to uncover some potentially problematic things or you're going to realize whatever you say it does like that's what you test.
[00:04:16]
And so in this case, when I call loadWidgets, It should call widgetsFace.loadWidgets. That's it. So you test only what that function does. So a test for load widgets. All I'm asserting is that, when I call load widgets, that it properly delegates and calls, widgets facade, load widgets. And so you can see even at the container component layer, there's not a lot to test.
[00:05:01]
This is by design. And so if we look at the spec, You'll notice here that when I call, let's go up here, Any of these, we can just pick one, but, I'm saying, when I select a widget on the component. So let's look back here. Select a widget, what does it do?
[00:05:36]
Well, it calls selectWidget and passes that widget in. So when I call selectWidget with a mockWidget, what I'm verifying is that I expect this spy to have been called with this mockWidget ID. So all ready, if you're new to testing and testing is overwhelming, I'm giving you a huge head start.
[00:06:04]
In that, if you want to get good at testing, focus on writing good code. What does that mean? Very small, granular, single purpose functions, and then only test what that method does. So, in a lot of ways, we have covered the entire component layer from a unit testing standpoint.
[00:06:34]
Now, with that said, let me offer some commentary on the pieces here so that we know what is happening. And I'm going to, Choose, well, this one will work. But what I'm going to do is I'm going to just short circuit this a little bit. So when you write a test at the core, you need to create an environment in which that thing that you wanna test can be spun up, so that you can interact with it and make assertions.
[00:07:22]
So under the hood, what we need to do is we need the ability, in this case, to instantiate a widget component in a controlled environment, without having to spin up everything else. How we do this is with the TestBed. So in this case, TestBed, configureTestingModule, and then we define just like an entry module or declarations, which because we have in our widgets component two other components need to declare them.
[00:08:02]
So it's just like you're doing an entry module. We need to import our dependencies or an approximation of them, for instance, you may notice that we have this HttpClientTestingModule. So we're using that in lieu of the HTTP client module. RouterTestingModule, if you have the material module, well, angular material has a dependency on animations.
[00:08:30]
Well, you can replace that with the noop animations. And then as well, your services. And I'm going to talk about this in just a moment. But I'm basically saying, spin up this module and then I want you to compile the components. Now this is done asynchronously because depending on how you load your templates, they could be external.
[00:09:04]
And so there's might be this small asynchronous call, which grabs that. Now, typically, I'm under the assumption that everything is basically has been web bundled to the max, and so I don't see this happening a lot, but it is possible. And so this is an asynchronous operation. You are configuring the module and compiling the components.
[00:09:30]
And then from there, you want to actually instantiate the component. So in the TestBed, we are getting, we're saying create component and you're passing the component you want to create, which returns a fixture. Now think of a fixture as this controlled harness in which your component is going to live in.
[00:09:52]
So it's not the component itself, but it's the fixture for that component. Then you can get an instance to that component via fixture.componentInstance. And so now, what you can do is within this, I mean this is an instance of the component, so any properties methods that you have on your actual component class, is now available here.
[00:10:20]
And then, you'll notice here that I have a debug element. So I'm grabbing the debug element off the fixture. And what this is, is essentially the template for that component. So this is actually really, really cool. This was super hard to do in legacy AngularJS. Now, it's actually not hard to do in the sense of, I want to render this component in memory and I want to make assertions on the template itself.
[00:10:55]
So I could say, in this component, I have this h1 tag and it's bound to this property. Expect the guts of this h1 tag to basically render to this property. Well, now you can say, give me the debug element. And then using a CSS selector, you can grab an instance of that h1 tag and test against it.
[00:11:17]
So it's kind of like end-to-end testing, like that you testing some interactions. I would say, use that sparingly but it's pretty helpful, especially when you're testing for inputs and outputs on a child component. So what I would do is, if I wanted to actually test an output is that, I would trigger that via the debug element.
[00:11:41]
And then one little piece here, that automatic change detection is turned off on the fixture or for your component. And so what you have to do is you have to manually say, I've made this change, now detect changes. And what this does is it allows you to have greater control over how you write your assertions pre-binding and post binding.
[00:12:07]
I've never needed to do that, but I can see or somebody would need that, I need to make an assertion before it renders and then after it renders. And so you have that ability if you need to. And then from here which you can see is I'm just calling the methods like it's just a regular class.
[00:12:28]
And so just to review, you have essentially a test module that you spin up, that kind of compiles everything together, it's asynchronous. And then from there, you get your test fixture, which is kind of the environment that your component is living in, and then you can get access to the component, the debug element, and you can trigger change detection.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops