function
A helper function to use when unit testing AngularJS services that depend upon downgraded Angular services.
createAngularJSTestingModule(angularModules: any[]): string
angularModules | any[] | a collection of Angular modules to include in the configuration. |
string
This function returns an AngularJS module that is configured to wire up the AngularJS and Angular injectors without the need to actually bootstrap a hybrid application. This makes it simpler and faster to unit test services.
Use the returned AngularJS module in a call to angular.mocks.module
to include this module in the unit test injector.
In the following code snippet, we are configuring the $injector
with two modules: The AngularJS ng1AppModule
, which is the AngularJS part of our hybrid application and the Ng2AppModule
, which is the Angular part.
beforeEach(module(createAngularJSTestingModule([Ng2AppModule]))); beforeEach(module(ng1AppModule.name));
Once this is done we can get hold of services via the AngularJS $injector
as normal. Services that are (or have dependencies on) a downgraded Angular service, will be instantiated as needed by the Angular root Injector
.
In the following code snippet, heroesService
is a downgraded Angular service that we are accessing from AngularJS.
it('should have access to the HeroesService', inject((heroesService: HeroesService) => { expect(heroesService).toBeDefined(); }));
This helper is for testing services not components. For Component testing you must still bootstrap a hybrid app. See
UpgradeModule
ordowngradeModule
for more information.
The resulting configuration does not wire up AngularJS digests to Zone hooks. It is the responsibility of the test writer to call
$rootScope.$apply
, as necessary, to trigger AngularJS handlers of async events from Angular.
The helper sets up global variables to hold the shared Angular and AngularJS injectors.
- Only call this helper once per spec.
- Do not use
createAngularJSTestingModule
in the same spec ascreateAngularTestingModule
.
Here is the example application and its unit tests that use createAngularTestingModule
and createAngularJSTestingModule
.
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {TestBed} from '@angular/core/testing'; import {createAngularJSTestingModule, createAngularTestingModule} from '@angular/upgrade/static/testing'; import {HeroesService, ng1AppModule, Ng2AppModule} from './module'; const {module, inject} = (window as any).angular.mock; describe('HeroesService (from Angular)', () => { beforeEach(() => { TestBed.configureTestingModule( {imports: [createAngularTestingModule([ng1AppModule.name]), Ng2AppModule]}); }); it('should have access to the HeroesService', () => { const heroesService = TestBed.inject(HeroesService); expect(heroesService).toBeDefined(); }); }); describe('HeroesService (from AngularJS)', () => { beforeEach(module(createAngularJSTestingModule([Ng2AppModule]))); beforeEach(module(ng1AppModule.name)); it('should have access to the HeroesService', inject((heroesService: HeroesService) => { expect(heroesService).toBeDefined(); })); });
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {Component, Directive, ElementRef, EventEmitter, Injectable, Injector, Input, NgModule, Output} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {downgradeComponent, downgradeInjectable, UpgradeComponent, UpgradeModule} from '@angular/upgrade/static'; declare var angular: ng.IAngularStatic; export interface Hero { name: string; description: string; } export class TextFormatter { titleCase(value: string) { return value.replace(/((^|\s)[a-z])/g, (_, c) => c.toUpperCase()); } } // This Angular component will be "downgraded" to be used in AngularJS @Component({ selector: 'ng2-heroes', // This template uses the upgraded `ng1-hero` component // Note that because its element is compiled by Angular we must use camelCased attribute names template: `<header><ng-content selector="h1"></ng-content></header> <ng-content selector=".extra"></ng-content> <div *ngFor="let hero of heroes"> <ng1-hero [hero]="hero" (onRemove)="removeHero.emit(hero)"><strong>Super Hero</strong></ng1-hero> </div> <button (click)="addHero.emit()">Add Hero</button>`, }) export class Ng2HeroesComponent { @Input() heroes!: Hero[]; @Output() addHero = new EventEmitter(); @Output() removeHero = new EventEmitter(); } // This Angular service will be "downgraded" to be used in AngularJS @Injectable() export class HeroesService { heroes: Hero[] = [ {name: 'superman', description: 'The man of steel'}, {name: 'wonder woman', description: 'Princess of the Amazons'}, {name: 'thor', description: 'The hammer-wielding god'} ]; constructor(textFormatter: TextFormatter) { // Change all the hero names to title case, using the "upgraded" AngularJS service this.heroes.forEach((hero: Hero) => hero.name = textFormatter.titleCase(hero.name)); } addHero() { this.heroes = this.heroes.concat([{name: 'Kamala Khan', description: 'Epic shape-shifting healer'}]); } removeHero(hero: Hero) { this.heroes = this.heroes.filter((item: Hero) => item !== hero); } } // This Angular directive will act as an interface to the "upgraded" AngularJS component @Directive({selector: 'ng1-hero'}) export class Ng1HeroComponentWrapper extends UpgradeComponent { // The names of the input and output properties here must match the names of the // `<` and `&` bindings in the AngularJS component that is being wrapped @Input() hero!: Hero; @Output() onRemove!: EventEmitter<void>; constructor(elementRef: ElementRef, injector: Injector) { // We must pass the name of the directive as used by AngularJS to the super super('ng1Hero', elementRef, injector); } } // This NgModule represents the Angular pieces of the application @NgModule({ declarations: [Ng2HeroesComponent, Ng1HeroComponentWrapper], providers: [ HeroesService, // Register an Angular provider whose value is the "upgraded" AngularJS service {provide: TextFormatter, useFactory: (i: any) => i.get('textFormatter'), deps: ['$injector']} ], // All components that are to be "downgraded" must be declared as `entryComponents` entryComponents: [Ng2HeroesComponent], // We must import `UpgradeModule` to get access to the AngularJS core services imports: [BrowserModule, UpgradeModule] }) export class Ng2AppModule { constructor(private upgrade: UpgradeModule) {} ngDoBootstrap() { // We bootstrap the AngularJS app. this.upgrade.bootstrap(document.body, [ng1AppModule.name]); } } // This Angular 1 module represents the AngularJS pieces of the application export const ng1AppModule: ng.IModule = angular.module('ng1AppModule', []); // This AngularJS component will be "upgraded" to be used in Angular ng1AppModule.component('ng1Hero', { bindings: {hero: '<', onRemove: '&'}, transclude: true, template: `<div class="title" ng-transclude></div> <h2>{{ $ctrl.hero.name }}</h2> <p>{{ $ctrl.hero.description }}</p> <button ng-click="$ctrl.onRemove()">Remove</button>` }); // This AngularJS service will be "upgraded" to be used in Angular ng1AppModule.service('textFormatter', [TextFormatter]); // Register an AngularJS service, whose value is the "downgraded" Angular injectable. ng1AppModule.factory('heroesService', downgradeInjectable(HeroesService) as any); // This directive will act as the interface to the "downgraded" Angular component ng1AppModule.directive('ng2Heroes', downgradeComponent({component: Ng2HeroesComponent})); // This is our top level application component ng1AppModule.component('exampleApp', { // We inject the "downgraded" HeroesService into this AngularJS component // (We don't need the `HeroesService` type for AngularJS DI - it just helps with TypeScript // compilation) controller: [ 'heroesService', function(heroesService: HeroesService) { this.heroesService = heroesService; } ], // This template makes use of the downgraded `ng2-heroes` component // Note that because its element is compiled by AngularJS we must use kebab-case attributes // for inputs and outputs template: `<link rel="stylesheet" href="./styles.css"> <ng2-heroes [heroes]="$ctrl.heroesService.heroes" (add-hero)="$ctrl.heroesService.addHero()" (remove-hero)="$ctrl.heroesService.removeHero($event)"> <h1>Heroes</h1> <p class="extra">There are {{ $ctrl.heroesService.heroes.length }} heroes.</p> </ng2-heroes>` }); // We bootstrap the Angular module as we would do in a normal Angular app. // (We are using the dynamic browser platform as this example has not been compiled AOT.) platformBrowserDynamic().bootstrapModule(Ng2AppModule);
© 2010–2020 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://v10.angular.io/api/upgrade/static/testing/createAngularJSTestingModule