Angular Test First Development
CC Attribution 2.0 Belgium:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor.
Pasteur, Florian
How to write Angular components that can evolve ? How can we write components that can easily be refactored ? How can we write tests that won't be useless as soon as we change the implementation ? We can achieve all that by writing the tests first, writing tests that are agnostic of the implementation, so writing tests that focus on features. When it comes, refactoring an Angular component can be delicate especially if the refactoring breaks all existing tests. Adapting the tests while refactoring is always a risk, it can make the tests useless. So how can we write tests that aren't coupled to the implementation ? We can do it by writing the tests firsts, if there is no implementation, tests can't be coupled. Let's see how we can write theses tests without knowing how the component looks like.
 Hello and welcome. Thank you for joining. I'm Florent. And today I would like to talk about Angular Test First development. So I would like in this to show you a way of writing your test before the implementation of your Angular component in order to make sure that your component can be easily refactored and that your tests are focused on the business of your component, the logic that you want to implement and not the implementation itself. To do so, we're going to write this small component, which is a drop-off point selector. So you can switch between pages and show the location of a point into the map. Okay, so let's get started. So the component is completely empty at first. So the template is empty and the controller is empty too. We just have the input to retrieve to get the data. And in the test file, I only have one function to generate the drop-off point data. And I can just overwrite you properties of this data. Okay, so let's write the first test. What's going to be the first test? If we try to write the first state without any specific code, what do we want in terms of feature? The first thing I want is the list of the location with the name of the location. And I want only the first page because I will do the pagination at the same time. So let's name this a test should print one page. And one page of location. So let's summarize what we want in the given when, then so in the given, based on a certain list of points, I want when it renders. And then I expect, let's say five, let's say we want a pagination of five locations with names A, B, C, and whatever. So that's going to be our first test. Given a certain list of location, when it renders, I expect to have a subset of the list, the first element, and I expect to have the names. How can I write that since I don't know how the template is implemented because I did not implement it yet. So before thinking about the template, let's just create the test data. So in the drop off point array, I'm going to just generate some data. So to generate data, I'm going to use the function I defined before. So it's just a builder. So thanks to the drop off point builder, I can just overwrite the name. I don't care about the other data for the moment, whatever. And I want to build that to have just the value. Okay, I have some mocks here, which allow me to, yeah, I'm just going to copy paste the name of a few names. I'm going to take like seven of it. One, two, three, four, five, six, seven. Yes. Okay. So given this list of location, when it renders, how can I define it renders in the test way? It's simple. Angular give us something for us, which is the text changes. So we need to tell Angular, yeah, detect some changes and render the template. So it's when here. And now the tricky part, how can I retrieve the data from the template? I don't know how the template looks like. So in this case, I will need a contract with the template. I don't know how it looks like, but I want at least to define how I want to retrieve the value, the value of the name of each location. So to do so, I'm just going to assume that the HTML element that will have the name will have a certain class. And the class I want to use is going to be, let's say, the class drop of point name. I want this class. So now I want to retrieve all the elements in the HTML that have this class. So to do so, I also have something in Angular, which is in the debug element. I can query all. So in the query all, I can give a predicate, so a way to filter the element I want and the element I don't want. And I also have something to do it easily with Angular, which is a buy. And when I say buy CSS, I can use a CSS selector to determine whether I want or not the HTML element. So I'm going to write it as a class selector. So I'm going to use a dot and say drop of point name. With that, Angular will give me every HTML element, every not HTML element, every element that has that have this class. So it's going to give me an array of debug elements. But what I want is not an array of debug elements, but an array of HTML elements. So I need to transform that into HTML elements. So to do so, I'm going to just iterate over it. So I'm going to use a reform to encapsulate that. And in my array from, I'm just going to map it. And for each debug element, I'm going to retrieve the native element of it. So debug element, native element. Okay. Not arrays, but array. Okay. Perfect. That's why. So with this full line, what I will have is an array of HTML element. As my bad, an array of HTML element. But that's not only what I want. What I want is the value of that. So first, I'm going to just extract that as a function, because I might use it later. I will call it find all. And I'm going to put the selector as parameter. Okay. String. So I'm going to find all. All drop off point name classes element. Let's return that. And it returns an HTML element. But as I said, I don't want only the HTML element, but I want the value of it. So I'm going to map again. And since this is an HTML element I receive here, what I can do is from it, I can say I want the text content of the DOM element, the real DOM element. And since it's HTML, it can have a blank space between and at the end and at the beginning. So I'm just going to trim that. So by querying the template like this, I create a contract saying that I want to retrieve each element marked as drop off name, and I want the value. So these strings, these strings are the drop off point names. And now, thanks to this array, I can do some expectation of it. So I can expect that the drop off point name length to equal like five, because I want only five locations at first. So I don't want all of the location here, but only the five first. But at the moment, I only verify that I have five. So I need to verify that's the five first. To do so, I'm just going to say that the drop off point names contain to contain. And to contain, for example, Albert. I don't use to equal and having an array after because I don't care about the order of the list here. So I'm just using to contain, which is more flexible. So let's do it for the five, the four remaining. Oh, yeah, I have multiple time. But yeah, the idea is there. One, two, three, four. And so I missed this one. And I also want this element. Okay, so now I expect correctly that I have five element, five location name somewhere. Okay. Okay. So next test. So next test going to be almost the same, but instead of just having the element to render, I want to go to the next page. So let's almost copy this test because it's going to be almost the same should print the second page of location. And this time, instead of just having fixed your test exchange, I want a way to click on the next page button. So I want an element with the click. So I need to create a new contract. So I need to create a new function find because I don't want to use find all because I only want one this time. And I want to find some things that have the next page marker, let's say. So this function is going to be almost the same as the find all. Let me put that below and let's create the find function. So the find function is as defined, but instead of querying all elements, I'm going to query only one. And after I'm going to use the native element, create the native element. So this way I have defined as simple as defined. Okay. So I have my next page. So this next page is something that is an HTML element. So I don't know. Finally, I don't want to use a var here. I just want to click on this element. Oh, my bad. It's not going to be an array of HTML element. That's why the completion is not correct. Yes. So I want to click on it. And I also want to detect that the change is off Angular. So I need to rerender the template again. So now I've done the next page. I don't want to have five elements, but only two. And it's going to be the two remaining elements here. So let me change the assertion here. Okay. Okay. Let's save that. So now we know how to next page. So from the first page, I know how to go to the next page. We verify that the names are correct. So let's do the same for the previous buttons. It's going to be almost as the same as the first. The first test, but instead of just detecting the changes here, I'm going to do the same as next page and also do previous page. So I'm going to do next then previous to make sure that, yeah, it's not because it's print at first. Let's let me correct that. Okay. So we have quite some tests now. We can maybe start by writing an implementation for that. So let's go to the template. So I'm going to use a table and create a new row. And for the new row, I'm going to create a new cell. Okay. And I'm going to do a for each and before let drop off point, drop off point of drop off points. I want to print the location name drop off point singular location name. But I still need to say that in the template that yeah, this is where the name is located. So I need to add the class here saying that the drop off point name is here. And I also need two buttons. One saying that this is the next page, next page and same for previous, previous page. Okay, but I also need to implement the page here. So when I click, I want to do next page, create this method. And I want to page to be plus equals one. So let's create this field. It's a number starting from zero. And in the previous page, previous page, this, let's get to that, create that this page. And minus one. Okay, let's have a look to the test. They were failing. Forgot to show you but yeah, that was obvious. Oh, yeah, I forgot something. I forgot to use the pagination here. So what I want is I want to slice, slice page times five, two, page plus one times five. Okay. So let's have a look to the test. Test on our green, that's cool. And we can have a look at the implementation. The next page is working the previous page. It's working perfect. So now we want something to select a point to show on the map a point. So what we're going to do, let me commit that first. The implement page in issue. Okay. Okay. Okay. Next test. Next test. Now we want something close to that. So in this test, I don't want to verify the name. I don't want to change that. I just want to have the same value. And instead of having the name here, I will also override the value of the coordinate. We've longitude here. Let me find some data. Okay. Okay. In the spec. Okay. Perfect. That's the last one I could be. Yeah. Perfect. Okay. So now I have points with a certain coordinates. And I can verify that when I click on show on the map of this element, it's going to show me on the map what I'm looking for. So same as before, we want to first render the list. And after I want to find the button show on map. So new contract. I want an element clickable that have a show on map. Show on map. And when I want to click on this one, let's use a find all, by the way. Okay. And for example, click on the first one. Then I want, then what I want, by the way, yeah, what I want. What I want is now I want to have a map shown with the location, the coordinate of the location, the address. I don't want to do it in this element. So I'm going to delegate that to the map component. So I can do the fixture, the text changes here, by the way. And now instead of finding an element with a selector, I can use a different kind of contract saying that instead of querying by CSS, what I can do is querying by directive like this. And I can say I want to select a map component. And I don't want the native element because I want the Angular element of it. So what I want with that is the component instance of this map component. And this map component has a certain contract with me. It has some latitude and some longitude. So when I give these two values to the map component, I know that the map component will display it correctly. So I don't want to verify if it's correctly displayed. If I give to the map component the correct data, it's going to be correctly displayed. So what I can verify if I do that is I want to expect that the map component latitude is equal to the first latitude. So this value and the longitude to equal to this value. And by doing that, I'm sure that it will, when I click on show on map, it will render the correct way because I delegate to the map component the fact to show on the map. So it's not over yet the test because since I have a new component, a map component, now I need to declare it in the test bed. So let's just add it here. And now I can check that the test is failing. Yeah, I have a correctly failed test. So now let's implement it. I want to have a map component here. So it's going to be an app map. I want some latitude and some longitude here. And I want which latitude and which longitude. I don't know yet. So map, location, latitude and location, longitude. I can create this both element, public and do the same for longitude. Okay, so also what I want is now is a button that will show on the map. So let's create a button here. I need to respect the contract by saying that the class is show on map. Show on map. And when I click on it, I want to show on map this location. So, okay, I'm going to create this method. And this method will simply assign the latitude of the drop of point and do the same for longitude. Let's verify the test. Did not miss anything. Okay, test is green. That's a good sign. It means that, yeah, when I click on show on map, it shows on the map. Perfect. Let's commit that. Okay. See how we can write the test before the implementation and be sure that it's correctly displayed after. To summarize, this is a way how you can do some template contract in order to test your component. I hope you enjoyed and learned something and see you right now for some live question. And thank you for your attention.
 Hello and welcome. Thank you for joining. I'm Florent. And today I would like to talk about  Angular Test First development. So I would like in this to show you a way of writing your test  before the implementation of your Angular component in order to make sure that your  component can be easily refactored and that your tests are focused on the business of your  component, the logic that you want to implement and not the implementation itself. To do so,  we're going to write this small component, which is a drop-off point selector. So you can  switch between pages and show the location of a point into the map. Okay, so let's get started.  So the component is completely empty at first. So the template is empty and the controller  is empty too. We just have the input to retrieve to get the data. And in the test file, I only have  one function to generate the drop-off point data. And I can just overwrite you properties  of this data. Okay, so let's write the first test. What's going to be the first test?  If we try to write the first state without any specific code, what do we want in terms of  feature? The first thing I want is the list of the location with the name of the location. And I  want only the first page because I will do the pagination at the same time. So let's name this  a test should print one page. And one page of location. So let's summarize what we want in the given  when, then so in the given, based on a certain list of points, I want when it renders.  And then I expect, let's say five, let's say we want a pagination of five locations with names  A, B, C, and whatever. So that's going to be our first test. Given a certain list of location,  when it renders, I expect to have a subset of the list, the first element, and I expect to have the  names. How can I write that since I don't know how the template is implemented because I did not  implement it yet. So before thinking about the template, let's just create the test data. So in  the drop off point array, I'm going to just generate some data. So to generate data, I'm going to use  the function I defined before. So it's just a builder. So thanks to the drop off point builder,  I can just overwrite the name. I don't care about the other data for the moment, whatever. And I want  to build that to have just the value. Okay, I have some mocks here, which allow me to, yeah,  I'm just going to copy paste the name of a few names. I'm going to take like  seven of it. One, two, three, four, five, six, seven. Yes. Okay. So given this list of location,  when it renders, how can I define it renders in the test way? It's simple. Angular give us something  for us, which is the text changes. So we need to tell Angular, yeah, detect some changes and render  the template. So it's when here. And now the tricky part, how can I retrieve the data from the  template? I don't know how the template looks like. So in this case, I will need a contract with the  template. I don't know how it looks like, but I want at least to define how I want to retrieve  the value, the value of the name of each location. So to do so, I'm just going to assume that the  HTML element that will have the name will have a certain class. And the class I want to use is going  to be, let's say, the class drop of point name. I want this class. So now I want to retrieve  all the elements in the HTML that have this class. So to do so, I also have something in Angular,  which is in the debug element. I can query all. So in the query all, I can give a predicate,  so a way to filter the element I want and the element I don't want. And I also have something to  do it easily with Angular, which is a buy. And when I say buy CSS, I can use a CSS  selector to determine whether I want or not the HTML element. So I'm going to write it as a class  selector. So I'm going to use a dot and say drop of point name. With that, Angular will give me  every HTML element, every not HTML element, every element that has that have this class.  So it's going to give me an array of debug elements. But what I want is not an array of  debug elements, but an array of HTML elements. So I need to transform that into HTML elements.  So to do so, I'm going to just iterate over it. So I'm going to use a reform to encapsulate that.  And in my array from, I'm just going to map it. And for each debug element, I'm going to retrieve  the native element of it. So debug element, native element. Okay. Not arrays, but array.  Okay. Perfect. That's why. So with this full line, what I will have is an array of HTML element.  As my bad, an array of HTML element. But that's not only what I want. What I want is the value  of that. So first, I'm going to just extract that as a function, because I might use it later.  I will call it find all. And I'm going to put the selector as parameter.  Okay. String. So I'm going to find all. All drop off point name classes element. Let's return that.  And it returns an HTML element. But as I said, I don't want only the  HTML element, but I want the value of it. So I'm going to map again. And since this is an HTML  element I receive here, what I can do is from it, I can say I want the text content of the  DOM element, the real DOM element. And since it's HTML, it can have a blank space between  and at the end and at the beginning. So I'm just going to trim that. So by querying the template  like this, I create a contract saying that I want to retrieve each element marked as drop off name,  and I want the value. So these strings, these strings are the drop off point names. And now,  thanks to this array, I can do some expectation of it. So I can expect that the drop off point name  length to equal like five, because I want only five locations at first. So I don't want all of the  location here, but only the five first. But at the moment, I only verify that I have five. So I need  to verify that's the five first. To do so, I'm just going to say that the drop off point names  contain to contain. And to contain, for example, Albert. I don't use to equal and having an array  after because I don't care about the order of the list here. So I'm just using to contain,  which is more flexible. So let's do it for the five, the four remaining.  Oh, yeah, I have multiple time. But yeah, the idea is there.  One, two, three, four. And so I missed this one. And I also want this element.  Okay, so now I expect correctly that I have five element, five location name somewhere. Okay.  Okay. So next test. So next test going to be almost the same, but instead of just having  the element to render, I want to go to the next page. So let's almost copy this test because  it's going to be almost the same should print the second page of location. And this time,  instead of just having fixed your test exchange, I want a way to click on the next page button.  So I want an element with the click. So I need to create a new contract. So I need to create  a new function find because I don't want to use find all because I only want one this time.  And I want to find some things that have the next page marker, let's say. So this function is going  to be almost the same as the find all. Let me put that below and let's create the find function. So  the find function is as defined, but instead of querying  all elements, I'm going to query only one. And after I'm going to use the native element,  create the native element. So this way I have defined as simple as defined. Okay. So I have my  next page. So this next page is something that is an HTML element. So I don't know. Finally,  I don't want to use a var here. I just want to click on this element.  Oh, my bad. It's not going to be an array of HTML element. That's why  the completion is not correct. Yes. So I want to click on it.  And I also want to detect that the change is off Angular. So I need to rerender the template again.  So now I've done the next page. I don't want to have five elements, but only two. And it's going  to be the two remaining elements here. So let me change the assertion here. Okay.  Okay. Let's save that. So now we know how to next page. So from the first page, I know how to go  to the next page. We verify that the names are correct. So let's do the same for the previous  buttons. It's going to be almost as the same as the first. The first test, but instead of just  detecting the changes here, I'm going to do the same as next page and also do  previous page. So I'm going to do next then previous to make sure that, yeah, it's not  because it's print at first. Let's let me correct that. Okay. So we have quite some tests now.  We can maybe start by writing an implementation for that. So let's go to the template.  So I'm going to use a table and create a new row. And for the new row, I'm going to create a new  cell. Okay. And I'm going to do a for each and before let drop off point,  drop off point of drop off points. I want to print  the location name drop off point singular location name.  But I still need to say that in the template that yeah, this is where the name is located. So I need  to add the class here saying that the drop off point name is here. And I also need two buttons.  One saying that this is the next page, next page and same for previous,  previous page. Okay, but I also need to implement the page here. So when I click, I want to do  next page, create this method. And I want to page to be plus equals one. So let's create this field.  It's a number starting from zero. And in the previous page, previous page, this, let's get  to that, create that this page. And minus one. Okay, let's have a look to the test. They were  failing. Forgot to show you but yeah, that was obvious. Oh, yeah, I forgot something.  I forgot to use the pagination here. So what I want is I want to slice, slice  page times five, two, page plus one times five. Okay. So let's have a look to the test.  Test on our green, that's cool. And we can have a look at the implementation. The next page is  working the previous page. It's working perfect. So now we want something to select a point to  show on the map a point. So what we're going to do, let me commit that first.  The implement page in issue. Okay.  Okay. Okay. Next test. Next test. Now we want something close to that. So  in this test, I don't want to verify the name. I don't want to change that. I just want to have  the same value. And instead of having the name here, I will also override the value of the coordinate.  We've longitude here. Let me find some data. Okay.  Okay. In the spec. Okay. Perfect.  That's the last one I could be. Yeah. Perfect.  Okay. So now I have points with a certain coordinates. And I can verify that when I  click on show on the map of this element, it's going to show me on the map what I'm looking for.  So same as before, we want to first render the list. And after I want to find the button show on  map. So new contract. I want an element clickable that have a show on map.  Show on map. And when I want to click on this one, let's use a find all, by the way.  Okay. And for example, click on the first one. Then I want, then what I want, by the way,  yeah, what I want. What I want is now I want to have a map shown with the location, the  coordinate of the location, the address. I don't want to do it in this element. So I'm going to  delegate that to the map component. So I can do the fixture, the text changes here, by the way.  And now instead of finding an element with a selector, I can use a different kind of contract  saying that instead of querying by CSS, what I can do is querying by directive like this.  And I can say I want to select a map component. And I don't want the native element because I want  the Angular element of it. So what I want with that is the component instance of this map component.  And this map component has a certain contract with me. It has some latitude and some longitude.  So when I give these two values to the map component, I know that the map component will  display it correctly. So I don't want to verify if it's correctly displayed. If I give to the map  component the correct data, it's going to be correctly displayed. So what I can verify if I do that  is I want to expect that the map component latitude is equal to the first latitude. So this value  and the longitude to equal to this value. And by doing that, I'm sure that it will, when I click  on show on map, it will render the correct way because I delegate to the map component  the fact to show on the map. So it's not over yet the test because since I have a new component,  a map component, now I need to declare it in the test bed. So let's just add it here.  And now I can check that the test is failing.  Yeah, I have a correctly failed test. So now let's implement it.  I want to have a map component here. So it's going to be an app map.  I want some latitude and some longitude here. And I want which latitude and which longitude.  I don't know yet. So map, location, latitude and location, longitude. I can create this  both element, public and do the same for longitude.  Okay, so also what I want is now  is a button that will show on the map. So let's create a button here. I need to respect the  contract by saying that the class is show on map.  Show on map. And when I click on it, I want to show on map this location.  So, okay, I'm going to create this method. And this method will simply assign the latitude  of the drop of point and do the same for longitude. Let's verify the test. Did not miss anything.  Okay, test is green. That's a good sign. It means that, yeah, when I click on show on map,  it shows on the map. Perfect.  Let's commit that.  Okay. See how we can write the test before the implementation and be sure that it's correctly  displayed after. To summarize, this is a way how you can do some template contract in order to test  your component. I hope you enjoyed and learned something and see you right now for some live  question. And thank you for your attention.
