From 3fb78aaaccf32b72a7b9eff55f144fb39e74474f Mon Sep 17 00:00:00 2001
From: Pete Bacon Darwin <pete@bacondarwin.com>
Date: Fri, 22 Mar 2019 09:42:52 +0000
Subject: [PATCH] feat(upgrade): provide unit test helpers for wiring up
 injectors (#16848)

Adds two new helper functions that can be used when unit testing Angular services
that depend upon upgraded AngularJS services, or vice versa.
The functions return a module (AngularJS or NgModule) that is configured to wire up
the Angular and AngularJS injectors without the need to actually bootstrap a full
hybrid application.

This makes it simpler and faster to unit test services.

PR Close #16848
---
 .../transforms/angular-api-package/index.js   |   1 +
 .../transforms/authors-package/api-package.js |   2 +-
 integration/typings_test_ts34/include-all.ts  |   4 +-
 .../upgrade/static/ts/full/module.spec.ts     |  46 ++++++++
 .../examples/upgrade/static/ts/full/module.ts |  14 +--
 packages/examples/upgrade/upgrade_example.bzl |   3 +
 packages/upgrade/BUILD.bazel                  |   2 +
 packages/upgrade/src/common/src/angular1.ts   |   5 +
 packages/upgrade/static/testing/BUILD.bazel   |  19 ++++
 packages/upgrade/static/testing/index.ts      |   9 ++
 packages/upgrade/static/testing/package.json  |  11 ++
 packages/upgrade/static/testing/public_api.ts |  10 ++
 .../src/create_angular_testing_module.ts      |  99 +++++++++++++++++
 .../src/create_angularjs_testing_module.ts    | 100 ++++++++++++++++++
 .../upgrade/static/testing/test/BUILD.bazel   |  29 +++++
 .../create_angular_testing_module_spec.ts     |  48 +++++++++
 .../create_angularjs_testing_module_spec.ts   |  33 ++++++
 packages/upgrade/static/testing/test/mocks.ts |  76 +++++++++++++
 test-main.js                                  |   1 +
 .../upgrade/static/testing.d.ts               |   3 +
 20 files changed, 506 insertions(+), 9 deletions(-)
 create mode 100644 packages/examples/upgrade/static/ts/full/module.spec.ts
 create mode 100644 packages/upgrade/static/testing/BUILD.bazel
 create mode 100755 packages/upgrade/static/testing/index.ts
 create mode 100644 packages/upgrade/static/testing/package.json
 create mode 100755 packages/upgrade/static/testing/public_api.ts
 create mode 100644 packages/upgrade/static/testing/src/create_angular_testing_module.ts
 create mode 100644 packages/upgrade/static/testing/src/create_angularjs_testing_module.ts
 create mode 100644 packages/upgrade/static/testing/test/BUILD.bazel
 create mode 100644 packages/upgrade/static/testing/test/create_angular_testing_module_spec.ts
 create mode 100644 packages/upgrade/static/testing/test/create_angularjs_testing_module_spec.ts
 create mode 100644 packages/upgrade/static/testing/test/mocks.ts
 create mode 100644 tools/public_api_guard/upgrade/static/testing.d.ts

diff --git a/aio/tools/transforms/angular-api-package/index.js b/aio/tools/transforms/angular-api-package/index.js
index 55baa84af0..93cbbf37c7 100644
--- a/aio/tools/transforms/angular-api-package/index.js
+++ b/aio/tools/transforms/angular-api-package/index.js
@@ -115,6 +115,7 @@ module.exports =
             'service-worker/index.ts',
             'upgrade/index.ts',
             'upgrade/static/index.ts',
+            'upgrade/static/testing/index.ts',
           ];
 
           readFilesProcessor.fileReaders.push(packageContentFileReader);
