{ "id": "guide/testing-attribute-directives", "title": "Testing Attribute Directives", "contents": "\n\n\n
\n mode_edit\n
\n\n\n
\n \n

Testing Attribute Directiveslink

\n

An attribute directive modifies the behavior of an element, component or another directive.\nIts name reflects the way the directive is applied: as an attribute on a host element.

\n
\n

For the sample app that the testing guides describe, see the sample app.

\n

For the tests features in the testing guides, see tests.

\n
\n

Testing the HighlightDirectivelink

\n

The sample application's HighlightDirective sets the background color of an element\nbased on either a data bound color or a default color (lightgray).\nIt also sets a custom property of the element (customProperty) to true\nfor no reason other than to show that it can.

\n\nimport { Directive, ElementRef, Input, OnChanges } from '@angular/core';\n\n@Directive({ selector: '[highlight]' })\n/**\n * Set backgroundColor for the attached element to highlight color\n * and set the element's customProperty to true\n */\nexport class HighlightDirective implements OnChanges {\n\n defaultColor = 'rgb(211, 211, 211)'; // lightgray\n\n @Input('highlight') bgColor: string;\n\n constructor(private el: ElementRef) {\n el.nativeElement.style.customProperty = true;\n }\n\n ngOnChanges() {\n this.el.nativeElement.style.backgroundColor = this.bgColor || this.defaultColor;\n }\n}\n\n\n\n

It's used throughout the application, perhaps most simply in the AboutComponent:

\n\nimport { Component } from '@angular/core';\n@Component({\n template: `\n <h2 highlight=\"skyblue\">About</h2>\n <h3>Quote of the day:</h3>\n <twain-quote></twain-quote>\n `\n})\nexport class AboutComponent { }\n\n\n\n

Testing the specific use of the HighlightDirective within the AboutComponent requires only the techniques explored in the \"Nested component tests\" section of Component testing scenarios.

\n\nbeforeEach(() => {\n fixture = TestBed.configureTestingModule({\n declarations: [ AboutComponent, HighlightDirective ],\n schemas: [ NO_ERRORS_SCHEMA ]\n })\n .createComponent(AboutComponent);\n fixture.detectChanges(); // initial binding\n});\n\nit('should have skyblue <h2>', () => {\n const h2: HTMLElement = fixture.nativeElement.querySelector('h2');\n const bgColor = h2.style.backgroundColor;\n expect(bgColor).toBe('skyblue');\n});\n\n\n

However, testing a single use case is unlikely to explore the full range of a directive's capabilities.\nFinding and testing all components that use the directive is tedious, brittle, and almost as unlikely to afford full coverage.

\n

Class-only tests might be helpful,\nbut attribute directives like this one tend to manipulate the DOM.\nIsolated unit tests don't touch the DOM and, therefore,\ndo not inspire confidence in the directive's efficacy.

\n

A better solution is to create an artificial test component that demonstrates all ways to apply the directive.

\n\n@Component({\n template: `\n <h2 highlight=\"yellow\">Something Yellow</h2>\n <h2 highlight>The Default (Gray)</h2>\n <h2>No Highlight</h2>\n <input #box [highlight]=\"box.value\" value=\"cyan\"/>`\n})\nclass TestComponent { }\n\n\n
\n \"HighlightDirective\n
\n
\n

The <input> case binds the HighlightDirective to the name of a color value in the input box.\nThe initial value is the word \"cyan\" which should be the background color of the input box.

\n
\n

Here are some tests of this component:

\n\nbeforeEach(() => {\n fixture = TestBed.configureTestingModule({\n declarations: [ HighlightDirective, TestComponent ]\n })\n .createComponent(TestComponent);\n\n fixture.detectChanges(); // initial binding\n\n // all elements with an attached HighlightDirective\n des = fixture.debugElement.queryAll(By.directive(HighlightDirective));\n\n // the h2 without the HighlightDirective\n bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])'));\n});\n\n// color tests\nit('should have three highlighted elements', () => {\n expect(des.length).toBe(3);\n});\n\nit('should color 1st <h2> background \"yellow\"', () => {\n const bgColor = des[0].nativeElement.style.backgroundColor;\n expect(bgColor).toBe('yellow');\n});\n\nit('should color 2nd <h2> background w/ default color', () => {\n const dir = des[1].injector.get(HighlightDirective) as HighlightDirective;\n const bgColor = des[1].nativeElement.style.backgroundColor;\n expect(bgColor).toBe(dir.defaultColor);\n});\n\nit('should bind <input> background to value color', () => {\n // easier to work with nativeElement\n const input = des[2].nativeElement as HTMLInputElement;\n expect(input.style.backgroundColor).toBe('cyan', 'initial backgroundColor');\n\n input.value = 'green';\n\n // Dispatch a DOM event so that Angular responds to the input value change.\n // In older browsers, such as IE, you might need a CustomEvent instead. See\n // https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill\n input.dispatchEvent(new Event('input'));\n fixture.detectChanges();\n\n expect(input.style.backgroundColor).toBe('green', 'changed backgroundColor');\n});\n\n\nit('bare <h2> should not have a customProperty', () => {\n expect(bareH2.properties.customProperty).toBeUndefined();\n});\n\n\n

A few techniques are noteworthy:

\n\n\n \n
\n\n\n" }