How to unit test Angular directive with @input alias

Viewed 309

I want to show/hide elements according to a user's role (some buttons shall just be visible for admins for instance). This can be achieved with *ngIf, I believe a better approach is a directive though:

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
import { AuthService } from '../services/auth.service';

@Directive({
    selector: '[appHasRole]',
})
export class HasRoleDirective {
    constructor(private auth: AuthService, private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) {}

    @Input() set appHasRole(role: string) {
        if (this.checkRole(role)) {
            this.viewContainer.createEmbeddedView(this.templateRef);
        } else {
            this.viewContainer.clear();
        }
    }

    checkRole(role: string): boolean {
        return this.auth.getRole() === role;
    }
}

Usage:

<div *appHasRole="'admin'">

This works as expected. I struggle to write a unit test for the component though. Despite reading the testing examples in the official Angular documentation and some related posts (Angular 2 / 4 - How to test Directive @Input values? and How to test the set method of @Input in an Angular directives), I could not make it work.

I'm struggling because I do not use a separated input like

<div *appHasRole [role]="'admin'"> 

but use an @input alias.

Attempt 1:

@Component({
    template: `<div *appHasRole="'admin'" class="test"></div>`,
})
class TestComponent {
    @ViewChild(HasRoleDirective) directive: HasRoleDirective;
}

beforeEach(async(() => {
    fixture = TestBed.configureTestingModule({
        declarations: [HasRoleDirective, TestComponent],
        providers: [AuthService, HasRoleDirective, TemplateRef, ViewContainerRef],
    }).createComponent(TestComponent);
    testDirective = new HasRoleDirective(new AuthService(), new TemplateRef(), new ViewContainerRef());
}))

I did not even add any unit tests here because ViewContainerRef is an abstract class and cannot be directly instantiated. So I don't see any way of testing

this.viewContainer.createEmbeddedView(this.templateRef);

with this approach.

Attempt 2:

@Component({
    template: `<div *appHasRole="'admin'"></div>`,
})
class TestComponent {
    @ViewChild(HasRoleDirective) directive: HasRoleDirective;
}

let directive: DebugElement;
let testComponent: TestComponent;

beforeEach(() => {
    fixture = TestBed.configureTestingModule({
        declarations: [HasRoleDirective, TestComponent],
        imports: [HttpClientModule, RouterTestingModule],
        providers: [HasRoleDirective, TemplateRef, ViewContainerRef],
    }).createComponent(TestComponent);

    fixture.detectChanges();
    testComponent = fixture.componentInstance;
    directive = fixture.debugElement.query(By.directive(HasRoleDirective));
    console.log(directive);
});

The directive is always null, which prevents further tests. Even when I try to access it with a helper class:

Attempt 3:

@Component({
    template: `<div *appHasRole="'admin'" class="test"></div>`,
})
class TestComponent {
    @ViewChild(HasRoleDirective) directive: HasRoleDirective;
}

let directive: DebugElement;
let testComponent: TestComponent;

beforeEach(() => {
    fixture = TestBed.configureTestingModule({
        declarations: [HasRoleDirective, TestComponent],
        imports: [HttpClientModule, RouterTestingModule],
        providers: [HasRoleDirective, TemplateRef, ViewContainerRef],
    }).createComponent(TestComponent);

    fixture.detectChanges();
    testComponent = fixture.componentInstance;
    directive = fixture.debugElement.query(By.css('.test'));
    console.log(directive);
});

Can anyone give me a hint?

0 Answers
Related