Skip to main content

How to render TemplateRef of a mock declaration

This functionality has been deprecated

Templates are often used in UI component libraries such as Angular Material, NG Bootstrap, PrimeNG, ng-select etc. They bring flexibility via templates when we want to render a table or a calendar, but with custom design.

Usually, they are declared like a structural directive or with ids:

<!-- id --><ng-template #header>...</ng-template>
<!-- structural directive --><ng-template pTemplate="header">...</ng-template><ng-template ng-label-tmp let-item="item">...</ng-template>
<!-- structural directive --><tr *matHeaderRowDef="..."></tr>

Solution#

In a test, instead of fighting with providing all required data for ui components, we could mock them and assert that their inputs / outputs have been linked correctly, and injected templates contain what we want, but when and how they are rendered by the library - we do not want to care.

There are several test examples for different UI libraries:

Other libraries / components can be tested in the similar way.

Templates by id#

<xd-card>  <ng-template #header>My Header</ng-template>  <ng-template #footer>My Footer</ng-template></xd-card>

This code means, that xd-card uses ContentChild('header') and ContentChild('footer') to fetch the templates.

In order to manipulate them, ng-mock provides __render(id, context?, additionalVars?) and __hide(id) functions.

In case of this example, we need to:

  1. find the component's instance
  2. ensure that it has been mocked
  3. render the templates
  4. assert their content
// looking for the instanceconst component = ngMocks.findInstance(XdCardComponent);
// checking that the component is a mockif (isMockOf(component, XdCardComponent, 'c')) {  component.__render('header');  component.__render('footer');}
// asserting headerconst header = ngMocks.find('[data-key="header"]');expect(header.nativeElement.innerHTML)  .toContain('My Header');
// asserting footerconst footer = ngMocks.find('[data-key="footer"]');expect(footer.nativeElement.innerHTML)  .toContain('My Footer');

That is it. Now we have a test which verifies templates for xd-card.

Templates by directive#

<xd-card>  <ng-template xdTpl="header">My Header</ng-template>  <ng-template xdTpl="footer">My Footer</ng-template></xd-card>

This approach differs a bit from rendering ng-templates by id. This code means, that xd-card relies on a directive with the xdTpl selector, in order to get the desired templates.

And so do we. The render is based not on the related component, but on the related directive. Therefore, we need to:

  1. find the directives
  2. ensure that they have been mocked
  3. render their templates
  4. assert their content
important

Currently, it is not as easy as could be. We can find the directive via providing its class, but, unfortunately, not its selector.

However, good news is that it will be changed soon. Please follow this issue on github: render by selector.

// looking for the element// which is produced// by the desired componentconst container = ngMocks.find('xd-card');
// fetching directivesconst [header, footer] = ngMocks.findInstances(  container,  XdTplDirective,);
// asserting headerexpect(header.xdTpl).toEqual('header');ngMocks.render(header, header);expect(container.nativeElement.innerHTML)  .toContain('My Header');
// asserting footerexpect(footer.xdTpl).toEqual('footer');ngMocks.render(footer, footer);expect(container.nativeElement.innerHTML)  .toContain('My Footer');

Done. Now we have a test which verifies that xd-card gets the desired templates.

TemplateRef in properties#

There is one more way to render TemplateRef of a mock component - when we know the property which has been decorated with ContentChild or ContentChildren.

For example, we know that XdCardComponent uses ContentChildren on tpls property in order to fetch all templates.

<xd-card>  <ng-template xdTpl="header">My Header</ng-template>  <ng-template xdTpl="footer">My Footer</ng-template></xd-card>

Then we can use a special interface of __render method for properties. To do so, we need to a tuple as the first parameter.

ngMocks.render(component, ngMocks.findTemplateRef('header'));

This call will render the whole QueryList if it is ContentChildren or TemplateRef if it is ContentChild.

If we want to render only particular element, then we can pass its index in the tuple.

ngMocks.render(component, ngMocks.findTemplateRef('footer'));

The sample approach works for hide.

ngMocks.hide(component);

The templates will be rendered under a special element with data-prop attribute. We can find them with the next query:

const element = ngMocks.find('[data-prop="tpls"]');