angular_test_first_development_0_1127.png angular_test_first_development_1128_1607.png angular_test_first_development_1608_2171.png angular_test_first_development_2172_5951.png angular_test_first_development_5952_6035.png angular_test_first_development_6036_8867.png angular_test_first_development_8868_12563.png angular_test_first_development_12564_14675.png angular_test_first_development_14676_14747.png angular_test_first_development_14748_14807.png angular_test_first_development_14808_15635.png angular_test_first_development_15636_18791.png angular_test_first_development_18792_21707.png angular_test_first_development_21708_21791.png angular_test_first_development_21792_22139.png angular_test_first_development_22140_22823.png angular_test_first_development_22824_24107.png angular_test_first_development_24108_24155.png angular_test_first_development_24156_24767.png angular_test_first_development_24768_25643.png angular_test_first_development_25644_25763.png angular_test_first_development_25764_26087.png angular_test_first_development_26088_27839.png angular_test_first_development_27840_29279.png angular_test_first_development_29280_36803.png angular_test_first_development_36804_36947.png angular_test_first_development_36948_37619.png angular_test_first_development_37620_38075.png angular_test_first_development_38076_38195.png angular_test_first_development_38196_38315.png angular_test_first_development_38316_39803.png angular_test_first_development_39804_40463.png angular_test_first_development_40464_40787.png angular_test_first_development_40788_41711.png angular_test_first_development_41712_41846.png