diff --git a/aio/tools/transforms/authors-package/api-package.js b/aio/tools/transforms/authors-package/api-package.js
index aff578cf67..92b85ab2ed 100644
--- a/aio/tools/transforms/authors-package/api-package.js
+++ b/aio/tools/transforms/authors-package/api-package.js
@@ -24,7 +24,7 @@ const packageMap = {
   'platform-webworker-dynamic': ['platform-webworker-dynamic/index.ts'],
   router: ['router/index.ts', 'router/testing/index.ts', 'router/upgrade/index.ts'],
   'service-worker': ['service-worker/index.ts'],
-  upgrade: ['upgrade/index.ts', 'upgrade/static/index.ts']
+  upgrade: ['upgrade/index.ts', 'upgrade/static/index.ts', 'upgrade/static/testing/index.ts']
 };
 
 
diff --git a/integration/typings_test_ts34/include-all.ts b/integration/typings_test_ts34/include-all.ts
index 66a5e0ffc3..116ba30903 100644
--- a/integration/typings_test_ts34/include-all.ts
+++ b/integration/typings_test_ts34/include-all.ts
@@ -39,6 +39,7 @@ import * as routerUpgrade from '@angular/router/upgrade';
 import * as serviceWorker from '@angular/service-worker';
 import * as upgrade from '@angular/upgrade';
 import * as upgradeStatic from '@angular/upgrade/static';
