Monday, March 19, 2018

Testing ViewChildren in Angular

I have a component that uses a multi-select-dropdown (MSD) component. There can be many MSDs on a single page. When one of the MSDs is opened, I want to close all of the others. There's a function called close on each MSD, and each MSD also has an id property. Finally, the MSDs are created dynamically based on some remote data (so they don't have template reference names).

So how do I close every MSD on the page except the one I'm currently trying to open? It turns out I can get references to all of the MSDs on the page by using ViewChildren. I have a property on my component (my page component) that looks like this
@ViewChildren(MultiSelectDropdownComponent) dropdowns: QueryList<MultiSelectDropdownComponent>;
Now that I know how to get access to them, I have a new problem: how to I populate the dropdowns property from my unit tests?

It turns out that's really easy, too. I just need to let the TestBed do its thing. Since my MSDs are created using an ngFor on another property on my page component (called items), all I have to do is populate my items property, then rebuild the test component. That will build the MSDs for me. That looks like this:
const spies = [];
component.items = [
 {id: 1, options: [...]},
 {id: 2, options: [...]},
 {id: 3, options: [...]}
];
fixture.detectChanges();
component.dropdowns.forEach(d => {
  spies.push({
    id: d.id,
    spy: spyOn(d, 'close')
  });
});
When we invoke fixture.detectChanges our markup is rebuilt, which generates our three MSDs on the test form. Once that happens, the MSDs are available in the dropdowns property so I can iterate through the dropdowns property and spy on the close function of each separate MSD.

Now that I'm spying on the close function of each MSD, I want to invoke my function under test (called toggleOthers) and make sure that all the other MSDs are closed, but not the one that invoked toggleOthers in the first place (because I'm actually trying to open that one).
component.toggleOthers(2);

spies.forEach(s => {
  if (s.id === 2) {
    expect(s.spy).not.toHaveBeenCalled();
  } else {
    expect(s.spy).toHaveBeenCalledTimes(1);
  }
}
Since we invoked the toggleOthers function with the id we're opening, we can check to make sure the MSD associated with that id isn't closed. Then we can check that all other MSDs were closed.

The caveat to this approach is that if the MSD was complex we wouldn't really want to bring the real MSD into the tests just so we could do this. I'm sure that'll come up for me someday so if it does I'll try to remember to write a post on that and update this one with a link to it. If that's what you're looking for, sorry. Hopefully this helps you get there.

3 comments:

  1. SO HELPFULL, i didnt think this simple, I insisted on trying to mock the dropdowns variable :D

    ReplyDelete
  2. But i got issue, but the "close" function were undefined. so i mock it and it works

    ReplyDelete
  3. Answers I Couldn'T Find Anywhere Else: Testing Viewchildren In Angular >>>>> Download Now

    >>>>> Download Full

    Answers I Couldn'T Find Anywhere Else: Testing Viewchildren In Angular >>>>> Download LINK

    >>>>> Download Now

    Answers I Couldn'T Find Anywhere Else: Testing Viewchildren In Angular >>>>> Download Full

    >>>>> Download LINK O2

    ReplyDelete