Skip to content

Commit d820c68

Browse files
committed
basic testin components added
1 parent 19401e4 commit d820c68

3 files changed

Lines changed: 273 additions & 0 deletions

File tree

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# Basic of testing components
2+
3+
A component, unlike all other parts of an Angular application, combines an HTML template and a TypeScript class. The component truly is the template and the class working together. To adequately test a component, you should test that they work together as intended.
4+
5+
Such tests require creating the component's host element in the browser DOM, as Angular does, and investigating the component class's interaction with the DOM as described by its template.
6+
7+
In many cases, testing the component class alone, without DOM involvement, can validate much of the component's behavior in a straightforward, more obvious way.
8+
9+
## Component DOM testing
10+
11+
### CLI-generated tests
12+
13+
The CLI creates an initial test file for you by default when you ask it to generate a new component.
14+
15+
```bash
16+
npx ng generate component banner --inline-template --inline-style
17+
```
18+
19+
Generates an initial test
20+
21+
```ts
22+
import { ComponentFixture, TestBed } from "@angular/core/testing";
23+
24+
import { BannerComponent } from "./banner.component";
25+
26+
describe("BannerComponent", () => {
27+
let component: BannerComponent;
28+
let fixture: ComponentFixture<BannerComponent>;
29+
30+
beforeEach(async () => {
31+
await TestBed.configureTestingModule({
32+
imports: [BannerComponent],
33+
}).compileComponents();
34+
35+
fixture = TestBed.createComponent(BannerComponent);
36+
component = fixture.componentInstance;
37+
fixture.detectChanges();
38+
});
39+
40+
it("should create", () => {
41+
expect(component).toBeTruthy();
42+
});
43+
});
44+
```
45+
46+
> `compileComponents` is async, it uses [`waitForAsync`](https://angular.dev/api/core/testing/waitForAsync)
47+
48+
### Reduce the setup
49+
50+
We can reduce the file size, since we don not need to compile the component.
51+
52+
Update `app/banner/banner.component.spec.ts`
53+
54+
```ts
55+
import { TestBed } from "@angular/core/testing";
56+
57+
import { BannerComponent } from "./banner.component";
58+
59+
describe("BannerComponent", () => {
60+
it("should create", () => {
61+
TestBed.configureTestingModule({ imports: [BannerComponent] });
62+
const fixture = TestBed.createComponent(BannerComponent);
63+
const component = fixture.componentInstance;
64+
expect(component).toBeTruthy();
65+
});
66+
});
67+
```
68+
69+
> There's no need to declare or import anything else. The default test module is pre-configured with something like the `BrowserModule` from `@angular/platform-browser`.
70+
71+
### `createComponent()`
72+
73+
`TestBed.createComponent()` creates an instance of the `BannerComponent`, adds a corresponding element to the test-runner DOM, and returns a `ComponentFixture`.
74+
75+
> IMPORTANT: Do not re-configure TestBed after calling `createCompnent`
76+
77+
The `createComponent` method **freezes the current TestBed definition**, closing it to further configuration.
78+
79+
You cannot call any more TestBed configuration methods, not `configureTestingModule()`, nor `get()`, nor any of the override... methods. If you try, `TestBed` throws an error.
80+
81+
### `ComponentFixture`
82+
83+
[ComponentFixture](https://angular.dev/api/core/testing/ComponentFixture) s a test harness for interacting with the created component and its corresponding element.
84+
85+
### `beforeEach()`
86+
87+
We can use `beforeEach` for the `TestBed` configuration
88+
89+
Update `banner.component.spec.ts`
90+
91+
```diff
92+
describe('BannerComponent', () => {
93+
+ let component: BannerComponent;
94+
+ let fixture: ComponentFixture<BannerComponent>;
95+
+
96+
+ beforeEach(() => {
97+
+ TestBed.configureTestingModule({ imports: [BannerComponent] });
98+
+ fixture = TestBed.createComponent(BannerComponent);
99+
+ component = fixture.componentInstance;
100+
+ });
101+
+
102+
it('should create', () => {
103+
- TestBed.configureTestingModule({ imports: [BannerComponent] });
104+
- const fixture = TestBed.createComponent(BannerComponent);
105+
- const component = fixture.componentInstance;
106+
expect(component).toBeTruthy();
107+
});
108+
});
109+
```
110+
111+
### `nativeElement`
112+
113+
We can retrieve the component's element using `fixture.nativeElement`
114+
115+
Update `banner.component.spec.ts`
116+
117+
```ts
118+
it('should contain "banner works!"', () => {
119+
const bannerElement: HTMLElement = fixture.nativeElement;
120+
expect(bannerElement.textContent).toContain("banner works!");
121+
});
122+
```
123+
124+
```bash
125+
npx ng test --include app/banner/banner.component.spec.ts
126+
```
127+
128+
Angular can't know at compile time what kind of HTML element the nativeElement is or if it even is an HTML element. The application might be running on a _non-browser platform_, such as the server or a Web Worker, where the element might have a diminished API or not exist at all.
129+
130+
The tests in this guide are designed to run in a browser so a `nativeElement` value will always be an `HTMLElement` or one of its derived classes.
131+
132+
Knowing that it is an `HTMLElement` of some sort, use the standard HTML `querySelector` to dive deeper into the element tree.
133+
134+
Update `banner.component.spec.ts`
135+
136+
```ts
137+
it('should have <p> with "banner works!"', () => {
138+
const bannerElement: HTMLElement = fixture.nativeElement;
139+
const p = bannerElement.querySelector("p");
140+
expect(p?.textContent).toContain("banner works!");
141+
});
142+
```
143+
144+
### `DebugElement`
145+
146+
The Angular _fixture_ provides the component's element directly through the `fixture.nativeElement`.
147+
148+
```ts
149+
const bannerElement: HTMLElement = fixture.nativeElement;
150+
```
151+
152+
This is actually a convenience method, implemented as `fixture.debugElement.nativeElement`.
153+
154+
```ts
155+
const bannerDe: DebugElement = fixture.debugElement;
156+
const bannerEl: HTMLElement = bannerDe.nativeElement;
157+
```
158+
159+
The properties of the `nativeElement` depend upon the runtime environment. You could be running these tests on a _non-browser_ platform that doesn't have a DOM or whose DOM-emulation doesn't support the full HTMLElement API.
160+
161+
Angular relies on the DebugElement abstraction to work safely across _all supported platforms_. Instead of creating an HTML element tree, Angular creates a DebugElement tree that wraps the native elements for the runtime platform. The `nativeElement` property unwraps the `DebugElement` and returns the platform-specific element object.
162+
163+
Update `banner.component.spec.ts`
164+
165+
```diff
166+
import { ComponentFixture, TestBed } from '@angular/core/testing';
167+
168+
import { BannerComponent } from './banner.component';
169+
+import { DebugElement } from '@angular/core';
170+
....
171+
```
172+
173+
```ts
174+
it("should find the <p> with fixture.debugElement.nativeElement)", () => {
175+
const bannerDe: DebugElement = fixture.debugElement;
176+
const bannerEl: HTMLElement = bannerDe.nativeElement;
177+
const p = bannerEl.querySelector("p")!;
178+
expect(p.textContent).toContain("banner works!");
179+
});
180+
```
181+
182+
### `By.css()`
183+
184+
Although the tests in this guide all run in the browser, some applications might run on a different platform at least some of the time.
185+
186+
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. If it doesn't support `querySelector`, the previous test could fail.
187+
188+
The DebugElement offers query methods that work for all supported platforms. These query methods take a _predicate_ function that returns true when a node in the DebugElement tree matches the selection criteria.
189+
190+
Update `banner.component.spec.ts`
191+
192+
```diff
193+
import { ComponentFixture, TestBed } from '@angular/core/testing';
194+
195+
import { BannerComponent } from './banner.component';
196+
import { DebugElement } from '@angular/core';
197+
+import { By } from '@angular/platform-browser';
198+
....
199+
```
200+
201+
```ts
202+
it("should find the <p> with fixture.debugElement.query(By.css)", () => {
203+
const bannerDe: DebugElement = fixture.debugElement;
204+
const paragraphDe = bannerDe.query(By.css("p"));
205+
const p: HTMLElement = paragraphDe.nativeElement;
206+
expect(p.textContent).toContain("banner works!");
207+
});
208+
```
209+
210+
- The `By.css()` static method selects `DebugElement` nodes with a [standard CSS selector](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Basic_selectors)
211+
- The query returns a `DebugElement` for the paragraph
212+
- You must unwrap the result to get the paragraph element
213+
214+
When you're filtering by CSS selector and only testing properties of a browser's _native element_, the `By.css` approach might be overkill.
215+
216+
It's often more straightforward and clear to filter with a standard HTMLElement method such as `querySelector()` or `querySelectorAll()`.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { BannerComponent } from './banner.component';
4+
import { DebugElement } from '@angular/core';
5+
import { By } from '@angular/platform-browser';
6+
7+
describe('BannerComponent', () => {
8+
let component: BannerComponent;
9+
let fixture: ComponentFixture<BannerComponent>;
10+
11+
beforeEach(() => {
12+
TestBed.configureTestingModule({
13+
imports: [BannerComponent],
14+
});
15+
16+
fixture = TestBed.createComponent(BannerComponent);
17+
component = fixture.componentInstance;
18+
});
19+
20+
it('should create', () => {
21+
expect(component).toBeTruthy();
22+
});
23+
24+
it('should contain "banner works!"', () => {
25+
const bannerElement: HTMLElement = fixture.nativeElement;
26+
expect(bannerElement.textContent).toContain("banner works!");
27+
});
28+
29+
it("should find the <p> with fixture.debugElement.nativeElement)", () => {
30+
const bannerDe: DebugElement = fixture.debugElement;
31+
const bannerEl: HTMLElement = bannerDe.nativeElement;
32+
const p = bannerEl.querySelector("p")!;
33+
expect(p.textContent).toContain("banner works!");
34+
});
35+
36+
it("should find the <p> with fixture.debugElement.query(By.css)", () => {
37+
const bannerDe: DebugElement = fixture.debugElement;
38+
const paragraphDe = bannerDe.query(By.css("p"));
39+
const p: HTMLElement = paragraphDe.nativeElement;
40+
expect(p.textContent).toContain("banner works!");
41+
});
42+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Component } from '@angular/core';
2+
3+
@Component({
4+
selector: 'app-banner',
5+
imports: [],
6+
template: `
7+
<p>
8+
banner works!
9+
</p>
10+
`,
11+
styles: ``
12+
})
13+
export class BannerComponent {
14+
15+
}

0 commit comments

Comments
 (0)