How to Unit Test a Directive In Angular 2?

38,211

Solution 1

Testing compiled directive using TestBed

Let's say you have a following directive:

@Directive({
  selector: '[my-directive]',
})
class MyDirective {
  public directiveProperty = 'hi!';
}

What you have to do, is to create a component that uses the directive (it can be just for testing purpose):

@Component({
  selector: 'my-test-component',
  template: ''
})
class TestComponent {}

Now you need to create a module that has them declared:

describe('App', () => {

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [
        TestComponent,
        MyDirective
      ]
    });
  });

  // ...

});

You can add the template (that contains directive) to the component, but it can be handled dynamically by overwriting the template in test:

it('should be able to test directive', async(() => {
  TestBed.overrideComponent(TestComponent, {
    set: {
      template: '<div my-directive></div>'
    }
  });

  // ...      

}));

Now you can try to compile the component, and query it using By.directive. At the very end, there is a possibility to get a directive instance using the injector:

TestBed.compileComponents().then(() => {
  const fixture = TestBed.createComponent(TestComponent);
  const directiveEl = fixture.debugElement.query(By.directive(MyDirective));
  expect(directiveEl).not.toBeNull();

  const directiveInstance = directiveEl.injector.get(MyDirective);
  expect(directiveInstance.directiveProperty).toBe('hi!');
}); 

# Old answer:

To test a directive you need to create a fake component with it:

@Component({
  selector: 'test-cmp',
  directives: [MyAttrDirective],
  template: ''
})
class TestComponent {}

You can add the template in the component itself but it can be handled dynamically by overwriting the template in test:

it('Should setup with conversation', inject([TestComponentBuilder], (testComponentBuilder: TestComponentBuilder) => {
    return testComponentBuilder
      .overrideTemplate(TestComponent, `<div my-attr-directive></div>`)
      .createAsync(TestComponent)
      .then((fixture: ComponentFixture<TestComponent>) => {
        fixture.detectChanges();
        const directiveEl = fixture.debugElement.query(By.css('[my-attr-directive]'));
        expect(directiveEl.nativeElement).toBeDefined();
      });
  }));

Note that you're able to test what directive renders but I couldn't find the way to test a directive in a way components are (there is no TestComponentBuilder for directives).

Solution 2

Took me a while to find a good example, a good person on angular gitter channel pointed me to look at the Angular Material Design 2 repository for examples. You can find a Directive test example here. This is the test file for the tooltip directive of Material Design 2. It looks like you have to test it as part of a component.

Share:
38,211

Related videos on Youtube

daniel.caspers
Author by

daniel.caspers

SOreadytohelp Check out my personal site for more info!

Updated on July 09, 2022

Comments

  • daniel.caspers
    daniel.caspers almost 2 years

    Problem: I would like to be able to unit test a directive in Angular 2 to make sure that it properly compiles.

    In Angular 1, it was possible to use$compile(angular.element(myElement) service and call $scope.$digest() after that. I specifically want to be able to do this in unit tests so I could test that when Angular ends up running across <div my-attr-directive/> in the code that my-attr-directive compiles.

    Constraints:

    • Günter Zöchbauer
      Günter Zöchbauer about 8 years
      You use TestComponentBuilder as shown in the lined SO question/answer. Create a test component where the directive is used in the template and then get a reference to the directive from the created test component instance.
    • daniel.caspers
      daniel.caspers about 8 years
      Thanks for answering both. Just realized that you answered that question previously! I thought there was a comment on that answer regarding removing support for TestComponentBuilder when it got moved to a different package, but I think I'm blurring SO posts in my mind. Thanks. I'll close this one out?
  • daniel.caspers
    daniel.caspers over 7 years
    Cool resource! Thanks for sharing. It doesn't meet my requirements of the question of being a JS file.
  • Jusef
    Jusef over 7 years
    It is difficult to find any resources in pure JS. One idea would be to get the material design repo, compile it and have a look at the .js file that it produces. I will be surprised by how clean is the JavaScript code produced by the Typescript compiler
  • Jusef
    Jusef over 7 years
    It is difficult to find any resources in pure JS at the moment. One idea would be to get the material design repo, compile it and have a look at the .js file that it produces. You will be surprised by how clean is the JavaScript code produced by the Typescript compiler
  • Rahul Singh
    Rahul Singh about 7 years
    I am getting error : No provider for DirectiveResolver what could be the possible cause?
  • stevek-pro
    stevek-pro about 7 years
    To make use of By you will need to import { By } from '@angular/platform-browser';
  • Rolando
    Rolando about 7 years
    what about the difective has @input member?, I am interested to add unit test to this control: github.com/czeckd/angular-dual-listbox.. but i don't know how to start.
  • Drenai
    Drenai over 6 years
    @Wojchieh - const directiveEl = fixture.debugElement.query(By.directive(MyDirective)); is really helpful. Great answer
  • maxime1992
    maxime1992 almost 6 years
    Accessing directive instance though injector: Brilliant :) I'll remember that. Thanks
  • Rohìt Jíndal
    Rohìt Jíndal almost 6 years
    Great explanation. Works like a champ :)
  • JayChase
    JayChase almost 5 years
    That's a good example to follow. It looks like the repo has been re-organized. The tooltip spec is now here
  • vibhu
    vibhu almost 5 years
    Instead of accessing the Directive instance through injector why not just define a property on the TestComponent as follows: @ViewChild(MyDirective) directiveInstance: MyDirective and then use it in your test case like this expect(fixture.componentInstance.directiveInstance.directive‌​Property).toBe('hi!'‌​);