{ "id": "guide/testing-components-basics", "title": "Basics of testing components", "contents": "\n\n\n
\n mode_edit\n
\n\n\n
\n

Basics of testing componentslink

\n

A component, unlike all other parts of an Angular application,\ncombines an HTML template and a TypeScript class.\nThe component truly is the template and the class working together. To adequately test a component, you should test that they work together\nas intended.

\n

Such tests require creating the component's host element in the browser DOM,\nas Angular does, and investigating the component class's interaction with\nthe DOM as described by its template.

\n

The Angular TestBed facilitates this kind of testing as you'll see in the sections below.\nBut in many cases, testing the component class alone, without DOM involvement,\ncan validate much of the component's behavior in an easier, more obvious way.

\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\n

Component class testinglink

\n

Test a component class on its own as you would test a service class.

\n

Component class testing should be kept very clean and simple.\nIt should test only a single unit.\nAt first glance, you should be able to understand\nwhat the test is testing.

\n

Consider this LightswitchComponent which toggles a light on and off\n(represented by an on-screen message) when the user clicks the button.

\n\n@Component({\n selector: 'lightswitch-comp',\n template: `\n <button (click)=\"clicked()\">Click me!</button>\n <span>{{message}}</span>`\n})\nexport class LightswitchComponent {\n isOn = false;\n clicked() { this.isOn = !this.isOn; }\n get message() { return `The light is ${this.isOn ? 'On' : 'Off'}`; }\n}\n\n\n

You might decide only to test that the clicked() method\ntoggles the light's on/off state and sets the message appropriately.

\n

This component class has no dependencies. To test these types of classes, follow the same steps as you would for a service that has no dependencies:

\n
    \n
  1. Create a component using the new keyword.
  2. \n
  3. Poke at its API.
  4. \n
  5. Assert expectations on its public state.
  6. \n
\n\ndescribe('LightswitchComp', () => {\n it('#clicked() should toggle #isOn', () => {\n const comp = new LightswitchComponent();\n expect(comp.isOn).toBe(false, 'off at first');\n comp.clicked();\n expect(comp.isOn).toBe(true, 'on after click');\n comp.clicked();\n expect(comp.isOn).toBe(false, 'off after second click');\n });\n\n it('#clicked() should set #message to \"is on\"', () => {\n const comp = new LightswitchComponent();\n expect(comp.message).toMatch(/is off/i, 'off at first');\n comp.clicked();\n expect(comp.message).toMatch(/is on/i, 'on after clicked');\n });\n});\n\n\n

Here is the DashboardHeroComponent from the Tour of Heroes tutorial.

\n\nexport class DashboardHeroComponent {\n @Input() hero: Hero;\n @Output() selected = new EventEmitter<Hero>();\n click() { this.selected.emit(this.hero); }\n}\n\n\n

It appears within the template of a parent component,\nwhich binds a hero to the @Input property and\nlistens for an event raised through the selected @Output property.

\n

You can test that the class code works without creating the DashboardHeroComponent\nor its parent component.

\n\nit('raises the selected event when clicked', () => {\n const comp = new DashboardHeroComponent();\n const hero: Hero = {id: 42, name: 'Test'};\n comp.hero = hero;\n\n comp.selected.subscribe((selectedHero: Hero) => expect(selectedHero).toBe(hero));\n comp.click();\n});\n\n\n

When a component has dependencies, you may wish to use the TestBed to both\ncreate the component and its dependencies.

\n

The following WelcomeComponent depends on the UserService to know the name of the user to greet.

\n\nexport class WelcomeComponent implements OnInit {\n welcome: string;\n constructor(private userService: UserService) { }\n\n ngOnInit(): void {\n this.welcome = this.userService.isLoggedIn ?\n 'Welcome, ' + this.userService.user.name : 'Please log in.';\n }\n}\n\n\n

You might start by creating a mock of the UserService that meets the minimum needs of this component.

\n\nclass MockUserService {\n isLoggedIn = true;\n user = { name: 'Test User'};\n}\n\n\n

Then provide and inject both the component and the service in the TestBed configuration.

\n\nbeforeEach(() => {\n TestBed.configureTestingModule({\n // provide the component-under-test and dependent service\n providers: [\n WelcomeComponent,\n { provide: UserService, useClass: MockUserService }\n ]\n });\n // inject both the component and the dependent service.\n comp = TestBed.inject(WelcomeComponent);\n userService = TestBed.inject(UserService);\n});\n\n\n

Then exercise the component class, remembering to call the lifecycle hook methods as Angular does when running the app.

\n\nit('should not have welcome message after construction', () => {\n expect(comp.welcome).toBeUndefined();\n});\n\nit('should welcome logged in user after Angular calls ngOnInit', () => {\n comp.ngOnInit();\n expect(comp.welcome).toContain(userService.user.name);\n});\n\nit('should ask user to log in if not logged in after ngOnInit', () => {\n userService.isLoggedIn = false;\n comp.ngOnInit();\n expect(comp.welcome).not.toContain(userService.user.name);\n expect(comp.welcome).toContain('log in');\n});\n\n\n

Component DOM testinglink

\n

Testing the component class is as easy as testing a service.

\n

But a component is more than just its class.\nA component interacts with the DOM and with other components.\nThe class-only tests can tell you about class behavior.\nThey cannot tell you if the component is going to render properly,\nrespond to user input and gestures, or integrate with its parent and child components.

\n

None of the class-only tests above can answer key questions about how the\ncomponents actually behave on screen.

\n\n

These may not be troubling questions for the simple components illustrated above.\nBut many components have complex interactions with the DOM elements\ndescribed in their templates, causing HTML to appear and disappear as\nthe component state changes.

\n

To answer these kinds of questions, you have to create the DOM elements associated\nwith the components, you must examine the DOM to confirm that component state\ndisplays properly at the appropriate times, and you must simulate user interaction\nwith the screen to determine whether those interactions cause the component to\nbehave as expected.

\n

To write these kinds of test, you'll use additional features of the TestBed\nas well as other testing helpers.

\n

CLI-generated testslink

\n

The CLI creates an initial test file for you by default when you ask it to\ngenerate a new component.

\n

For example, the following CLI command generates a BannerComponent in the app/banner folder (with inline template and styles):

\n\nng generate component banner --inline-template --inline-style --module app\n\n

It also generates an initial test file for the component, banner-external.component.spec.ts, that looks like this:

\n\nimport { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';\n\nimport { BannerComponent } from './banner.component';\n\ndescribe('BannerComponent', () => {\n let component: BannerComponent;\n let fixture: ComponentFixture<BannerComponent>;\n\n beforeEach(waitForAsync(() => {\n TestBed.configureTestingModule({declarations: [BannerComponent]}).compileComponents();\n }));\n\n beforeEach(() => {\n fixture = TestBed.createComponent(BannerComponent);\n component = fixture.componentInstance;\n fixture.detectChanges();\n });\n\n it('should create', () => {\n expect(component).toBeDefined();\n });\n});\n\n\n
\n

Because compileComponents is asynchronous, it uses\nthe waitForAsync utility\nfunction imported from @angular/core/testing.

\n

Please refer to the waitForAsync section for more details.

\n
\n

Reduce the setuplink

\n

Only the last three lines of this file actually test the component\nand all they do is assert that Angular can create the component.

\n

The rest of the file is boilerplate setup code anticipating more advanced tests that might become necessary if the component evolves into something substantial.

\n

You'll learn about these advanced test features below.\nFor now, you can radically reduce this test file to a more manageable size:

\n\ndescribe('BannerComponent (minimal)', () => {\n it('should create', () => {\n TestBed.configureTestingModule({declarations: [BannerComponent]});\n const fixture = TestBed.createComponent(BannerComponent);\n const component = fixture.componentInstance;\n expect(component).toBeDefined();\n });\n});\n\n\n

In this example, the metadata object passed to TestBed.configureTestingModule\nsimply declares BannerComponent, the component to test.

\n\nTestBed.configureTestingModule({declarations: [BannerComponent]});\n\n\n
\n

There's no need to declare or import anything else.\nThe default test module is pre-configured with\nsomething like the BrowserModule from @angular/platform-browser.

\n

Later you'll call TestBed.configureTestingModule() with\nimports, providers, and more declarations to suit your testing needs.\nOptional override methods can further fine-tune aspects of the configuration.

\n
\n\n

createComponent()link

\n

After configuring TestBed, you call its createComponent() method.

\n\nconst fixture = TestBed.createComponent(BannerComponent);\n\n\n

TestBed.createComponent() creates an instance of the BannerComponent,\nadds a corresponding element to the test-runner DOM,\nand returns a ComponentFixture.

\n
\n

Do not re-configure TestBed after calling createComponent.

\n

The createComponent method freezes the current TestBed definition,\nclosing it to further configuration.

\n

You cannot call any more TestBed configuration methods, not configureTestingModule(),\nnor get(), nor any of the override... methods.\nIf you try, TestBed throws an error.

\n
\n\n

ComponentFixturelink

\n

The ComponentFixture is a test harness for interacting with the created component and its corresponding element.

\n

Access the component instance through the fixture and confirm it exists with a Jasmine expectation:

\n\nconst component = fixture.componentInstance;\nexpect(component).toBeDefined();\n\n\n

beforeEach()link

\n

You will add more tests as this component evolves.\nRather than duplicate the TestBed configuration for each test,\nyou refactor to pull the setup into a Jasmine beforeEach() and some supporting variables:

\n\ndescribe('BannerComponent (with beforeEach)', () => {\n let component: BannerComponent;\n let fixture: ComponentFixture<BannerComponent>;\n\n beforeEach(() => {\n TestBed.configureTestingModule({declarations: [BannerComponent]});\n fixture = TestBed.createComponent(BannerComponent);\n component = fixture.componentInstance;\n });\n\n it('should create', () => {\n expect(component).toBeDefined();\n });\n});\n\n\n

Now add a test that gets the component's element from fixture.nativeElement and\nlooks for the expected text.

\n\nit('should contain \"banner works!\"', () => {\n const bannerElement: HTMLElement = fixture.nativeElement;\n expect(bannerElement.textContent).toContain('banner works!');\n});\n\n\n\n

nativeElementlink

\n

The value of ComponentFixture.nativeElement has the any type.\nLater you'll encounter the DebugElement.nativeElement and it too has the any type.

\n

Angular can't know at compile time what kind of HTML element the nativeElement is or\nif it even is an HTML element.\nThe app might be running on a non-browser platform, such as the server or a\nWeb Worker,\nwhere the element may have a diminished API or not exist at all.

\n

The tests in this guide are designed to run in a browser so a\nnativeElement value will always be an HTMLElement or\none of its derived classes.

\n

Knowing that it is an HTMLElement of some sort, you can use\nthe standard HTML querySelector to dive deeper into the element tree.

\n

Here's another test that calls HTMLElement.querySelector to get the paragraph element and look for the banner text:

\n\nit('should have <p> with \"banner works!\"', () => {\n const bannerElement: HTMLElement = fixture.nativeElement;\n const p = bannerElement.querySelector('p');\n expect(p.textContent).toEqual('banner works!');\n});\n\n\n\n

DebugElementlink

\n

The Angular fixture provides the component's element directly through the fixture.nativeElement.

\n\nconst bannerElement: HTMLElement = fixture.nativeElement;\n\n\n

This is actually a convenience method, implemented as fixture.debugElement.nativeElement.

\n\nconst bannerDe: DebugElement = fixture.debugElement;\nconst bannerEl: HTMLElement = bannerDe.nativeElement;\n\n\n

There's a good reason for this circuitous path to the element.

\n

The properties of the nativeElement depend upon the runtime environment.\nYou could be running these tests on a non-browser platform that doesn't have a DOM or\nwhose DOM-emulation doesn't support the full HTMLElement API.

\n

Angular relies on the DebugElement abstraction to work safely across all supported platforms.\nInstead of creating an HTML element tree, Angular creates a DebugElement tree that wraps the native elements for the runtime platform.\nThe nativeElement property unwraps the DebugElement and returns the platform-specific element object.

\n

Because the sample tests for this guide are designed to run only in a browser,\na nativeElement in these tests is always an HTMLElement\nwhose familiar methods and properties you can explore within a test.

\n

Here's the previous test, re-implemented with fixture.debugElement.nativeElement:

\n\nit('should find the <p> with fixture.debugElement.nativeElement)', () => {\n const bannerDe: DebugElement = fixture.debugElement;\n const bannerEl: HTMLElement = bannerDe.nativeElement;\n const p = bannerEl.querySelector('p');\n expect(p.textContent).toEqual('banner works!');\n});\n\n\n

The DebugElement has other methods and properties that\nare useful in tests, as you'll see elsewhere in this guide.

\n

You import the DebugElement symbol from the Angular core library.

\n\nimport { DebugElement } from '@angular/core';\n\n\n\n

By.css()link

\n

Although the tests in this guide all run in the browser,\nsome apps might run on a different platform at least some of the time.

\n

For example, the component might render first on the server as part of a strategy to make the application launch faster on poorly connected devices. The server-side renderer might not support the full HTML element API.\nIf it doesn't support querySelector, the previous test could fail.

\n

The DebugElement offers query methods that work for all supported platforms.\nThese query methods take a predicate function that returns true when a node in the DebugElement tree matches the selection criteria.

\n

You create a predicate with the help of a By class imported from a\nlibrary for the runtime platform. Here's the By import for the browser platform:

\n\nimport { By } from '@angular/platform-browser';\n\n\n

The following example re-implements the previous test with\nDebugElement.query() and the browser's By.css method.

\n\nit('should find the <p> with fixture.debugElement.query(By.css)', () => {\n const bannerDe: DebugElement = fixture.debugElement;\n const paragraphDe = bannerDe.query(By.css('p'));\n const p: HTMLElement = paragraphDe.nativeElement;\n expect(p.textContent).toEqual('banner works!');\n});\n\n\n

Some noteworthy observations:

\n\n

When you're filtering by CSS selector and only testing properties of a browser's native element, the By.css approach may be overkill.

\n

It's often easier and more clear to filter with a standard HTMLElement method\nsuch as querySelector() or querySelectorAll().

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