Search code examples
htmlangularangular-dependency-injection

How to provide a HostAttributeToken in Angular tests?


I recently discovered the HostAttributeToken in Angular (see docs) to provide static attributes for a component.

The following example works fine:

import { Component, HostAttributeToken, inject } from '@angular/core';

@Component({
  selector: 'demo-component',
  template: `<div [class]="styleClass">Hello world</div>`,
})
export class DemoComponent {
  styleClass = inject(new HostAttributeToken('style-class'));
}

usage

<demo-component style-class="bla" />

My question: How can provide the HostAttributeToken in a test with jasmine/karma?

I want to make this attribute required (there is also a flag to make the HostAttributeToken optional).

It is of course easy if there is a host component, but how can I achieve the same thing with the TestBed Api?

A basic test setup without providing an HostAttributeToken would look like this:

import { TestBed } from '@angular/core/testing';
import { DemoComponent } from './demo.component';

describe('DemoComponent', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [DemoComponent],
    }).compileComponents();
  });

  it('should create', () => {
    const fixture = TestBed.createComponent(DemoComponent);
    expect(fixture.componentInstance).toBeTruthy();
  });
});

which errors with

Error: NG0201: No provider for HostAttributeToken style-class found. Find more at https://angular.dev/errors/NG0201


Solution

  • When you have that kind of question, it is often interesting how the feature itself is unit-testing inside the angular repo.

    Here is the relevant test file.

    Long story short, there is no other way than having a fake host component to test that feature.

    // The directive you're testing
    @Directive({selector: '[dir]', standalone: true})
    class Dir {
      value = inject(new HostAttributeToken('some-attr'));
    }
    
    // The "fake" host component 
    @Component({
      standalone: true,
      template: '<div dir some-attr="foo" other="ignore"></div>',
      imports: [Dir],
    })
    class TestCmp {
      @ViewChild(Dir) dir!: Dir;
    }
    
    const fixture = TestBed.createComponent(TestCmp);
    fixture.detectChanges();
    expect(fixture.componentInstance.dir.value).toBe('foo');