Angular component testing - Overcoming the hurdles

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
this might be something that's familiar to you this is an broken karma test a broken angular component test and if this is the type of thing that you've seen and you've had trouble getting through some of the issues that things like this show you're in the right place and someone else had a problem almost exactly like this a few years ago it was one of our clients who called up and said that she knew how to do some karma and jasmine she understood how those work and she was an angular developer she knew how components worked but she had run into problems just like this on her code see at her company they hadn't had a culture of unit testing so even though she individually knew that unit tests would be really good for her that it would be good for the application that she could have a more reliable application a more reliable project project that she would be more confident in being able to rewrite her code to do refactorings if she had a unit test suite available she hadn't actually done it and so then after the fact she tried to add some unit tests to her components and she added the default boilerplate unit tests with test beds and in uh injection and fixtures and all those things that angular gives us and when you start out and she ran into problems like this she ran into hurdles and angular gives us a lot of power with unit testing but with that power there are hurdles that we need to go over and that's the point of this talk how do we overcome those hurdles that we run into when we do angular component testing so my name is lance finney i'm a software developer an angular developer with oasis digital i'm also one of the instructors at angular boot camp you can see my linkedin profile and my twitter account if you want to follow i have been with oasis digital for about three years and i've been able to write a lot of scalable robust robust applications for several different clients that we have because of unit testing what i'm going to focus on today is a component from the tour of heroes many of you may be familiar with the tour of heroes it is a application that is built within the angular tutorial that shows how to start from very simple principles to build up an application that has crud features and good back end and a lot of good development principles when my client asked me for help with her unit testing a while ago i decided that i wanted to look at tour of heroes to see what does it do for unit testing figuring that since tour of heroes is the way that angular teaches the correct ways to do so many things that they would show the correct way to do testing and i was surprised because at that point the types of failures that my client was getting were the types of failures that the tour of heroes had because churros heroes that team the documentation team did the same thing that my client did and that they started out creating the components and it automatically created the unit tests and then they cr added everything that they needed to to these components but they didn't up the update the tests along the way so the tests had failures exactly like what i showed before so i went through the process with her of looking at what it would take for the tour of heroes to fix things and that's the process i'm going to go through today these won't be the exact same failures that you have if you have issues with dependency injection there'll be similar things that you have but not exactly what i show today but i will show some of the basic things to look at and to think about to get over those hurdles to get over them but more importantly at the end i'm going to show that you don't always have to go over the hurdle sometimes you can go around it because there are ways to do unit testing component unit testing in angular that avoid a lot of the problems if you're writing the right type of test for it so the tour of heroes the component that i'm going to look at is called the dashboard component and that is the component that makes up this part from the top heroes label at the top to these four buttons and then there's a sub component with another label and the text field in there that's the component that we're going to look at and i mentioned that the angular team had these tests broken back then they're fine now if you download the tour of heroes zip file at this point those tests pass but in order to be able to talk about them i strip that out i reverted them back to what the boilerplate was and we'll see what the problems are and build from there and the first thing i'm going to do is fix the tests and they do need to be fixed because this is what happens when i run one of those tests right now that test fails and i'm going to talk a lot more about what's going on here and why it fails and how it fails so i'm going to fix it i'm going to get this test to green but i'm going to do more than just fix it i'm going to expand the test because the initial test doesn't really do anything it doesn't assert much i'm going to add some assertions to show why we had to go through those that effort what going over those hurdles bought us why it was worthwhile and then i'm going to show that depending upon what type of test you write you don't need to do that and if you have the right type of test and if you divide your tests up in the right way you can simplify your tests to have tests that run more quickly and make your life a lot easier in the practice so that's what i'm going to be doing here welcome so let's take a look first at the component and its test on the right is the component so this is the typescript for the component it's a fairly small component there's not a whole lot here but there's enough here that the boilerplate test fails if we take a look at it it has the component decorator like all angular components do and a decorator the component decorator in particular does a few things of metadata for angular the obvious ones are that it provides the selector which allows angular's other components to load in this particular component it defines the files for the template and the styling that also part of this component and then there's the actual uh content oh sorry but there's more that the decorator does and i'm going to talk about that in a bit because the decorator sets things up for our dependency injection which is really really powerful but it's part of what causes some problems with our tests the component itself doesn't do a whole lot it has the on init life cycle hook so when it is generated that function is called it has a constructor where it takes in a service and then in the on init lifecycle hook which is what's called right here it calls a function when we call that method it uses the service that was injected in the constructor to pull out some heroes that's really all it's doing it builds a constructor to give us a service that we use during the engine init to grab the heroes for display if we look at whoops i click the wrong button sorry if we look at the template the template is pretty simple as well it has that top heroes label that i mentioned it has some information here based on the number of heroes that are in here it will show a link for each of those and it has the subcomponent so let's look at the test this is as i said the default boilerplate test that you get when you create a new component using the angular cli there's one difference though i added the word f here so the describe block is what we usually use to group our tests together to have a test suite of like-minded tests that can share set setup and tear down and things like that i did the letter f here to focus in so this is the only test that will be run there's other tests in this application but this is the only one that's going to get run this time because i'm focusing on it otherwise i would just have a describe and all the tests would run within my describe this is the typical structure for a component test we get a label at the top which is the name of the component that i want to run and then it sets up for us by default so a couple before each so these are the code that are initializing things for our component to run the test i'm going to talk a lot more about what's going on in there because that's where we run into these problems of these hurdles that we need to overcome and then finally there's an individual test that's where our actual assertion is we say all right the dashboard component it should create and all we are asserting here we are expecting is that the component instance that was fetched as part of the before ages exists that's it so really all we're doing here is we're testing that the constructor works and if we look at the test it doesn't it fails so very simple test and it fails right out of the gate and this as i said is what my client was running into so what are the problems that we have here let's take a look at this now fortunately most of the angular issues that you have of this sort give you some pretty good information but it takes you a little while to get used to them and learn how to read them this particular error is not a test specific error i've run into this error many times as i've built up new components in my production code and i've referred to a service and a component or a component to another component and i haven't set everything up yet this is a production error and it's such a common production error actually that the angular team just as part of angular 11.1 which was just released made this as part of the initial set of eight error messages that are keyed in that you can go find more information about easily and they just released a video explaining more about null injector error i think it's about a six minute video i'll have a link in my references later it's a very common error it's not just for testing it's also a production so what is happening here what it's saying and my cat has joined us i'm sorry about that uh there she is i'll put her down we'll see how much she bothers me uh what this is saying is when it tries to generate the dashboard component it runs into a problem because it can't find the other things it needs and this is something that comes from the fact that angular has this component decorator in addition to the things i already talked about the component decorator is special in that it means not only that the dashboard component can be injected into other things that have an at component decorator or other decorators but also that angular will take responsibility for the constructor to inject other things into it in this particular case what it means is that we have this constructor running of the hero service and in our test we never actually run the constructor there's no new dashboard component because we're letting angular do that for us angular's dependency injection is taking responsibility for that and so when we ask it for a dashboard component it needs to run the constructor and in order to run the constructor we are saying angular your dependency injection system is responsible for providing that hero service to us but angular doesn't know what the hero service is so why is that if we look at if we look at the production module um we can see that you know it doesn't specifically ever mention the hero service in here so why are we running into a problem when we try to load in the hero service for our test and the answer is something that you can see in this error message it's not actually the hero server so it's the problem it's the http client so what's going on there so two questions why does it need http client at all and then why is it okay in the production but not the test so let's look at the first question first so if i go back to this component what happens is we are telling the angular dependency injection system it's up to you to provide for me the hero service that i need so angular will go try to create a hero service now it can find a hero service because hero service itself has a decorator it has a special decorator of at injectable and add injectable is the decorator that we use for services in angular it's kind of like a subset of the component decorator it doesn't interact with all the other files it doesn't do the selector but it does mean the same things for dependency injection that this component can be injected into a sorry the service can be injected into something else or other things can be injected into it further because it says provided in it doesn't need to be explicitly mentioned anywhere some things in angular need to be explicitly registered in a module and in angular it used to be true that services needed to be done that way but since i think angular 6 most of our services are provided in provided in root which basically means if anything ever requests a hero service in its constructor as an another injectable or component just create one and give it to me and actually it'll only create one in your entire app will just be one and it's a singleton so first time create one and give it to me and anytime after that just give me the one that you've already created so that's what's going on with this error message it's trying in order to build put in the dashboard component it's trying to create the hero service and then it's running into the problem because in order to create the hero service it needs to bring in the http client and the message service now the message service isn't a problem because it it also is provided in root so it is automatically generated without any trouble but http client is where the problem is so http client is a service that comes from angular itself and angular has decided not to make its the angular team has decided not to make their services provided in root because they want the angular initial space to be small that you need to intentionally add services so we need to intentionally add that service now if we go back to the production module where did we add it well we never explicitly provided just http client but we imported the http client module and that is a module that includes a lot of things including http client and so by importing that into our production module it was available to our production component well we don't have that production module we have a special module so it's not available to our component in our test and that's what's going on over here in the four eaches the first before each uses this special part from angular testing testbed comes from angular core testing to create a module specific to this one test so we're not using the production module for this test we're using our own module that is specific only to this test and when we create it we configure it according to what is passed into the configure testing module function and so we're and so by default all it knows all that angular sets up when you create one with boilerplate is that it should have the dashboard component declared and it doesn't know about anything else it doesn't explicitly know about hero service but hero service is automatically provided by provided in that's fine but it also doesn't know about http client or http client module and so we need to tell it to use it here so we need to set up this component with the things that are sorry i set up this module with the things that our component will need to know so that our component will be able to run and if you follow the pattern from the production module you might think that the first thing that you should do would be to import the http client module because that works in production that's what should work for us in testing right well almost it kind of works but let's go back to our test so yeah the test is green now so it looks like it works and if you didn't look at the actual console log you might be tricked into thinking it works but if we look at the console log we can see a problem it's actually trying to make the http call because we're using the real production http client so uh a real production http client so that is um trying to do the real production http client so we don't want that we want to use a special one that is only set up for testing that mocks out the real things um so uh someone asks uh is there some questions in the chat if you have questions that you want me to answer please put them in the q a session in in the uh sorry i'm blinking a q a session and zoom and then i'll come back to them when i get to q a i'm not going to have time to really look at chat while i'm going but if you put it in a question q a i'll definitely take a look at that um so anyway back to this we have we're using the real http client so it's trying to make a real call and we can't do that in our test our test is supposed to be isolated from that fortunately angular gave us a way around that instead of using the real http client module we use the http client testing module now this has the exact same shape as the real http client module but it's set up with a bunch of mocks for testing there's a whole bunch of stuff you can do it with that that i'm not going to get into in this session but there's a lot that you can do to mock out your http calls and see what actually was sent with your http calls etc that's beyond the scope of this but this is the start of what you can do for http testing and now that i've done that my test passes and that warning message goes away there's another warning message down here that i'm going to get to next let's ignore that for the moment but the fact the improvement is that the warning message goes away and my test is green however i'm not really happy with this approach and the reason i'm not happy with the approach for this component test is that this component if we go back to it doesn't know anything about http client the hero service is what knows about http client the component itself doesn't so we are writing a test for the component that has parts of it that depend on the implementation of something else well what if that other thing changes what if we rewrite hero service so that it uses web sockets instead of http well then the unit test for this dashboard component will break even though the dashboard component well nothing within it will have changed that's a problem our unit test should be focused on the code that is in the unit that we're testing and we should really try to eliminate other dependencies so this works this gets us to a green level but i want to do something more now the problem we get of course is that the hero service is what uses the http client so we need to change out our hero service instead of using the real hero service we want to fake the hero service so i'm going to show you how to do that at least in karma so i remove that we come back and we'll see that we'll get our error again okay actually you know other errors yeah it's just everything's horrible what i want to do now is what is the old school way of doing things in angular with services so i mentioned before that with provided in you don't have to explicitly provide a service this is the way we used to have to do it and this is the way that we're going to do it here within our test now just doing that explicitly providing what is implicitly provided doesn't get us anything but it's a step in the right direction because what we really want to do is provide a copy a fake a mock for the service the code that we have here now is saying all right angular when my dashboard component loads that i'm declaring here you have the responsibility that if it's constructor includes a hero service you will provide me the hero service in this case it's saying you will provide me the real hero service but i can use a different syntax to provide a fake hero service so i'm going to say all right angular dependency injection system when you are when my component needs a hero service i want you to provide not the real one but i want used to use a value and so jasmine which is the one of the testing tools that comes by default with an angular cli project jasmine has a nice utility called create spy object so this is going to create a fake version of the hero service for us and inject that so now when our test tries to load the dashboard component it's going to get here and say all right you've asked me for a hero service because that's in your constructor instead of giving you the real one i'm going to give you a fake one and then with our test i haven't talked about this before but the way that this is going to work is it's not just going to be running this one file it's going to be bringing the typescript file and the html template file and the css styling file it's going to combine them all together compile them into a single angular component and going to run the lifecycle for us it's going to do all that for us which is nice then over here in the other part of the book for each we use the fixture as our way to get that fully compiled component out so we we're not going to get the dashboard component itself as just this we're going to get the fully compiled component that has all the styles and the html and everything together that's what we will get out from our testbed from our dependency injection we will be able to then get the individual component out see if it's truthy and then the fixture is going to detect changes so let the change detection cycle finish and settle down and when we're done with all that we will have an integrated test and it's not going to work it's close we are in a better shape now but it's not going to work yet the reason this doesn't work yet is because of that life cycle that i just talked about so when we compile the component it doesn't just call the constructor the constructor actually passed but it also called ng on init because that's in the life cycle of the component and when it calls ng on init it calls get heroes which calls the get hero service and calls tries to call this function well we created this spy here there's probably not that much in there let's see what's actually in there so it created that and i can see in the console log here at the bottom that service is nothing it doesn't have anything it doesn't have a get heroes function so we need to tell this that we want to be calling the get heroes function unfortunately there's an easy way to do that i kind of uh wait hand waved over it but when i created the spa using the create spy object the argument the first argument it takes is a string array and i just put in an empty array because that was just enough to get it to an empty string because that was just enough to get it going but what i really want to put in here is a list of all of the functions that i want this to create for me and so now when this runs again it gets a little bit further it's no longer saying that get heroes is not a function you can see from the console log that get heroes is a function in this object so we're further along but the error that we get now is cannot read property subscribe of undefined i'm going to call that function on here get heroes and you can see that by default it returns undefined so we we have told our spy that it want needs to have a function for get heroes but we haven't told it anything that we want it to do and so in our code it gets here it gets to uh our git heroes and then it returns it undefined and it's trying to do observable things on undefined and that's when it chokes so we need to change this so that it doesn't just return a f object that has a function that doesn't do anything we need to tell it to do things so this is going to get a little bit more complicated so i'm going to extract from here my hero service spy and that's what i'm going to use in here but now i'm going to do some functionality on this i want to use this spy to tell it what to do with get heroes but unfortunately when i hover over that you can see that the type it infers is any it doesn't give me anything well that's unfortunate but the fortunate thing is that's a very tiny hurdle to get over because create spy object is parameterized i can give it a generic of the type of thing that it is spying for and now when i hover over it you can see it's inferring that it is a spy object of hero service so now i can come to my hero service spy and it has things on it it knows that it should be able to deal with a get hero get here uh heroes all these other things these are functions that are on the actual hero service and we have told the spy these are the things you should be ready for without that generic it wouldn't have worked so now what i can say is all right because i've put this in that array you have a get heroes function for that i want you to return a value and the value i want you to return is an observable of an empty array so this of is the simplest way to create an observable so since the real get heroes returns an observable of an array i'm going to tell it to return an observable of an array there's a lot more you can do in here it's i don't have time to go into all the things you can do but this is a simple thing that you can do with that so now if we come back to here we can look at our test we can see that the service when it's called it actually has a heroes it's actually returning an observable our test is green and we don't have any problems with our hero service so we have gone over that major hump we are using dependency injection from the test perspective to get that information in there so i'm going to take a little break here we're not done with fixing things but just want to take a break just for those of you who didn't come in when i said something earlier this is not the only webinar we're doing we have another webinar coming up in a few weeks on ngrx right after this session we're going to have an angular clinic where you if you have any problems with angular that you can go ask experts from our company for free advice on how to fix your problems if you go to oasisdigital.com training you can see all of these things other webinars meetups uh youtube video sessions a whole bunch of stuff that we provide a lot of it's for free some of the bigger ones specifically our training classes are not free but a lot of the stuff is free so getting back to our goal remember our goal is to first fix then expand and simplify we have gotten it mostly fixed well partially fixed things are green but there's this problem down here that i told you we'd come back to it's time to come back to it so what's going on here saying that app hero search is not a known element remember a few minutes ago then i said that that some of these errors angular as of um the most recent version will give you the ability to go search on them that's what i meant here this ng0304 that's a searchable code now and you can go find more information and a video on on what's going on with this but what's going on why are we getting this app here a search is not a known element there's no reference in our code here to anything like that and we're getting it because as i said this isn't just looking at the typescript code it's looking at the entire component and if we look at the html we have a sub component called app hero search so this when it's trying to compile and use the entire component it's trying to build in even the sub components and in angular when you have a component you need to tell the module what you're doing and that's what this these error messages are here it says that they're two different ways to fix uh this problem i'm actually going to talk about four different ways two of them are good two of them are not so good one of those is really bad i'll talk through these the first thing if apparel search is an angular component and in this case it is then verify it's part of this module well remember we're creating a module that's specific only to this test there's no reference at all to that here in our actual production module here's a reference to it we declared hero search component as part of the production module so the dashboard component can use it we don't have anything like that in our testing module so the simplest way is just to add that declaration and this works this solves the problem i'm not entirely happy with it though for the same reasons that i wasn't happy with just bringing in the http client module i'm not happy with this because in both cases work allowing dependencies to other things affect our test so in this particular case it wasn't a problem it was easy to add but what if hero search component had other dependencies it had sub-components or brought in other services or something now we're responsible for the things that hero search component has in order for this test for something that's not a hero search component to work that's a bit rough now there are advantages of doing it this way if you can see under the test bed in this karma that's running we actually have the hero search it got added we're actually seeing the application run on the fly here like we we do we'll see the real application here and that's kind of cool but it's not worth it for me so i'm going to take that out and let's look at the other suggestion it gives the other thing it says is if your search is a web component then add the schema to your schemas so this is for the fact that not all angular not all the components in an angular component need to be from angular this is another production feature now most of our components in an angular application are going to be angular components but you can bring in a web component that was written in stencil or view or react or whatever and bring that into your component and an angular module won't know anything about it it'll have no idea how to deal with it and if that's what you have in your production then you can add custom element schema to either that component or to the containing module and we can do the same in our testing module so if i come over here i can add line schemas custom element schema and now what that's really doing is saying to our system hey if you run across a sub component when you're building up this component and it's something that you don't know about don't worry about it don't throw an error don't try to bring it in just ignore it we do lose having the component actually brought into our component for testing but if we're not actually going to do anything with it that's fine we don't need it and so it insulates us from that problem and just removes one of our errors so this is the approach i like best there's another approach that is not quite as good but that you'll sometimes see where you can put in a different schema called no error schema and no error schema is similar to custom element schema except it's a much heavier hammer and it says no matter what error you get if it comes from the template ignore it so that can hide actual problems if you're writing a test to actually do something on your template and so i don't like that it's it's a last resort the video uh from the angular team where they talk about this really refers to that as the last resort they don't even mention the next thing that i'm going to mention because this is a mistake that i made once and i really don't want you to make the mistake one point when i was looking at these things i said you know what i'm kind of tired of having to bring in all these other declarations and schemas and providers and all of these different things when i'm building up my test module i've already done that in my production module why don't i make use of that i could just import my actual production module and then i'll get all of that stuff for free and i won't have to do anything and i can save a good five seconds of typing each time don't do this this is a mistake when i did this before i added this and as i said it saved me a few seconds of typing but the module that i was bringing in was a large module it was 50 of a particular application that wasn't particularly well uh architected and it to load that one module took about half a second three quarters of a second something like that and the first time i wrote a test i didn't notice that problem but it grew into a big problem because what happens is this is this before each is run before each individual test and so that was getting loaded every time the first time i didn't notice the half second delay but eventually i wrote a bunch more tests in that file and then i used that file as a pattern for a bunch of other files and eventually i was wondering why are our tests taking 10 minutes to run and i looked into it and there wasn't any individual one test that was the problem it was that every single execution that was running was taking half a second three quarters of a second to load a huge amount of our production when our tests were focused on one little bit of our code so in order to save five seconds of typing i cost myself minutes upon minutes on hours over an extended period of time and when i took those things out the running time of the test we dropped from 10 minutes to 50 seconds so don't make that mistake all right so we have here our test that's working we have no errors in our console we have no errors running live our tests work we overcome overcame the hurdles we overcame the null injector error that was in the hero service to the http client we figured out how to bring in a spy object to handle that we dealt with the sub component that's great so that was a lot of work though why is that even necessary what is that for so now i'm ready for the second step i'm going to expand i'm going to show you what this solves for us or what this enables so looking back at our component i want to write a unit test here so this component like looking at the behavior that it has here this should have heroes so i'm going to expect that the component heroes length is 4. because if i look here this is going to slice out four of the heroes from the original array and show them so i'm going to expect that that has worked and i'm also going to expect nevermind i'm not going to do that so that's why i'm going to test and my test now fails and the reason my test now fails is that we're not actually passing any heroes we're slicing four out of zero and so that doesn't work so i need to pass in some actual heroes for this to work fortunately in this particular code base i know that there's an array of heroes that i can use it has 10 heroes so that's going to slice out four of them but now this actually brings up another problem now that there are actually heroes there's going to be there's a problem another problem in my template you see here can't bind to router link since it's not a known property of a if we look back at my html there's router link so what's happening is router link is not a built-in part of html it's an angular thing that works if we have our module set up to be able to use it but our module doesn't know anything about that if i look back at the real module this brings in hdb uh router module which imports router module so one way to solve this would be to import the production router module but again as before we don't want to use the real production version we want to use the testing version and this gives us a whole bunch of power that i'm not going to get into today of things that we can do to mock out routing and with that now our test is fine so this is yet another little hurdle that we had to jump over because of another dependency to set up but now you can see like it's actually loading in the heroes that's great now let's write a test to see that these heroes are in the dom and this is where the fixture comes in so up until this point the only thing that we have used the fixture for is to get the component out of it out of the fully compiled version of the component but i want to write a test now for the dom and that is where the fixture comes in because this component here it doesn't show what's on it doesn't have access to what's on the browser it really only has access to the typescript part of things but now i want to test stuff from the html side and that's what i need to use the fixture for so what i want to do is i want to expect that the fixture has four of those links in it now there's a lot to this api i can't explain it all now but i'm just going to show a little bit of it that i can query all of that debug element by css and now i need to import the right one make sure you import the one from angular not the one from protractor i want to import css and get its length and i expect that to be four so i expect in the dom this will actually create four of these links and it has done that and i have my tests and everything is good that's the expand part of this talk that is why we did this that is why we had to jump over those hoops and that is why the angular community our angular team when you create a cli and initially gives you the test bed and the fixtures and all of these things it's so that you can write tests like this so you can write what they call integration component tests that give you the ability to go into the um into the dom into the the browser and if this is the type of test that you want to write we are done because we needed to set things up to get to this point in order to be able to test in the dom we need the fixture in order to have the fixture we need to have the test bed in order to have the test bed we need our dependency injections all set up we need each of these steps along the way however we're now at the simplify phase because not every test needs something like this in fact i'd never write tests like this i never use the fixture because when i want to write assert things that are in the dom i use cypress could also use protractor but personally i use cypress which is designed for testing things in the browser it pops up in the browser runs the browser and use them that's what i use instead so i would never write that test so now that i'm not doing that with the fixture are there ways to simplify that yes so let's look at this test before we go further with the simplification let's take a look and we can see here that it took .085 seconds to run these two tests remember .085 we'll come back to that all right so what are we using this fixture for well before we were using it for both getting the component out of dependency injection and doing the dom stuff we're not doing the dom stuff anymore we're only using it to get stuff out of dependency injection do we need the fixture to do that you know here we are using testbed.createcomponent to get the fixture and from that getting the instance is that the only way to get something out of dependency injection in angular no it isn't because i could write this i could get my service i could inject the hero service and there it will pull the hero surface that dependency injection is using out the thing is i can do the same thing with the component that i did for that here service so we write stuff like this testbed.inject a lot for services but you can also do it for a component the component decorator is a superset of the injectable decorator so because you can do with injectables you can do with components so instead of using the fixture to get the instance out i can use inject and then i have an instance of the component that can be used here for my assertions and then i don't need the fixture now the thing is when i'm doing this by not using the fixture i am no longer using the component and the the styling in the template and compiling them all together so i don't need compile components well and since i'm not doing the template i don't need to import router 10 testing module because that was only for the use of router link in the test in the template and if i'm not using the template at all if i'm really only testing the code here that's in the typescript i don't need that anymore and i don't need the schema anymore because that was again only to deal with the sub component but the typescript code doesn't know anything about that i can get rid of that look at how much code i've been able to get rid of now there's one other thing i need to do here i'm declaring that component as though actually two more things first i'm declaring that component as though it were a component well i'm no longer using it as a component i'm using it as a service so i put it in the provider's array like i do with other services and now i have access to that so let's see does that make our test work well no our test is broken again and that's the second thing we have to do and the reason that our test is broken is that there was one other thing that compiling the component did for us besides bringing in the different files together and that was it ran the life cycle for us it automatically ran ng on init for us and the other life cycle events we've lost that but fortunately that's easy to get back because i can explicitly manually call ng on init and now all of the same typescript code is being called as before i still have the same test that's asserting all the same things for the test and instead of taking .085 seconds it took .038 seconds and took about half the while not half a lot fewer lines of code so i showed you before how to get over those hurdles because of dependency injection and all these other things and i'm showing you you can avoid it now again if you are writing a test that actually uses stuff in the fixture this is not available to you you need to go through all of those steps to do things and you need to bring in your schemas and router testing module and all those other things but if you're not doing that if you're really just testing your your typescript code you don't need that you can do this thing is we can even go further though because we're using this testbed what are we using it for we're using it for dependency injection all right what is dependency injection doing it is giving us that hero service where's dependency injection getting that hero service from us well let's just cut out the middle man instead of using the dependency injection system to get it and instead of treating that component as though it's a service how about i treat that component and so it's a class because a component is a class and now i've gotten rid of even more code and my running time is 0.22 seconds about a quarter of the running time of the original way so if you can treat your components like a class if you have any sort of dependency injection if you're bringing in an activated router http client you can't do that because angular only provides those through dependency injection so if that's your situation you can't do it as a class but you can treat it like a service so either do it like test it like a class if you can you can't do that test it like a service and if you can't do that because you really want to test things in the dom using the fixture then you have to go over those hurdles you have to do all of those things that we set up in advance but you can really simplify your life this type of testing is called by the angular team an isolated unit test an isolated component test it's a totally valid way of doing it but they set up the boilerplate using all of the testbed and fixture and all that because the most advanced versions of component testing need it but you don't always need it you can make your life easier by testing your component as a component or as a class or as a service when you can so we fixed the test we expanded it we simplified the test greatly when i went through these steps for one of my old clients i was able to reduce the running time for one of their test suites from two minutes to seven seconds it's totally worth it so that's my talk that's the webinar uh here are the references i'm going to tweet out a link to this later so you can grab these urls from my tweet my twitter account is lmfinicoder i have the repo and the repo is what i use here where i start from scratch build it up and then break it back down to the simpler version i also have a link to the video from the angular team on the null injector error so um i'd love to know what questions you have for me and so now i can take a look at uh the q a section so um so yeah please if you have any other questions add them to the q a section uh so first question from indira aren't there any global settings or files all modules required can be imported for all the tests no there's not they intentionally don't do that because they don't want to have you loading in everything for all your modules they want your modules to be lightweight to be quick for your users to be able to download them if they did what you're suggesting it would really just add a lot of stuff the closest thing that they have for that is changing how services work by doing the provided in so that they're automatically made available by making them automatically available like that by automatically registering self-registering them then it is available whenever any service needs it um but then if it's not needed when it's time to bundle it to send it down to your clients it will be metaphorically tree shaken they'll build up a list of all of the dependencies from the root to all of your different components and services and see what actually is used and shake it out but if you do anything like this to automatically provide your modules tree shaking will break all right next question um we use cypress to test templates and only unit tests without testing tumble yep okay sometimes unit testing components without test bed and only testing components doesn't show full coverage and a comment it instable ignore next is needed at the end of the component is there another way to have full code coverage on a component without test bed in istanbul nor next um the way that i the test coverage that i use um i have not seen that problem um so i'm not exactly sure a great answer to you um i will ha i have seen things along the lines of like an ng on destroyer or one of the life cycle hooks won't necessarily get called um and so that particular function or method might not be called or show up in the code coverage whereas um it would be if i use the integration style with the default compile components and all that um but now i mean that just tends to be little bits of code and our system allows you know says all right that's you know two percent that's fine so no i don't have a good answer for that question i'm sorry so what is an example of where you would need the fixture or using cyprus as you described eliminate the need for it would fixture so be needed for things like components with viewchild so cyprus solves a lot of the problems there are specific things that you can do in integration testing with uh the fixture um i as i said i haven't done much of it uh because i tend to focus my unit tests for the typescript code and then cyprus to do the behavior uh you will need to do uh if you have stuff in their components with viewchild you will need to have a more involved system a more involved dependency injection you will have to put something in there of your declaring the other components because that is actually a runtime dependency of your typescript code to have access to that uh so um so yeah there are times that you will need to bring those things in but i um but i don't believe that you need the fixture to do that you you could just uh refer to them in your code what's the kitty's name the kitty is cleo um uh hey joswell i haven't seen you in a while uh how important is to add the correct types to mock service objects versus using any so i believe what you're asking and you can clarify if i'm missing it is what if i hadn't put this in here this code will work just as well and if i show you the test the test will pass everything's fine it's just it's not as useful to me because if i click on here i do control space to try to get some intellisense and and vs code has no idea what's in there so if i know exactly what i'm typing i can get there in the right spot um everything will work out okay however if i put in that typing in there so i get the right thing now intellisense is smarter and i get a lot more help with it um so i those so those are five questions that we've i've had so far i'm going to take a look at the chat to see if there's anything else so someone 20 40 minutes ago asked if you're supposed to be seeing something on the screen i hope you figured out zoom and were able to see something on the screen because i did share quite a bit as i said i'm going to tweet this out in just a few minutes so we'll have a link to the slides so that from there you can also take a look at get a link to the repo so you can look at the code there and have that example the video of this webinar will be up sometime soon i i don't know when there's also a link here to an angular team video on one of these errors uh any other questions that we have now before we're about to run out of time let's see someone asked a question are there any special things that need to be done for testing event driven code like rxjs subjects or something yeah that is a whole other topic um so yeah there are special things that you need to do testing observables can be kind of difficult i'm going to provide a link here i'm going to show something uh that i use as a test bed and it's from my company so you'll get a chance to see that uh that i make use of sometimes so angular boot camp as i said is our three day training course and most of our curriculum is provided for free if you go to this url and i'll put it in i'll put it in chat if you go to that url you can see most of the curriculum that we do for this class including some extra things that we often don't get to and that down here in that area in step 700 26 we have an example here that i can click on the stack box link pop it up in stack blitz and then you can see some examples of how of some tests that are written that use uh fake async and subscribe and a couple different approaches to writing tests for rxjs code this is only a few examples it is not a comprehensive discussion about it it doesn't go into depth on jasmine marbles and all the other things if you want to get more of that information we do have a some classes that are specifically on rxjs we have some classes that are specifically on angular testing in different ways so that we can go into that in more depth but at this point in the context of this meeting all i can basically say is yeah there are special things um and i can show you a couple examples and joswell answered do i recommend marble testing for all observable related tests no i don't not for all because it takes a while to get used to marble testing and for those of you who have no idea what i'm talking about i'll pop up an example so this is just the first blog post that i've seen i've looked at this before um it's it's a way of writing tests for uh observables where you can say i want you cr starting out with an observable that has this particular shape where it emits hello and then world i expect that the result of it is going to be something that looks like this so it's a way of using this mini language to define how your observables and what order they'll be released in and stuff like that it's very powerful and there are some things that i have only been able to test that way but overall it's more complicated and i find them harder to read so usually i will try to find a way to subscribe to my observable and assert the values that come out of it if it's a single shot observable i'll in my test i might convert it to a promise and then do an async await on that to assert what is in the value that comes out of it but there are some situations uh where you're if you're testing multiple values that are coming through or if you're testing um a a stream that has a filter on it and you want to make sure that nothing ever actually gets emitted from it things like that i've only been ever really ever been able to test it with marbles so an anonymous attendee added details of the question on angular nation pm i will i'll take a look at that later and i think at this point we are out of time so i hope that every everyone got something out of this
Info
Channel: Oasis Digital
Views: 445
Rating: undefined out of 5
Keywords:
Id: xJ45MGDAi6c
Channel Id: undefined
Length: 57min 5sec (3425 seconds)
Published: Fri Apr 30 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.