mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
docs: rewrite testing docs
Those rewrites focus on using Vitest instead of jasmine, drop usages of `fakeAsync`, present modern testing strategy that rely on `whenStable` more than explicit calls to `detectChanges`.
fixes #42748, #48510, #64962, #65987, #66029, #66150
(cherry picked from commit 71cde39ff0)
This commit is contained in:
@@ -623,11 +623,6 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
path: 'guide/testing/utility-apis',
|
||||
contentPath: 'guide/testing/utility-apis',
|
||||
},
|
||||
{
|
||||
label: 'Zone.js Testing Utilities',
|
||||
path: 'guide/testing/zone-js-testing-utilities',
|
||||
contentPath: 'guide/testing/zone-js-testing-utilities',
|
||||
},
|
||||
{
|
||||
label: 'Component harnesses overview',
|
||||
path: 'guide/testing/component-harnesses-overview',
|
||||
@@ -658,6 +653,11 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
path: 'guide/testing/karma',
|
||||
contentPath: 'guide/testing/karma',
|
||||
},
|
||||
{
|
||||
label: 'Zone.js Testing Utilities',
|
||||
path: 'guide/testing/zone-js-testing-utilities',
|
||||
contentPath: 'guide/testing/zone-js-testing-utilities',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {FavoriteColorReactiveComponent} from './reactive/favorite-color/favorite-color.component';
|
||||
import {FavoriteColorTemplateComponent} from './template/favorite-color/favorite-color.component';
|
||||
import {FavoriteColorReactive} from './reactive/favorite-color/favorite-color.component';
|
||||
import {FavoriteColorTemplate} from './template/favorite-color/favorite-color.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css'],
|
||||
imports: [FavoriteColorTemplateComponent, FavoriteColorReactiveComponent],
|
||||
imports: [FavoriteColorTemplate, FavoriteColorReactive],
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'forms-intro';
|
||||
|
||||
+6
-12
@@ -1,22 +1,16 @@
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
|
||||
import {createNewEvent} from '../../shared/utils';
|
||||
import {FavoriteColorReactiveComponent} from './favorite-color.component';
|
||||
import {FavoriteColorReactive} from './favorite-color.component';
|
||||
|
||||
describe('Favorite Color Component', () => {
|
||||
let component: FavoriteColorReactiveComponent;
|
||||
let fixture: ComponentFixture<FavoriteColorReactiveComponent>;
|
||||
let component: FavoriteColorReactive;
|
||||
let fixture: ComponentFixture<FavoriteColorReactive>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [FavoriteColorReactiveComponent],
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FavoriteColorReactiveComponent);
|
||||
beforeEach(async () => {
|
||||
fixture = TestBed.createComponent(FavoriteColorReactive);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
|
||||
+1
-1
@@ -6,6 +6,6 @@ import {FormControl, ReactiveFormsModule} from '@angular/forms';
|
||||
template: ` Favorite Color: <input type="text" [formControl]="favoriteColorControl" /> `,
|
||||
imports: [ReactiveFormsModule],
|
||||
})
|
||||
export class FavoriteColorReactiveComponent {
|
||||
export class FavoriteColorReactive {
|
||||
favoriteColorControl = new FormControl('');
|
||||
}
|
||||
|
||||
+15
-24
@@ -1,22 +1,16 @@
|
||||
import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {createNewEvent} from '../../shared/utils';
|
||||
import {FavoriteColorTemplateComponent} from './favorite-color.component';
|
||||
import {FavoriteColorTemplate} from './favorite-color.component';
|
||||
|
||||
describe('FavoriteColorComponent', () => {
|
||||
let component: FavoriteColorTemplateComponent;
|
||||
let fixture: ComponentFixture<FavoriteColorTemplateComponent>;
|
||||
let component: FavoriteColorTemplate;
|
||||
let fixture: ComponentFixture<FavoriteColorTemplate>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [FavoriteColorTemplateComponent],
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FavoriteColorTemplateComponent);
|
||||
beforeEach(async () => {
|
||||
fixture = TestBed.createComponent(FavoriteColorTemplate);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
@@ -24,30 +18,27 @@ describe('FavoriteColorComponent', () => {
|
||||
});
|
||||
|
||||
// #docregion model-to-view
|
||||
it('should update the favorite color on the input field', fakeAsync(() => {
|
||||
component.favoriteColor = 'Blue';
|
||||
it('should update the favorite color on the input field', async () => {
|
||||
component.favoriteColor.set('Blue');
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
tick();
|
||||
await fixture.whenStable();
|
||||
|
||||
const input = fixture.nativeElement.querySelector('input');
|
||||
|
||||
expect(input.value).toBe('Blue');
|
||||
}));
|
||||
});
|
||||
// #enddocregion model-to-view
|
||||
|
||||
// #docregion view-to-model
|
||||
it('should update the favorite color in the component', fakeAsync(() => {
|
||||
it('should update the favorite color in the component', async () => {
|
||||
const input = fixture.nativeElement.querySelector('input');
|
||||
const event = createNewEvent('input');
|
||||
|
||||
input.value = 'Red';
|
||||
input.dispatchEvent(event);
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.favoriteColor).toEqual('Red');
|
||||
}));
|
||||
expect(component.favoriteColor()).toEqual('Red');
|
||||
});
|
||||
// #enddocregion view-to-model
|
||||
});
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {Component, signal} from '@angular/core';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
@@ -6,6 +6,6 @@ import {FormsModule} from '@angular/forms';
|
||||
template: ` Favorite Color: <input type="text" [(ngModel)]="favoriteColor" /> `,
|
||||
imports: [FormsModule],
|
||||
})
|
||||
export class FavoriteColorTemplateComponent {
|
||||
favoriteColor = '';
|
||||
export class FavoriteColorTemplate {
|
||||
favoriteColor = signal('');
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ Here are the steps performed in the model to view test.
|
||||
|
||||
1. Use the component instance to set the value of the `favoriteColor` property.
|
||||
1. Run change detection through the test fixture.
|
||||
1. Use the `tick()` method to simulate the passage of time within the `fakeAsync()` task.
|
||||
1. Use `await fixture.whenStable()` to wait for the next rendering.
|
||||
1. Query the view for the form input element.
|
||||
1. Assert that the input value matches the value of the `favoriteColor` property in the component instance.
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@ Testing routing and navigation is essential to ensure your application behaves c
|
||||
|
||||
This guide assumes you are familiar with the following tools and libraries:
|
||||
|
||||
- **[Jasmine](https://jasmine.github.io/)** - JavaScript testing framework that provides the testing syntax (`describe`, `it`, `expect`)
|
||||
- **[Karma](https://karma-runner.github.io/)** - Test runner that executes tests in browsers
|
||||
- **[Vitest](https://vitest.dev/)** - JavaScript testing framework that provides the testing syntax (`describe`, `it`, `expect`)
|
||||
- **[Angular Testing Utilities](guide/testing)** - Angular's built-in testing tools ([`TestBed`](api/core/testing/TestBed), [`ComponentFixture`](api/core/testing/ComponentFixture))
|
||||
- **[`RouterTestingHarness`](api/router/testing/RouterTestingHarness)** - Test harness for testing routed components with built-in navigation and component testing capabilities
|
||||
|
||||
@@ -326,6 +325,6 @@ export class Search {
|
||||
1. **Use RouterTestingHarness** - For testing routed components, use [`RouterTestingHarness`](api/router/testing/RouterTestingHarness) which provides a cleaner API and eliminates the need for test host components. It offers direct component access, built-in navigation, and better type safety. However, it isn't as suitable for some scenarios, such as testing named outlets, where you may need to create custom host components.
|
||||
2. **Handle external dependencies thoughtfully** - Prefer real implementations when possible for more realistic tests. If real implementations aren't feasible (e.g., external APIs), use fakes that approximate the real behavior. Use mocks or stubs only as a last resort, as they can make tests brittle and less reliable.
|
||||
3. **Test navigation state** - Verify both the navigation action and the resulting application state, including URL changes and component rendering.
|
||||
4. **Handle asynchronous operations** - Router navigation is asynchronous. Use `async/await` or [`fakeAsync`](api/core/testing/fakeAsync) to properly handle timing in your tests.
|
||||
4. **Handle asynchronous operations** - Router navigation is asynchronous. Use `async/await` to properly handle timing in your tests.
|
||||
5. **Test error scenarios** - Include tests for invalid routes, failed navigation, and guard rejections to ensure your application handles edge cases gracefully.
|
||||
6. **Do not mock Angular Router** - Instead, provide real route configurations and use the harness to navigate. This makes your tests more robust and less likely to break on internal Angular updates, while also ensuring you catch real issues when the router updates since mocks can hide breaking changes.
|
||||
|
||||
@@ -3,20 +3,65 @@
|
||||
An _attribute directive_ modifies the behavior of an element, component or another directive.
|
||||
Its name reflects the way the directive is applied: as an attribute on a host element.
|
||||
|
||||
## Testing the `HighlightDirective`
|
||||
## Testing the `Highlight` directive
|
||||
|
||||
The sample application's `HighlightDirective` sets the background color of an element based on either a data bound color or a default color \(lightgray\).
|
||||
The sample application's `Highlight` directive sets the background color of an element based on either a data bound color or a default color \(lightgray\).
|
||||
It also sets a custom property of the element \(`customProperty`\) to `true` for no reason other than to show that it can.
|
||||
|
||||
<docs-code header="highlight.directive.ts" path="adev/src/content/examples/testing/src/app/shared/highlight.directive.ts"/>
|
||||
```ts
|
||||
import {Directive, inject, input} from '@angular/core';
|
||||
|
||||
It's used throughout the application, perhaps most simply in the `AboutComponent`:
|
||||
/**
|
||||
* Set backgroundColor for the attached element to highlight color
|
||||
* and set the element's customProperty attribute to true
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[highlight]',
|
||||
host: {
|
||||
'[style.backgroundColor]': 'bgColor() || defaultColor',
|
||||
},
|
||||
})
|
||||
export class Highlight {
|
||||
readonly defaultColor = 'rgb(211, 211, 211)'; // lightgray
|
||||
|
||||
<docs-code header="about.component.ts" path="adev/src/content/examples/testing/src/app/about/about.component.ts"/>
|
||||
readonly bgColor = input('', {alias: 'highlight'});
|
||||
}
|
||||
```
|
||||
|
||||
Testing the specific use of the `HighlightDirective` within the `AboutComponent` requires only the techniques explored in the ["Nested component tests"](guide/testing/components-scenarios#nested-component-tests) section of [Component testing scenarios](guide/testing/components-scenarios).
|
||||
It's used throughout the application, perhaps most simply in the `About` component:
|
||||
|
||||
<docs-code header="about.component.spec.ts" path="adev/src/content/examples/testing/src/app/about/about.component.spec.ts" region="tests"/>
|
||||
```ts
|
||||
@Component({
|
||||
imports: [Twain, Highlight],
|
||||
template: `
|
||||
<h2 highlight="skyblue">About</h2>
|
||||
<h3>Quote of the day:</h3>
|
||||
<twain-quote />
|
||||
`,
|
||||
})
|
||||
export class About {}
|
||||
```
|
||||
|
||||
Testing the specific use of the `Highlight` directive within the `About` component requires only the techniques explored in the ["Nested component tests"](guide/testing/components-scenarios#nested-component-tests) section of [Component testing scenarios](guide/testing/components-scenarios).
|
||||
|
||||
```ts
|
||||
let fixture: ComponentFixture<About>;
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [TwainService, UserService],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
});
|
||||
fixture = TestBed.createComponent(About);
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should have skyblue <h2>', () => {
|
||||
const h2: HTMLElement = fixture.nativeElement.querySelector('h2');
|
||||
const bgColor = h2.style.backgroundColor;
|
||||
expect(bgColor).toBe('skyblue');
|
||||
});
|
||||
```
|
||||
|
||||
However, testing a single use case is unlikely to explore the full range of a directive's capabilities.
|
||||
Finding and testing all components that use the directive is tedious, brittle, and almost as unlikely to afford full coverage.
|
||||
@@ -26,16 +71,75 @@ Isolated unit tests don't touch the DOM and, therefore, do not inspire confidenc
|
||||
|
||||
A better solution is to create an artificial test component that demonstrates all ways to apply the directive.
|
||||
|
||||
<docs-code header="highlight.directive.spec.ts (TestComponent)" path="adev/src/content/examples/testing/src/app/shared/highlight.directive.spec.ts" region="test-component"/>
|
||||
```angular-ts
|
||||
@Component({
|
||||
imports: [Highlight],
|
||||
template: `
|
||||
<h2 highlight="yellow">Something Yellow</h2>
|
||||
<h2 highlight>The Default (Gray)</h2>
|
||||
<h2>No Highlight</h2>
|
||||
<input #box [highlight]="box.value" value="cyan" />
|
||||
`,
|
||||
})
|
||||
class Test {}
|
||||
```
|
||||
|
||||
<img alt="HighlightDirective spec in action" src="assets/images/guide/testing/highlight-directive-spec.png">
|
||||
|
||||
HELPFUL: The `<input>` case binds the `HighlightDirective` to the name of a color value in the input box.
|
||||
HELPFUL: The `<input>` case binds the `Highlight` to the name of a color value in the input box.
|
||||
The initial value is the word "cyan" which should be the background color of the input box.
|
||||
|
||||
Here are some tests of this component:
|
||||
|
||||
<docs-code header="highlight.directive.spec.ts (selected tests)" path="adev/src/content/examples/testing/src/app/shared/highlight.directive.spec.ts" region="selected-tests"/>
|
||||
```ts
|
||||
let fixture: ComponentFixture<Test>;
|
||||
let des: DebugElement[]; // the three elements w/ the directive
|
||||
|
||||
beforeEach(async () => {
|
||||
fixture = TestBed.createComponent(Test);
|
||||
await fixture.whenStable();
|
||||
|
||||
// all elements with an attached Highlight
|
||||
des = fixture.debugElement.queryAll(By.directive(Highlight));
|
||||
});
|
||||
|
||||
// color tests
|
||||
it('should have three highlighted elements', () => {
|
||||
expect(des.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should color 1st <h2> background "yellow"', () => {
|
||||
const bgColor = des[0].nativeElement.style.backgroundColor;
|
||||
expect(bgColor).toBe('yellow');
|
||||
});
|
||||
|
||||
it('should color 2nd <h2> background w/ default color', () => {
|
||||
const dir = des[1].injector.get(Highlight);
|
||||
const bgColor = des[1].nativeElement.style.backgroundColor;
|
||||
expect(bgColor).toBe(dir.defaultColor);
|
||||
});
|
||||
|
||||
it('should bind <input> background to value color', async () => {
|
||||
// easier to work with nativeElement
|
||||
const input = des[2].nativeElement as HTMLInputElement;
|
||||
expect(input.style.backgroundColor, 'initial backgroundColor').toBe('cyan');
|
||||
|
||||
input.value = 'green';
|
||||
|
||||
// Dispatch a DOM event so that Angular responds to the input value change.
|
||||
input.dispatchEvent(new Event('input'));
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(input.style.backgroundColor, 'changed backgroundColor').toBe('green');
|
||||
});
|
||||
|
||||
it('bare <h2> should not have a backgroundColor', () => {
|
||||
// the h2 without the Highlight directive
|
||||
const bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])'));
|
||||
|
||||
expect(bareH2.styles.backgroundColor).toBeUndefined();
|
||||
});
|
||||
```
|
||||
|
||||
A few techniques are noteworthy:
|
||||
|
||||
@@ -47,6 +151,6 @@ A few techniques are noteworthy:
|
||||
But feel free to exploit the `nativeElement` when that seems easier or more clear than the abstraction.
|
||||
|
||||
- Angular adds a directive to the injector of the element to which it is applied.
|
||||
The test for the default color uses the injector of the second `<h2>` to get its `HighlightDirective` instance and its `defaultColor`.
|
||||
The test for the default color uses the injector of the second `<h2>` to get its `Highlight` instance and its `defaultColor`.
|
||||
|
||||
- `DebugElement.properties` affords access to the artificial custom property that is set by the directive
|
||||
|
||||
@@ -17,9 +17,9 @@ Classes alone cannot tell you if the component is going to render properly, resp
|
||||
|
||||
- Is `Lightswitch.clicked()` bound to anything such that the user can invoke it?
|
||||
- Is the `Lightswitch.message` displayed?
|
||||
- Can the user actually select the hero displayed by `DashboardHeroComponent`?
|
||||
- Can the user actually select the hero displayed by the `DashboardHero` component?
|
||||
- Is the hero name displayed as expected \(such as uppercase\)?
|
||||
- Is the welcome message displayed by the template of `WelcomeComponent`?
|
||||
- Is the welcome message displayed by the template of the `Welcome` component?
|
||||
|
||||
These might not be troubling questions for the preceding simple components illustrated.
|
||||
But many components have complex interactions with the DOM elements described in their templates, causing HTML to appear and disappear as the component state changes.
|
||||
@@ -32,19 +32,37 @@ To write these kinds of test, you'll use additional features of the `TestBed` as
|
||||
|
||||
The CLI creates an initial test file for you by default when you ask it to generate a new component.
|
||||
|
||||
For example, the following CLI command generates a `BannerComponent` in the `app/banner` folder \(with inline template and styles\):
|
||||
For example, the following CLI command generates a `Banner` component in the `app/banner` folder \(with inline template and styles\):
|
||||
|
||||
```shell
|
||||
ng generate component banner --inline-template --inline-style --module app
|
||||
ng generate component banner --inline-template --inline-style
|
||||
```
|
||||
|
||||
It also generates an initial test file for the component, `banner-external.component.spec.ts`, that looks like this:
|
||||
It also generates an initial test file for the component, `banner.spec.ts`, that looks like this:
|
||||
|
||||
<docs-code header="banner-external.component.spec.ts (initial)" path="adev/src/content/examples/testing/src/app/banner/banner-initial.component.spec.ts" region="v1"/>
|
||||
```ts
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {Banner} from './banner';
|
||||
|
||||
HELPFUL: Because `compileComponents` is asynchronous, it uses the [`waitForAsync`](api/core/testing/waitForAsync) utility function imported from `@angular/core/testing`.
|
||||
describe('Banner', () => {
|
||||
let component: Banner;
|
||||
let fixture: ComponentFixture<Banner>;
|
||||
|
||||
Refer to the [waitForAsync](guide/testing/components-scenarios#waitForAsync) section for more details.
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [Banner],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(Banner);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Reduce the setup
|
||||
|
||||
@@ -55,25 +73,30 @@ The rest of the file is boilerplate setup code anticipating more advanced tests
|
||||
You'll learn about these advanced test features in the following sections.
|
||||
For now, you can radically reduce this test file to a more manageable size:
|
||||
|
||||
<docs-code header="banner-initial.component.spec.ts (minimal)" path="adev/src/content/examples/testing/src/app/banner/banner-initial.component.spec.ts" region="v2"/>
|
||||
|
||||
In this example, the metadata object passed to `TestBed.configureTestingModule` simply declares `BannerComponent`, the component to test.
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/banner/banner-initial.component.spec.ts" region="configureTestingModule"/>
|
||||
|
||||
HELPFUL: 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`.
|
||||
```ts
|
||||
describe('Banner (minimal)', () => {
|
||||
it('should create', () => {
|
||||
const fixture = TestBed.createComponent(Banner);
|
||||
const component = fixture.componentInstance;
|
||||
expect(component).toBeDefined();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Later you'll call `TestBed.configureTestingModule()` with imports, providers, and more declarations to suit your testing needs.
|
||||
Optional `override` methods can further fine-tune aspects of the configuration.
|
||||
|
||||
NOTE: `TestBed.compileComponents` is only required when `@defer` blocks are used in the tested components.
|
||||
|
||||
### `createComponent()`
|
||||
|
||||
After configuring `TestBed`, you call its `createComponent()` method.
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/banner/banner-initial.component.spec.ts" region="createComponent"/>
|
||||
```ts
|
||||
const fixture = TestBed.createComponent(Banner);
|
||||
```
|
||||
|
||||
`TestBed.createComponent()` creates an instance of the `BannerComponent`, adds a corresponding element to the test-runner DOM, and returns a [`ComponentFixture`](#componentfixture).
|
||||
`TestBed.createComponent()` creates an instance of the `Banner` component, adds a corresponding element to the test-runner DOM, and returns a [`ComponentFixture`](#componentfixture).
|
||||
|
||||
IMPORTANT: Do not re-configure `TestBed` after calling `createComponent`.
|
||||
|
||||
@@ -84,22 +107,62 @@ If you try, `TestBed` throws an error.
|
||||
|
||||
### `ComponentFixture`
|
||||
|
||||
The [ComponentFixture](api/core/testing/ComponentFixture) is a test harness for interacting with the created component and its corresponding element.
|
||||
The [`ComponentFixture`](api/core/testing/ComponentFixture) is a test harness for interacting with the created component and its corresponding element.
|
||||
|
||||
Access the component instance through the fixture and confirm it exists with a Jasmine expectation:
|
||||
Access the component instance through the fixture and confirm it exists with an expectation:
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/banner/banner-initial.component.spec.ts" region="componentInstance"/>
|
||||
```ts
|
||||
const component = fixture.componentInstance;
|
||||
expect(component).toBeDefined();
|
||||
```
|
||||
|
||||
### `beforeEach()`
|
||||
|
||||
You will add more tests as this component evolves.
|
||||
Rather than duplicate the `TestBed` configuration for each test, you refactor to pull the setup into a Jasmine `beforeEach()` and some supporting variables:
|
||||
Rather than duplicate the `TestBed` configuration for each test, you refactor to pull the setup into a `beforeEach()` and some supporting variables:
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/banner/banner-initial.component.spec.ts" region="v3"/>
|
||||
```ts
|
||||
describe('Banner (with beforeEach)', () => {
|
||||
let component: Banner;
|
||||
let fixture: ComponentFixture<Banner>;
|
||||
|
||||
beforeEach(async () => {
|
||||
fixture = TestBed.createComponent(Banner);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
await fixture.whenStable(); // necessary to wait for the initial rendering
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeDefined();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
HELPFUL: By awaiting the initial rendering in the `beforeEach` with `await fixture.whenStable` the single tests synchronous.
|
||||
|
||||
Now add a test that gets the component's element from `fixture.nativeElement` and looks for the expected text.
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/banner/banner-initial.component.spec.ts" region="v4-test-2"/>
|
||||
```ts
|
||||
it('should contain "banner works!"', () => {
|
||||
const bannerElement: HTMLElement = fixture.nativeElement;
|
||||
expect(bannerElement.textContent).toContain('banner works!');
|
||||
});
|
||||
```
|
||||
|
||||
### create a `setup` function
|
||||
|
||||
As an alternative to `beforeEach`, you can also create a setup function that you will call in every test.
|
||||
A setup function has the advantage of being customizable via parameters.
|
||||
|
||||
Here is an example of what a setup function could look like:
|
||||
|
||||
```ts
|
||||
function setup(providers?: StaticProviders[]): ComponentFixture<Banner> {
|
||||
TestBed.configureTestingModule({providers});
|
||||
return TestBed.createComponent(Banner);
|
||||
}
|
||||
```
|
||||
|
||||
### `nativeElement`
|
||||
|
||||
@@ -107,7 +170,7 @@ The value of `ComponentFixture.nativeElement` has the `any` type.
|
||||
Later you'll encounter the `DebugElement.nativeElement` and it too has the `any` type.
|
||||
|
||||
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](https://developer.mozilla.org/docs/Web/API/Web_Workers_API), where the element might have a diminished API or not exist at all.
|
||||
The application might be running on a _non-browser platform_, such as the server or a node environment, where the element might have a diminished API or not exist at all.
|
||||
|
||||
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.
|
||||
|
||||
@@ -115,17 +178,28 @@ Knowing that it is an `HTMLElement` of some sort, use the standard HTML `querySe
|
||||
|
||||
Here's another test that calls `HTMLElement.querySelector` to get the paragraph element and look for the banner text:
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/banner/banner-initial.component.spec.ts" region="v4-test-3"/>
|
||||
```ts
|
||||
it('should have <p> with "banner works!"', () => {
|
||||
const bannerElement: HTMLElement = fixture.nativeElement;
|
||||
const p = bannerElement.querySelector('p')!;
|
||||
expect(p.textContent).toEqual('banner works!');
|
||||
});
|
||||
```
|
||||
|
||||
### `DebugElement`
|
||||
|
||||
The Angular _fixture_ provides the component's element directly through the `fixture.nativeElement`.
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/banner/banner-initial.component.spec.ts" region="nativeElement"/>
|
||||
```ts
|
||||
const bannerElement: HTMLElement = fixture.nativeElement;
|
||||
```
|
||||
|
||||
This is actually a convenience method, implemented as `fixture.debugElement.nativeElement`.
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/banner/banner-initial.component.spec.ts" region="debugElement-nativeElement"/>
|
||||
```ts
|
||||
const bannerDe: DebugElement = fixture.debugElement;
|
||||
const bannerEl: HTMLElement = bannerDe.nativeElement;
|
||||
```
|
||||
|
||||
There's a good reason for this circuitous path to the element.
|
||||
|
||||
@@ -140,13 +214,22 @@ Because the sample tests for this guide are designed to run only in a browser, a
|
||||
|
||||
Here's the previous test, re-implemented with `fixture.debugElement.nativeElement`:
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/banner/banner-initial.component.spec.ts" region="v4-test-4"/>
|
||||
```ts
|
||||
it('should find the <p> with fixture.debugElement.nativeElement', () => {
|
||||
const bannerDe: DebugElement = fixture.debugElement;
|
||||
const bannerEl: HTMLElement = bannerDe.nativeElement;
|
||||
const p = bannerEl.querySelector('p')!;
|
||||
expect(p.textContent).toEqual('banner works!');
|
||||
});
|
||||
```
|
||||
|
||||
The `DebugElement` has other methods and properties that are useful in tests, as you'll see elsewhere in this guide.
|
||||
|
||||
You import the `DebugElement` symbol from the Angular core library.
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/banner/banner-initial.component.spec.ts" region="import-debug-element"/>
|
||||
```ts
|
||||
import {DebugElement} from '@angular/core';
|
||||
```
|
||||
|
||||
### `By.css()`
|
||||
|
||||
@@ -162,11 +245,20 @@ These query methods take a _predicate_ function that returns `true` when a node
|
||||
You create a _predicate_ with the help of a `By` class imported from a library for the runtime platform.
|
||||
Here's the `By` import for the browser platform:
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/banner/banner-initial.component.spec.ts" region="import-by"/>
|
||||
```ts
|
||||
import {By} from '@angular/platform-browser';
|
||||
```
|
||||
|
||||
The following example re-implements the previous test with `DebugElement.query()` and the browser's `By.css` method.
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/banner/banner-initial.component.spec.ts" region="v4-test-5"/>
|
||||
```ts
|
||||
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
|
||||
const bannerDe: DebugElement = fixture.debugElement;
|
||||
const paragraphDe = bannerDe.query(By.css('p'));
|
||||
const p: HTMLElement = paragraphDe.nativeElement;
|
||||
expect(p.textContent).toEqual('banner works!');
|
||||
});
|
||||
```
|
||||
|
||||
Some noteworthy observations:
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -124,9 +124,11 @@ class MyMenuItem {}
|
||||
|
||||
@Component({
|
||||
selector: 'my-menu',
|
||||
template: `<my-popup>
|
||||
<ng-content />
|
||||
</my-popup> `,
|
||||
template: `
|
||||
<my-popup>
|
||||
<ng-content />
|
||||
</my-popup>
|
||||
`,
|
||||
})
|
||||
class MyMenu {
|
||||
triggerText = input('');
|
||||
|
||||
@@ -15,16 +15,6 @@ Debugging in the default Node.js environment is often the quickest way to diagno
|
||||
|
||||
## Debugging in a browser
|
||||
|
||||
Debugging in a browser is recommended for tests that rely on the DOM or other browser-specific APIs. This approach allows you to use the browser's own developer tools.
|
||||
Debugging with Vitest and [browser mode](/guide/testing/migrating-to-vitest#5-configure-browser-mode-optional) is not supported today.
|
||||
|
||||
1. Ensure you have a browser provider installed. See [Running tests in a browser](guide/testing/overview#running-tests-in-a-browser) for setup instructions.
|
||||
2. Run the `ng test` command with both the `--browsers` and `--debug` flags:
|
||||
```shell
|
||||
ng test --browsers=chromium --debug
|
||||
```
|
||||
3. This command runs the tests in a headed browser and keeps it open after the tests finish, allowing you to inspect the output.
|
||||
4. Open the browser's **Developer Tools**. On Windows, press `Ctrl-Shift-I`. On macOS, press `Command-Option-I`.
|
||||
5. Go to the **Sources** tab.
|
||||
6. Use `Control/Command-P` to search for and open your test file.
|
||||
7. Set a breakpoint in your test.
|
||||
8. Reload the test runner UI in the browser. The execution will now stop at your breakpoint.
|
||||
<!-- Update this section when https://github.com/angular/angular-cli/issues/31652 is fixed>
|
||||
|
||||
@@ -132,6 +132,8 @@ Add the `browsers` option to your `test` target's options. The browser name depe
|
||||
|
||||
Headless mode is enabled automatically if the `CI` environment variable is set or if a browser name includes "Headless" (e.g., `ChromeHeadless`). Otherwise, tests will run in a headed browser.
|
||||
|
||||
NOTE: Debugging with `ng test --debug` is not supported by browser mode.
|
||||
|
||||
## Automated test refactoring with schematics
|
||||
|
||||
IMPORTANT: The `refactor-jasmine-vitest` schematic is experimental and may not cover all possible test patterns. Always review the changes made by the schematic.
|
||||
@@ -222,6 +224,11 @@ Provide a direct path to a Vitest configuration file in your `angular.json`:
|
||||
**2. Automatic search for base configuration:**
|
||||
If you set `runnerConfig` to `true`, the builder will automatically search for a shared `vitest-base.config.*` file in your project and workspace roots.
|
||||
|
||||
## `zone.js` based helpers are not supported
|
||||
|
||||
The zone.js patches are not applied when running tests with Vitest, there for you won't be able use functions like `fakeAsync`, `flush` or `waitForAsync`.
|
||||
To migrate to Vitest you will also need to migrate your tests to native async and Vitest fake timers. See [an example here](/components-scenarios#async-test-with-a-vitest-fake-timers) for fake timers usages with Vitest.
|
||||
|
||||
## Bug reports
|
||||
|
||||
Report issues and feature requests on [GitHub](https://github.com/angular/angular-cli/issues).
|
||||
|
||||
@@ -11,11 +11,38 @@ Most pipes have no dependence on Angular other than the `@Pipe` metadata and an
|
||||
Consider a `TitleCasePipe` that capitalizes the first letter of each word.
|
||||
Here's an implementation with a regular expression.
|
||||
|
||||
<docs-code header="title-case.pipe.ts" path="adev/src/content/examples/testing/src/app/shared/title-case.pipe.ts"/>
|
||||
```ts
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
|
||||
@Pipe({name: 'titlecase', pure: true})
|
||||
/** Transform to Title Case: uppercase the first letter of the words in a string. */
|
||||
export class TitleCasePipe implements PipeTransform {
|
||||
transform(input: string): string {
|
||||
return input.length === 0
|
||||
? ''
|
||||
: input.replace(/\w\S*/g, (txt) => txt[0].toUpperCase() + txt.slice(1).toLowerCase());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Anything that uses a regular expression is worth testing thoroughly. You can use standard unit testing techniques to explore the expected cases and the edge cases.
|
||||
|
||||
<docs-code header="title-case.pipe.spec.ts" path="adev/src/content/examples/testing/src/app/shared/title-case.pipe.spec.ts" region="excerpt"/>
|
||||
```ts
|
||||
describe('TitleCasePipe', () => {
|
||||
// This pipe is a pure, stateless function so no need for BeforeEach
|
||||
const pipe = new TitleCasePipe();
|
||||
|
||||
it('transforms "abc" to "Abc"', () => {
|
||||
expect(pipe.transform('abc')).toBe('Abc');
|
||||
});
|
||||
|
||||
it('transforms "abc def" to "Abc Def"', () => {
|
||||
expect(pipe.transform('abc def')).toBe('Abc Def');
|
||||
});
|
||||
|
||||
// ... more tests ...
|
||||
});
|
||||
```
|
||||
|
||||
## Writing DOM tests to support a pipe test
|
||||
|
||||
@@ -24,4 +51,22 @@ They can't tell if the `TitleCasePipe` is working properly as applied in the app
|
||||
|
||||
Consider adding component tests such as this one:
|
||||
|
||||
<docs-code header="hero-detail.component.spec.ts (pipe test)" path="adev/src/content/examples/testing/src/app/hero/hero-detail.component.spec.ts" region="title-case-pipe"/>
|
||||
```ts
|
||||
it('should convert hero name to Title Case', async () => {
|
||||
// get the name's input and display elements from the DOM
|
||||
const hostElement: HTMLElement = harness.routeNativeElement!;
|
||||
const nameInput: HTMLInputElement = hostElement.querySelector('input')!;
|
||||
const nameDisplay: HTMLElement = hostElement.querySelector('span')!;
|
||||
|
||||
// simulate user entering a new name into the input box
|
||||
nameInput.value = 'quick BROWN fOx';
|
||||
|
||||
// Dispatch a DOM event so that Angular learns of input value change.
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
|
||||
// Wait for Angular to update the display binding through the title pipe
|
||||
await harness.fixture.whenStable();
|
||||
|
||||
expect(nameDisplay.textContent).toBe('Quick Brown Fox');
|
||||
});
|
||||
```
|
||||
|
||||
@@ -1,13 +1,37 @@
|
||||
# Testing services
|
||||
|
||||
NOTE: While this guide is being updated for Vitest, some code examples currently use Karma/Jasmine syntax and APIs. We are actively working to provide Vitest equivalents where applicable.
|
||||
|
||||
To check that your services are working as you intend, you can write tests specifically for them.
|
||||
|
||||
Services are often the smoothest files to unit test.
|
||||
Here are some synchronous and asynchronous unit tests of the `ValueService` written without assistance from Angular testing utilities.
|
||||
|
||||
<docs-code header="demo.spec.ts" path="adev/src/content/examples/testing/src/app/demo/demo.spec.ts" region="ValueService"/>
|
||||
```ts
|
||||
describe('ValueService', () => {
|
||||
let service: ValueService;
|
||||
|
||||
beforeEach(() => {
|
||||
// Only works if the service doesn't rely on Angular inject()
|
||||
service = new ValueService();
|
||||
});
|
||||
|
||||
it('getValue should return real value', () => {
|
||||
expect(service.getValue()).toBe('real value');
|
||||
});
|
||||
|
||||
it('getObservableValue should return value from observable', async () => {
|
||||
const value = await new Promise<string>((resolve) => {
|
||||
service.getObservableValue().subscribe(resolve);
|
||||
});
|
||||
|
||||
expect(value).toBe('observable value');
|
||||
});
|
||||
|
||||
it('getPromiseValue should return value from a promise', async () => {
|
||||
const value = await service.getPromiseValue();
|
||||
expect(value).toBe('promise value');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Testing services with the `TestBed`
|
||||
|
||||
@@ -29,75 +53,65 @@ The `TestBed.configureTestingModule()` method takes a metadata object that can h
|
||||
|
||||
To test a service, you set the `providers` metadata property with an array of the services that you'll test or mock.
|
||||
|
||||
<docs-code header="demo.testbed.spec.ts (provide ValueService in beforeEach)" path="adev/src/content/examples/testing/src/app/demo/demo.testbed.spec.ts" region="value-service-before-each"/>
|
||||
```ts
|
||||
let service: ValueService;
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({providers: [ValueService]});
|
||||
});
|
||||
```
|
||||
|
||||
Then inject it inside a test by calling `TestBed.inject()` with the service class as the argument.
|
||||
|
||||
HELPFUL: `TestBed.get()` was deprecated as of Angular version 9.
|
||||
To help minimize breaking changes, Angular introduces a new function called `TestBed.inject()`, which you should use instead.
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/demo/demo.testbed.spec.ts" region="value-service-inject-it"/>
|
||||
```ts
|
||||
it('should use ValueService', () => {
|
||||
service = TestBed.inject(ValueService);
|
||||
expect(service.getValue()).toBe('real value');
|
||||
});
|
||||
```
|
||||
|
||||
Or inside the `beforeEach()` if you prefer to inject the service as part of your setup.
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/demo/demo.testbed.spec.ts" region="value-service-inject-before-each"> </docs-code>
|
||||
```ts
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({providers: [ValueService]});
|
||||
service = TestBed.inject(ValueService);
|
||||
});
|
||||
```
|
||||
|
||||
When testing a service with a dependency, provide the mock in the `providers` array.
|
||||
|
||||
In the following example, the mock is a spy object.
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/demo/demo.testbed.spec.ts" region="master-service-before-each"/>
|
||||
```ts
|
||||
let masterService: MainService;
|
||||
let valueServiceSpy: Mocked<ValueService>;
|
||||
|
||||
beforeEach(() => {
|
||||
const spy: Mocked<ValueService> = {getValue: vi.fn()};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [MainService, {provide: ValueService, useValue: spy}],
|
||||
});
|
||||
|
||||
masterService = TestBed.inject(MainService);
|
||||
valueServiceSpy = TestBed.inject(ValueService) as Mocked<ValueService>;
|
||||
});
|
||||
```
|
||||
|
||||
The test consumes that spy in the same way it did earlier.
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/demo/demo.testbed.spec.ts" region="master-service-it"/>
|
||||
```ts
|
||||
it('getValue should return stubbed value from a spy', () => {
|
||||
const stubValue = 'stub value';
|
||||
|
||||
## Testing without `beforeEach()`
|
||||
valueServiceSpy.getValue.mockReturnValue(stubValue);
|
||||
|
||||
Most test suites in this guide call `beforeEach()` to set the preconditions for each `it()` test and rely on the `TestBed` to create classes and inject services.
|
||||
|
||||
There's another school of testing that never calls `beforeEach()` and prefers to create classes explicitly rather than use the `TestBed`.
|
||||
|
||||
Here's how you might rewrite one of the `MasterService` tests in that style.
|
||||
|
||||
Begin by putting re-usable, preparatory code in a _setup_ function instead of `beforeEach()`.
|
||||
|
||||
<docs-code header="demo.spec.ts (setup)" path="adev/src/content/examples/testing/src/app/demo/demo.spec.ts" region="no-before-each-setup"/>
|
||||
|
||||
The `setup()` function returns an object literal with the variables, such as `masterService`, that a test might reference.
|
||||
You don't define _semi-global_ variables \(for example, `let masterService: MasterService`\) in the body of the `describe()`.
|
||||
|
||||
Then each test invokes `setup()` in its first line, before continuing with steps that manipulate the test subject and assert expectations.
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/demo/demo.spec.ts" region="no-before-each-test"/>
|
||||
|
||||
Notice how the test uses [_destructuring assignment_](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) to extract the setup variables that it needs.
|
||||
|
||||
<docs-code path="adev/src/content/examples/testing/src/app/demo/demo.spec.ts" region="no-before-each-setup-call"/>
|
||||
|
||||
Many developers feel this approach is cleaner and more explicit than the traditional `beforeEach()` style.
|
||||
|
||||
Although this testing guide follows the traditional style and the default [CLI schematics](https://github.com/angular/angular-cli) generate test files with `beforeEach()` and `TestBed`, feel free to adopt _this alternative approach_ in your own projects.
|
||||
expect(masterService.getValue(), 'service returned stub value').toBe(stubValue);
|
||||
expect(valueServiceSpy.getValue, 'spy method was called once').toHaveBeenCalledTimes(1);
|
||||
expect(valueServiceSpy.getValue.mock.results.at(-1)?.value).toBe(stubValue);
|
||||
});
|
||||
```
|
||||
|
||||
## Testing HTTP services
|
||||
|
||||
Data services that make HTTP calls to remote servers typically inject and delegate to the Angular [`HttpClient`](guide/http/testing) service for XHR calls.
|
||||
|
||||
You can test a data service with an injected `HttpClient` spy as you would test any service with a dependency.
|
||||
|
||||
<docs-code header="hero.service.spec.ts (tests with spies)" path="adev/src/content/examples/testing/src/app/model/hero.service.spec.ts" region="test-with-spies"/>
|
||||
|
||||
IMPORTANT: The `HeroService` methods return `Observables`.
|
||||
You must _subscribe_ to an observable to \(a\) cause it to execute and \(b\) assert that the method succeeds or fails.
|
||||
|
||||
The `subscribe()` method takes a success \(`next`\) and fail \(`error`\) callback.
|
||||
Make sure you provide _both_ callbacks so that you capture errors.
|
||||
Neglecting to do so produces an asynchronous uncaught observable error that the test runner will likely attribute to a completely different test.
|
||||
|
||||
## `HttpClientTestingModule`
|
||||
|
||||
Extended interactions between a data service and the `HttpClient` can be complex and difficult to mock with spies.
|
||||
|
||||
The `HttpClientTestingModule` can make these testing scenarios more manageable.
|
||||
|
||||
While the _code sample_ accompanying this guide demonstrates `HttpClientTestingModule`, this page defers to the [Http guide](guide/http/testing), which covers testing with the `HttpClientTestingModule` in detail.
|
||||
For testing services that rely on the `HttpClient`, refer to the [dedicated guide](/guide/http/testing).
|
||||
|
||||
@@ -9,11 +9,10 @@ The [`TestBed`](#testbed-class-summary) and [`ComponentFixture`](#the-componentf
|
||||
|
||||
Here's a summary of the stand-alone functions, in order of likely utility:
|
||||
|
||||
| Function | Details |
|
||||
| :--------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `inject` | Injects one or more services from the current `TestBed` injector into a test function. It cannot inject a service provided by the component itself. See discussion of the [debugElement.injector](guide/testing/components-scenarios#get-injected-services). |
|
||||
| `ComponentFixtureAutoDetect` | A provider token for a service that turns on [automatic change detection](guide/testing/components-scenarios#automatic-change-detection). |
|
||||
| `getTestBed` | Gets the current instance of the `TestBed`. Usually unnecessary because the static class methods of the `TestBed` class are typically sufficient. The `TestBed` instance exposes a few rarely used members that are not available as static methods. |
|
||||
| Function | Details |
|
||||
| :----------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`inject`] | Injects one or more services from the current `TestBed` injector into a test function. It cannot inject a service provided by the component itself. See discussion of the [debugElement.injector](guide/testing/components-scenarios#get-injected-services). |
|
||||
| `getTestBed` | Gets the current instance of the `TestBed`. Usually unnecessary because the static class methods of the `TestBed` class are typically sufficient. The `TestBed` instance exposes a few rarely used members that are not available as static methods. |
|
||||
|
||||
For handling complex asynchronous scenarios or testing legacy Zone.js-based applications, see the [Zone.js Testing Utilities](guide/testing/zone-js-testing-utilities) guide.
|
||||
|
||||
@@ -52,18 +51,18 @@ Call `TestBed` methods _within_ a `beforeEach()` to ensure a fresh start before
|
||||
|
||||
Here are the most important static methods, in order of likely utility.
|
||||
|
||||
| Methods | Details |
|
||||
| :----------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `configureTestingModule` | The testing shims establish the [initial test environment](guide/testing) and a default testing module. The default testing module is configured with basic declaratives and some Angular service substitutes that every tester needs. <br /> Call `configureTestingModule` to refine the testing module configuration for a particular set of tests by adding and removing imports, declarations \(of components, directives, and pipes\), and providers. |
|
||||
| `compileComponents` | Compile the testing module asynchronously after you've finished configuring it. You **must** call this method if _any_ of the testing module components have a `templateUrl` or `styleUrls` because fetching component template and style files is necessarily asynchronous. See [compileComponents](guide/testing/components-scenarios#calling-compilecomponents). <br /> After calling `compileComponents`, the `TestBed` configuration is frozen for the duration of the current spec. |
|
||||
| `createComponent<T>` | Create an instance of a component of type `T` based on the current `TestBed` configuration. After calling `createComponent`, the `TestBed` configuration is frozen for the duration of the current spec. |
|
||||
| `overrideModule` | Replace metadata for the given `NgModule`. Recall that modules can import other modules. The `overrideModule` method can reach deeply into the current testing module to modify one of these inner modules. |
|
||||
| `overrideComponent` | Replace metadata for the given component class, which could be nested deeply within an inner module. |
|
||||
| `overrideDirective` | Replace metadata for the given directive class, which could be nested deeply within an inner module. |
|
||||
| `overridePipe` | Replace metadata for the given pipe class, which could be nested deeply within an inner module. |
|
||||
| Methods | Details |
|
||||
| :----------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `configureTestingModule` | The testing shims establish the [initial test environment](guide/testing) and a default testing module. The default testing module is configured with basic declaratives and some Angular service substitutes that every tester needs. <br /> Call `configureTestingModule` to refine the testing module configuration for a particular set of tests by adding and removing imports, declarations \(of components, directives, and pipes\), and providers. |
|
||||
| `compileComponents` | Compile the testing module asynchronously after you've finished configuring it. You **must** call this method if _any_ of the testing module components have asynchronously loaded resources (like @defer blocks). <br /> After calling `compileComponents`, the `TestBed` configuration is frozen for the duration of the current spec. |
|
||||
| `createComponent<T>` | Create an instance of a component of type `T` based on the current `TestBed` configuration. After calling `createComponent`, the `TestBed` configuration is frozen for the duration of the current spec. |
|
||||
| `overrideComponent` | Replace metadata for the given component class, which could be nested deeply within an inner module. |
|
||||
| `overrideDirective` | Replace metadata for the given directive class, which could be nested deeply within an inner module. |
|
||||
| `overridePipe` | Replace metadata for the given pipe class, which could be nested deeply within an inner module. |
|
||||
| `overrideModule` | Replace metadata for the given `NgModule`. Recall that modules can import other modules. The `overrideModule` method can reach deeply into the current testing module to modify one of these inner modules. |
|
||||
|
||||
|
|
||||
`inject` | Retrieve a service from the current `TestBed` injector. The `inject` function is often adequate for this purpose. But `inject` throws an error if it can't provide the service. <br /> What if the service is optional? <br /> The `TestBed.inject()` method takes an optional second parameter, the object to return if Angular can't find the provider \(`null` in this example\): <docs-code header="demo.testbed.spec.ts" path="adev/src/content/examples/testing/src/app/demo/demo.testbed.spec.ts" region="testbed-get-w-null"/> After calling `TestBed.inject`, the `TestBed` configuration is frozen for the duration of the current spec. |
|
||||
`inject` | Retrieve a service from the current `TestBed` injector. The `inject` function is often adequate for this purpose. But `inject` throws an error if it can't provide the service. <br /> What if the service is optional? <br /> The `TestBed.inject()` method takes an optional second parameter, the object to return if Angular can't find the provider \(`null` in this example\): `expect(TestBed.inject(NotProvided, null)).toBeNull();` After calling `TestBed.inject`, the `TestBed` configuration is frozen for the duration of the current spec. |
|
||||
|
|
||||
`initTestEnvironment` | Initialize the testing environment for the entire test run. <br /> The testing shims call it for you so there is rarely a reason for you to call it yourself. <br /> Call this method _exactly once_. To change this default in the middle of a test run, call `resetTestEnvironment` first. <br /> Specify the Angular compiler factory, a `PlatformRef`, and a default Angular testing module. Alternatives for non-browser platforms are available in the general form `@angular/platform-<platform_name>/testing/<platform_name>`. |
|
||||
| `resetTestEnvironment` | Reset the initial test environment, including the default testing module. |
|
||||
@@ -134,7 +133,10 @@ The `DebugElement.query(predicate)` and `DebugElement.queryAll(predicate)` metho
|
||||
The predicate is any method that takes a `DebugElement` and returns a _truthy_ value.
|
||||
The following example finds all `DebugElements` with a reference to a template local variable named "content":
|
||||
|
||||
<docs-code header="demo.testbed.spec.ts" path="adev/src/content/examples/testing/src/app/demo/demo.testbed.spec.ts" region="custom-predicate"/>
|
||||
```ts
|
||||
// Filter for DebugElements with a #content reference
|
||||
const contentRefs = el.queryAll((de) => de.references['content']);
|
||||
```
|
||||
|
||||
The Angular `By` class has three static methods for common predicates:
|
||||
|
||||
@@ -144,4 +146,8 @@ The Angular `By` class has three static methods for common predicates:
|
||||
| `By.css(selector)` | Return elements with matching CSS selectors |
|
||||
| `By.directive(directive)` | Return elements that Angular matched to an instance of the directive class |
|
||||
|
||||
<docs-code header="hero-list.component.spec.ts" path="adev/src/content/examples/testing/src/app/hero/hero-list.component.spec.ts" region="by"/>
|
||||
```ts
|
||||
// Can find DebugElement either by css selector or by directive
|
||||
const h2 = fixture.debugElement.query(By.css('h2'));
|
||||
const directive = fixture.debugElement.query(By.directive(Highlight));
|
||||
```
|
||||
|
||||
@@ -24,7 +24,7 @@ function withFakeAsyncTestModule(fn: (fakeAsyncTestModule: any) => any): any {
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANT: This API requires Zone.js
|
||||
* IMPORTANT: This API requires Zone.js and cannot be used with the Vitest test runner
|
||||
*
|
||||
* Clears out the shared fake async zone for a test.
|
||||
* To be called in a global `beforeEach`.
|
||||
@@ -42,7 +42,7 @@ export function resetFakeAsyncZoneIfExists(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANT: This API requires Zone.js
|
||||
* IMPORTANT: This API requires Zone.js and cannot be used with the Vitest test runner
|
||||
*
|
||||
* Wraps a function to be executed in the `fakeAsync` zone:
|
||||
* - Microtasks are manually executed by calling `flushMicrotasks()`.
|
||||
@@ -72,7 +72,7 @@ export function fakeAsync(fn: Function, options?: {flush?: boolean}): (...args:
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANT: This API requires Zone.js
|
||||
* IMPORTANT: This API requires Zone.js and cannot be used with the Vitest test runner
|
||||
*
|
||||
* Simulates the asynchronous passage of time for the timers in the `fakeAsync` zone.
|
||||
*
|
||||
@@ -147,7 +147,7 @@ export function tick(
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANT: This API requires Zone.js
|
||||
* IMPORTANT: This API requires Zone.js and cannot be used with the Vitest test runner
|
||||
*
|
||||
* Flushes any pending microtasks and simulates the asynchronous passage of time for the timers in
|
||||
* the `fakeAsync` zone by
|
||||
@@ -164,7 +164,7 @@ export function flush(maxTurns?: number): number {
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANT: This API requires Zone.js
|
||||
* IMPORTANT: This API requires Zone.js and cannot be used with the Vitest test runner
|
||||
*
|
||||
* Discard all remaining periodic tasks.
|
||||
*
|
||||
@@ -175,7 +175,7 @@ export function discardPeriodicTasks(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANT: This API requires Zone.js
|
||||
* IMPORTANT: This API requires Zone.js and cannot be used with the Vitest test runner
|
||||
*
|
||||
* Flush any pending microtasks.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user