+import * as upgradeTesting from '@angular/upgrade/static/testing';
 
 export default {
   animations,
@@ -71,5 +72,6 @@ export default {
   routerUpgrade,
   serviceWorker,
   upgrade,
-  upgradeStatic
+  upgradeStatic,
+  upgradeTesting,
 };
diff --git a/packages/examples/upgrade/static/ts/full/module.spec.ts b/packages/examples/upgrade/static/ts/full/module.spec.ts
new file mode 100644
index 0000000000..d9067534d1
--- /dev/null
+++ b/packages/examples/upgrade/static/ts/full/module.spec.ts
@@ -0,0 +1,46 @@
+/**
+ * @license
+ * Copyright Google Inc. 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
+ */
+
+// #docregion angular-setup
+import {TestBed} from '@angular/core/testing';
+import {createAngularJSTestingModule, createAngularTestingModule} from '@angular/upgrade/static/testing';
+
+import {HeroesService, Ng2AppModule, ng1AppModule} from './module';
+
+const {module, inject} = (window as any).angular.mock;
+
+// #enddocregion angular-setup
+describe('HeroesService (from Angular)', () => {
+
+  // #docregion angular-setup
+  beforeEach(() => {
+    TestBed.configureTestingModule(
+        {imports: [createAngularTestingModule([ng1AppModule.name]), Ng2AppModule]});
+  });
+  // #enddocregion angular-setup
+
+  // #docregion angular-spec
+  it('should have access to the HeroesService', () => {
+    const heroesService = TestBed.get(HeroesService) as HeroesService;
+    expect(heroesService).toBeDefined();
+  });
+  // #enddocregion angular-spec
+});
+
+
+describe('HeroesService (from AngularJS)', () => {
+  // #docregion angularjs-setup
+  beforeEach(module(createAngularJSTestingModule([Ng2AppModule])));
+  beforeEach(module(ng1AppModule.name));
+  // #enddocregion angularjs-setup
+
+  // #docregion angularjs-spec
+  it('should have access to the HeroesService',
+     inject((heroesService: HeroesService) => { expect(heroesService).toBeDefined(); }));
+  // #enddocregion angularjs-spec
+});
diff --git a/packages/examples/upgrade/static/ts/full/module.ts b/packages/examples/upgrade/static/ts/full/module.ts
index 1e7c155dbf..217de1a4d6 100644
--- a/packages/examples/upgrade/static/ts/full/module.ts
+++ b/packages/examples/upgrade/static/ts/full/module.ts
@@ -13,13 +13,13 @@ import {UpgradeComponent, UpgradeModule, downgradeComponent, downgradeInjectable
 
 declare var angular: ng.IAngularStatic;
 
-interface Hero {
+export interface Hero {
   name: string;
   description: string;
 }
 
 // #docregion ng1-text-formatter-service
-class TextFormatter {
+export class TextFormatter {
   titleCase(value: string) { return value.replace(/((^|\s)[a-z])/g, (_, c) => c.toUpperCase()); }
 }
 
@@ -38,7 +38,7 @@ class TextFormatter {
              </div>
              <button (click)="addHero.emit()">Add Hero</button>`,
 })
-class Ng2HeroesComponent {
+export class Ng2HeroesComponent {
   @Input() heroes !: Hero[];
   @Output() addHero = new EventEmitter();
   @Output() removeHero = new EventEmitter();
@@ -48,7 +48,7 @@ class Ng2HeroesComponent {
 // #docregion ng2-heroes-service
 // This Angular service will be "downgraded" to be used in AngularJS
 @Injectable()
-class HeroesService {
+export class HeroesService {
   heroes: Hero[] = [
     {name: 'superman', description: 'The man of steel'},
     {name: 'wonder woman', description: 'Princess of the Amazons'},
@@ -74,7 +74,7 @@ class HeroesService {
 // #docregion ng1-hero-wrapper
 // This Angular directive will act as an interface to the "upgraded" AngularJS component
 @Directive({selector: 'ng1-hero'})
-class Ng1HeroComponentWrapper extends UpgradeComponent {
+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;
@@ -104,7 +104,7 @@ class Ng1HeroComponentWrapper extends UpgradeComponent {
   imports: [BrowserModule, UpgradeModule]
 })
 // #docregion bootstrap-ng1
-class Ng2AppModule {
+export class Ng2AppModule {
   // #enddocregion ng2-module
   constructor(private upgrade: UpgradeModule) {}
 
@@ -122,7 +122,7 @@ class Ng2AppModule {
 // #docregion Angular 1 Stuff
 // #docregion ng1-module
 // This Angular 1 module represents the AngularJS pieces of the application
-const ng1AppModule = angular.module('ng1AppModule', []);
+export const ng1AppModule: ng.IModule = angular.module('ng1AppModule', []);
 // #enddocregion
 
 // #docregion ng1-hero
diff --git a/packages/examples/upgrade/upgrade_example.bzl b/packages/examples/upgrade/upgrade_example.bzl
index ad1b2c2151..7391c9fff1 100644
--- a/packages/examples/upgrade/upgrade_example.bzl
+++ b/packages/examples/upgrade/upgrade_example.bzl
@@ -17,10 +17,13 @@ def create_upgrade_example_targets(name, srcs, e2e_srcs, entry_module, assets =
         type_check = False,
         deps = [
             "@npm//@types/angular",
+            "@npm//@types/jasmine",
             "//packages/core",
             "//packages/platform-browser",
             "//packages/platform-browser-dynamic",
             "//packages/upgrade/static",
+            "//packages/core/testing",
+            "//packages/upgrade/static/testing",
         ],
         tsconfig = "//packages/examples/upgrade:tsconfig-build.json",
     )
diff --git a/packages/upgrade/BUILD.bazel b/packages/upgrade/BUILD.bazel
index 0af8a7bb7e..7e680f40f8 100644
--- a/packages/upgrade/BUILD.bazel
+++ b/packages/upgrade/BUILD.bazel
@@ -23,6 +23,7 @@ ng_package(
     srcs = [
         "package.json",
         "//packages/upgrade/static:package.json",
+        "//packages/upgrade/static/testing:package.json",
     ],
     entry_point = ":index.ts",
     tags = [
@@ -34,5 +35,6 @@ ng_package(
     deps = [
         ":upgrade",
         "//packages/upgrade/static",
+        "//packages/upgrade/static/testing",
     ],
 )
diff --git a/packages/upgrade/src/common/src/angular1.ts b/packages/upgrade/src/common/src/angular1.ts
index f4adaad23c..78832354f0 100644
--- a/packages/upgrade/src/common/src/angular1.ts
+++ b/packages/upgrade/src/common/src/angular1.ts
@@ -234,6 +234,7 @@ let angular: {
     (e: string | Element | Document | IAugmentedJQuery): IAugmentedJQuery;
     cleanData: (nodes: Node[] | NodeList) => void;
   },
+  injector: (modules: Array<string|IInjectable>, strictDi?: boolean) => IInjectorService,
   version: {major: number},
   resumeBootstrap: () => void,
   getTestability: (e: Element) => ITestabilityService
@@ -241,6 +242,7 @@ let angular: {
   bootstrap: noNg,
   module: noNg,
   element: noNgElement,
+  injector: noNg,
   version: undefined as any,
   resumeBootstrap: noNg,
   getTestability: noNg
@@ -304,6 +306,9 @@ export const module_: typeof angular.module = (prefix, dependencies?) =>
 export const element: typeof angular.element = (e => angular.element(e)) as typeof angular.element;
 element.cleanData = nodes => angular.element.cleanData(nodes);
 
+export const injector: typeof angular.injector =
+    (modules: Array<string|IInjectable>, strictDi?: boolean) => angular.injector(modules, strictDi);
+
 export const resumeBootstrap: typeof angular.resumeBootstrap = () => angular.resumeBootstrap();
 
 export const getTestability: typeof angular.getTestability = e => angular.getTestability(e);
diff --git a/packages/upgrade/static/testing/BUILD.bazel b/packages/upgrade/static/testing/BUILD.bazel
new file mode 100644
index 0000000000..9cece1502f
--- /dev/null
+++ b/packages/upgrade/static/testing/BUILD.bazel
@@ -0,0 +1,19 @@
+load("//tools:defaults.bzl", "ng_module")
+
+package(default_visibility = ["//visibility:public"])
+
+exports_files(["package.json"])
+
+ng_module(
+    name = "testing",
+    srcs = glob(
+        [
+            "*.ts",
+            "src/*.ts",
+        ],
+    ),
+    deps = [
+        "//packages/core/testing",
+        "//packages/upgrade/src/common",
+    ],
+)
diff --git a/packages/upgrade/static/testing/index.ts b/packages/upgrade/static/testing/index.ts
new file mode 100755
index 0000000000..f93e7c31d5
--- /dev/null
+++ b/packages/upgrade/static/testing/index.ts
@@ -0,0 +1,9 @@
+/**
+ * @license
+ * Copyright Google Inc. 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
+ */
+
+export * from './public_api';
diff --git a/packages/upgrade/static/testing/package.json b/packages/upgrade/static/testing/package.json
new file mode 100644
index 0000000000..eecbcda470
--- /dev/null
+++ b/packages/upgrade/static/testing/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "@angular/upgrade/static/testing",
+  "main": "../../bundles/upgrade-static-testing.umd.js",
+  "module": "../../fesm5/static/testing.js",
+  "es2015": "../../fesm2015/static/testing.js",
+  "esm5": "../../esm5/static/testing/testing.js",
+  "esm2015": "../../esm2015/static/testing/testing.js",
+  "fesm5": "../../fesm5/static/testing.js",
+  "fesm2015": "../../fesm2015/static/testing.js",
+  "typings": "./testing.d.ts"
+}
diff --git a/packages/upgrade/static/testing/public_api.ts b/packages/upgrade/static/testing/public_api.ts
new file mode 100755
index 0000000000..871ad430b1
--- /dev/null
+++ b/packages/upgrade/static/testing/public_api.ts
@@ -0,0 +1,10 @@
+/**
+ * @license
+ * Copyright Google Inc. 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
+ */
+
+export {createAngularTestingModule} from './src/create_angular_testing_module';
+export {createAngularJSTestingModule} from './src/create_angularjs_testing_module';
\ No newline at end of file
diff --git a/packages/upgrade/static/testing/src/create_angular_testing_module.ts b/packages/upgrade/static/testing/src/create_angular_testing_module.ts
new file mode 100644
index 0000000000..bbaa2f5c8f
--- /dev/null
+++ b/packages/upgrade/static/testing/src/create_angular_testing_module.ts
@@ -0,0 +1,99 @@
+/**
+ * @license
+ * Copyright Google Inc. 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 {Injector, NgModule, Type} from '@angular/core';
+
+import * as angular from '../../../src/common/src/angular1';
+import {$INJECTOR, INJECTOR_KEY, UPGRADE_APP_TYPE_KEY} from '../../../src/common/src/constants';
+import {UpgradeAppType} from '../../../src/common/src/util';
+
+export let $injector: angular.IInjectorService|null = null;
+let injector: Injector;
+
+export function $injectorFactory() {
+  return $injector;
+}
+
+@NgModule({providers: [{provide: $INJECTOR, useFactory: $injectorFactory}]})
+export class AngularTestingModule {
+  constructor(i: Injector) { injector = i; }
+}
+
+/**
+ * A helper function to use when unit testing Angular services that depend upon upgraded AngularJS
+ * services.
+ *
+ * This function returns an `NgModule` decorated class that is configured to wire up the Angular
+ * and AngularJS injectors without the need to actually bootstrap a hybrid application.
+ * This makes it simpler and faster to unit test services.
+ *
+ * Use the returned class as an "import" when configuring the `TestBed`.
+ *
+ * In the following code snippet, we are configuring the TestBed with two imports.
+ * The `Ng2AppModule` is the Angular part of our hybrid application and the `ng1AppModule` is the
+ * AngularJS part.
+ *
+ * <code-example path="upgrade/static/ts/full/module.spec.ts" region="angular-setup"></code-example>
+ *
+ * Once this is done we can get hold of services via the Angular `Injector` as normal.
+ * Services that are (or have dependencies on) an upgraded AngularJS service, will be instantiated
+ * as needed by the AngularJS `$injector`.
+ *
+ * In the following code snippet, `HeroesService` is an Angular service that depends upon an
+ * AngularJS service, `titleCase`.
+ *
+ * <code-example path="upgrade/static/ts/full/module.spec.ts" region="angular-spec"></code-example>
+ *
+ * <div class="alert is-important">
+ *
+ * This helper is for testing services not Components.
+ * For Component testing you must still bootstrap a hybrid app. See `UpgradeModule` or
+ * `downgradeModule` for more information.
+ *
+ * </div>
+ *
+ * <div class="alert is-important">
+ *
+ * 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.
+ *
+ * </div>
+ *
+ * <div class="alert is-important">
+ *
+ * The helper sets up global variables to hold the shared Angular and AngularJS injectors.
+ *
+ * * Only call this helper once per spec.
+ * * Do not use `createAngularTestingModule` in the same spec as `createAngularJSTestingModule`.
+ *
+ * </div>
+ *
+ * Here is the example application and its unit tests that use `createAngularTestingModule`
+ * and `createAngularJSTestingModule`.
+ *
+ * <code-tabs>
+ *  <code-pane header="module.spec.ts" path="upgrade/static/ts/full/module.spec.ts"></code-pane>
+ *  <code-pane header="module.ts" path="upgrade/static/ts/full/module.ts"></code-pane>
+ * </code-tabs>
+ *
+ *
+ * @param angularJSModules a collection of the names of AngularJS modules to include in the
+ * configuration.
+ * @param [strictDi] whether the AngularJS injector should have `strictDI` enabled.
+ *
+ * @publicApi
+ */
+export function createAngularTestingModule(
+    angularJSModules: string[], strictDi?: boolean): Type<any> {
+  angular.module_('$$angularJSTestingModule', angularJSModules)
+      .constant(UPGRADE_APP_TYPE_KEY, UpgradeAppType.Static)
+      .factory(INJECTOR_KEY, () => injector);
+  $injector = angular.injector(['ng', '$$angularJSTestingModule'], strictDi);
+  return AngularTestingModule;
+}
diff --git a/packages/upgrade/static/testing/src/create_angularjs_testing_module.ts b/packages/upgrade/static/testing/src/create_angularjs_testing_module.ts
new file mode 100644
index 0000000000..85a6d9adcf
--- /dev/null
+++ b/packages/upgrade/static/testing/src/create_angularjs_testing_module.ts
@@ -0,0 +1,100 @@
+/**
+ * @license
+ * Copyright Google Inc. 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 {Injector} from '@angular/core';
+import {TestBed} from '@angular/core/testing';
+
+import * as ng from '../../../src/common/src/angular1';
+import {$INJECTOR, INJECTOR_KEY, UPGRADE_APP_TYPE_KEY} from '../../../src/common/src/constants';
+import {UpgradeAppType} from '../../../src/common/src/util';
+
+
+/**
+ * A helper function to use when unit testing AngularJS services that depend upon downgraded Angular
+ * services.
+ *
+ * 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`](https://docs.angularjs.org/api/ngMock/function/angular.mock.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.
+ *
+ * <code-example path="upgrade/static/ts/full/module.spec.ts"
+ * region="angularjs-setup"></code-example>
+ *
+ * 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.
+ *
+ * <code-example path="upgrade/static/ts/full/module.spec.ts"
+ * region="angularjs-spec"></code-example>
+ *
+ * <div class="alert is-important">
+ *
+ * This helper is for testing services not components.
+ * For Component testing you must still bootstrap a hybrid app. See `UpgradeModule` or
+ * `downgradeModule` for more information.
+ *
+ * </div>
+ *
+ * <div class="alert is-important">
+ *
+ * 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.
+ *
+ * </div>
+ *
+ * <div class="alert is-important">
+ *
+ * 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 as `createAngularTestingModule`.
+ *
+ * </div>
+ *
+ * Here is the example application and its unit tests that use `createAngularTestingModule`
+ * and `createAngularJSTestingModule`.
+ *
+ * <code-tabs>
+ *  <code-pane header="module.spec.ts" path="upgrade/static/ts/full/module.spec.ts"></code-pane>
+ *  <code-pane header="module.ts" path="upgrade/static/ts/full/module.ts"></code-pane>
+ * </code-tabs>
+ *
+ *
+ * @param angularModules a collection of Angular modules to include in the configuration.
+ *
+ * @publicApi
+ */
+export function createAngularJSTestingModule(angularModules: any[]): string {
+  return ng.module_('$$angularJSTestingModule', [])
+      .constant(UPGRADE_APP_TYPE_KEY, UpgradeAppType.Static)
+      .factory(
+          INJECTOR_KEY,
+          [
+            $INJECTOR,
+            ($injector: ng.IInjectorService) => {
+              TestBed.configureTestingModule({
+                imports: angularModules,
+                providers: [{provide: $INJECTOR, useValue: $injector}]
+              });
+              return TestBed.get(Injector);
+            }
+          ])
+      .name;
+}
diff --git a/packages/upgrade/static/testing/test/BUILD.bazel b/packages/upgrade/static/testing/test/BUILD.bazel
new file mode 100644
index 0000000000..96aa6c731a
--- /dev/null
+++ b/packages/upgrade/static/testing/test/BUILD.bazel
@@ -0,0 +1,29 @@
+package(default_visibility = ["//visibility:public"])
+
+load("//tools:defaults.bzl", "ts_library", "ts_web_test_suite")
+
+ts_library(
+    name = "test_lib",
+    testonly = True,
+    srcs = glob([
+        "**/*.ts",
+    ]),
+    deps = [
+        "//packages/core",
+        "//packages/core/testing",
+        "//packages/upgrade/src/common",
+        "//packages/upgrade/src/common/test/helpers",
+        "//packages/upgrade/static",
+        "//packages/upgrade/static/testing",
+    ],
+)
+
+ts_web_test_suite(
+    name = "test",
+    static_files = [
+        "//:angularjs_scripts",
+    ],
+    deps = [
+        ":test_lib",
+    ],
+)
diff --git a/packages/upgrade/static/testing/test/create_angular_testing_module_spec.ts b/packages/upgrade/static/testing/test/create_angular_testing_module_spec.ts
new file mode 100644
index 0000000000..13d3629434
--- /dev/null
+++ b/packages/upgrade/static/testing/test/create_angular_testing_module_spec.ts
@@ -0,0 +1,48 @@
+/**
+ * @license
+ * Copyright Google Inc. 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 {Injector} from '@angular/core';
+import {TestBed} from '@angular/core/testing';
+
+import {$INJECTOR} from '../../../src/common/src/constants';
+import {withEachNg1Version} from '../../../src/common/test/helpers/common_test_helpers';
+import {createAngularTestingModule} from '../src/create_angular_testing_module';
+
+import {AppModule, Inventory, defineAppModule, serverRequestInstance} from './mocks';
+
+withEachNg1Version(() => {
+  describe('Angular entry point', () => {
+    it('should allow us to get an upgraded AngularJS service from an Angular service', () => {
+      defineAppModule();
+      // Configure an NgModule that has the Angular and AngularJS injectors wired up
+      TestBed.configureTestingModule({imports: [createAngularTestingModule(['app']), AppModule]});
+      const inventory = TestBed.get(Inventory) as Inventory;
+      expect(inventory.serverRequest).toBe(serverRequestInstance);
+    });
+
+    it('should create new injectors when we re-use the helper', () => {
+      defineAppModule();
+      TestBed.configureTestingModule({imports: [createAngularTestingModule(['app']), AppModule]});
+      // Check that the injectors are wired up correctly
+      TestBed.get(Inventory) as Inventory;
+
+      // Grab references to the current injectors
+      const injector = TestBed.get(Injector);
+      const $injector = TestBed.get($INJECTOR);
+
+      TestBed.resetTestingModule();
+      TestBed.configureTestingModule({imports: [createAngularTestingModule(['app']), AppModule]});
+      // Check that the injectors are wired up correctly
+      TestBed.get(Inventory) as Inventory;
+
+      // Check that the new injectors are different to the previous ones.
+      expect(TestBed.get(Injector)).not.toBe(injector);
+      expect(TestBed.get($INJECTOR)).not.toBe($injector);
+    });
+  });
+});
diff --git a/packages/upgrade/static/testing/test/create_angularjs_testing_module_spec.ts b/packages/upgrade/static/testing/test/create_angularjs_testing_module_spec.ts
new file mode 100644
index 0000000000..0e55b9e1db
--- /dev/null
+++ b/packages/upgrade/static/testing/test/create_angularjs_testing_module_spec.ts
@@ -0,0 +1,33 @@
+/**
+ * @license
+ * Copyright Google Inc. 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 {getAngularJSGlobal} from '../../../src/common/src/angular1';
+import {withEachNg1Version} from '../../../src/common/test/helpers/common_test_helpers';
+import {createAngularJSTestingModule} from '../src/create_angularjs_testing_module';
+
+import {AppModule, Inventory, defineAppModule} from './mocks';
+
+
+withEachNg1Version(() => {
+  describe('AngularJS entry point', () => {
+    it('should allow us to get a downgraded Angular service from an AngularJS service', () => {
+      defineAppModule();
+      // We have to get the `mock` object from the global `angular` variable, rather than trying to
+      // import it from `@angular/upgrade/src/common/angular1`, because that file doesn't export
+      // `ngMock` helpers.
+      const {inject, module} = getAngularJSGlobal().mock;
+      // Load the AngularJS bits of the application
+      module('app');
+      // Configure an AngularJS module that has the AngularJS and Angular injector wired up
+      module(createAngularJSTestingModule([AppModule]));
+      let inventory: any = undefined;
+      inject(function(shoppingCart: any) { inventory = shoppingCart.inventory; });
+      expect(inventory).toEqual(jasmine.any(Inventory));
+    });
+  });
+});
\ No newline at end of file
diff --git a/packages/upgrade/static/testing/test/mocks.ts b/packages/upgrade/static/testing/test/mocks.ts
new file mode 100644
index 0000000000..f563a84ab4
--- /dev/null
+++ b/packages/upgrade/static/testing/test/mocks.ts
@@ -0,0 +1,76 @@
+/**
+ * @license
+ * Copyright Google Inc. 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 {Inject, Injectable, NgModule} from '@angular/core';
+import {downgradeInjectable} from '@angular/upgrade/static';
+import * as ng from '../../../src/common/src/angular1';
+/*
+ * This mock application code contains the following services and their dependencies:
+ *
+ * shoppingCart (AngularJS)
+ *   -> Inventory (Angular - downgraded)
+ *      -> serverRequest (AngularJS - upgraded)
+ *         -> Logger (Angular - downgraded)
+ *
+ * This allows us to test two scenarios:
+ *  * AngularJS -> Angular -> AngularJS
+ *  * Angular -> AngularJS -> Angular
+ */
+
+/* START: Angular bits */
+@Injectable()
+export class Logger {
+  warn() {}
+}
+
+@Injectable()
+export class Inventory {
+  constructor(@Inject('serverRequest') public serverRequest: any) {}
+}
+
+export function serverRequestFactory(i: ng.IInjectorService) {
+  return i.get('serverRequest');
+}
+
+@NgModule({
+  providers: [
+    Logger,
+    Inventory,
+    {provide: 'serverRequest', useFactory: serverRequestFactory, deps: ['$injector']},
+  ]
+})
+export class AppModule {
+}
+/* END: Angular bits */
+
+/* START: AngularJS bits */
+export const serverRequestInstance: {logger?: Logger} = {};
+export const shoppingCartInstance: {inventory?: Inventory} = {};
+
+export function defineAppModule() {
+  ng.module_('app', [])
+      .factory('logger', downgradeInjectable(Logger))
+      .factory('inventory', downgradeInjectable(Inventory))
+      .factory(
+          'serverRequest',
+          [
+            'logger',
+            function(logger: Logger) {
+              serverRequestInstance.logger = logger;
+              return serverRequestInstance;
+            }
+          ])
+      .factory('shoppingCart', [
+        'inventory',
+        function(inventory: Inventory) {
+          shoppingCartInstance.inventory = inventory;
+          return shoppingCartInstance;
+        }
+      ]);
+}
+/* END: AngularJS bits */
diff --git a/test-main.js b/test-main.js
index 59e8c28c02..6e8f44e400 100644
--- a/test-main.js
+++ b/test-main.js
@@ -51,6 +51,7 @@ System.config({
     '@angular/router': {main: 'index.js', defaultExtension: 'js'},
     '@angular/http/testing': {main: 'index.js', defaultExtension: 'js'},
     '@angular/http': {main: 'index.js', defaultExtension: 'js'},
+    '@angular/upgrade/static/testing': {main: 'index.js', defaultExtension: 'js'},
     '@angular/upgrade/static': {main: 'index.js', defaultExtension: 'js'},
     '@angular/upgrade': {main: 'index.js', defaultExtension: 'js'},
     '@angular/platform-browser/animations/testing': {main: 'index.js', defaultExtension: 'js'},
diff --git a/tools/public_api_guard/upgrade/static/testing.d.ts b/tools/public_api_guard/upgrade/static/testing.d.ts
new file mode 100644
index 0000000000..f905c680b6
--- /dev/null
+++ b/tools/public_api_guard/upgrade/static/testing.d.ts
@@ -0,0 +1,3 @@
+export declare function createAngularJSTestingModule(angularModules: any[]): string;
+
+export declare function createAngularTestingModule(angularJSModules: string[], strictDi?: boolean): Type<any>;