简体   繁体   中英

Angular 2: “Unknown Provider: $$angularInjectorProvider” when unit testing with an `UpgradeModule` downgraded service

Our app currently has some 4,500+ existing Angular 1 tests. We're now incorporating Angular 2 using UpgradeModule to downgrade an Angular 2 service, which is used from our app's run() method. This causes all tests to fail with a Error: [$injector:unpr] Unknown Provider $$angularInjectorProvider error.

I created a plnkr to demonstrate this issue ( http://plnkr.co/edit/XuMYBr9xInqq18Kr6EAs?p=preview ).

Basically the Angular 1 module looks like this:

// angular1.module.ts

angular.module( 'testApp' )
    .run( [ 'Angular2Service', Angular2Service => {  // injecting downgraded service
        Angular2Service.showTestMessage();
    } ] );

Where Angular2Service is downgraded with:

// app.module.ts

angular.module( 'testApp' )
    .factory( 'Angular2Service', downgradeInjectable( Angular2Service ) );


@NgModule({
    imports: [ BrowserModule, UpgradeModule ],
    providers : [ Angular2Service ]
})
export class AppModule {
    ngDoBootstrap() {}
}

Originally with this so far, I was getting an Unknown Provider error for 'Angular2Service' itself when the tests executed, so I added a beforeEach() that compiles everything in AppModule :

beforeEach( async( () => {
    TestBed.configureTestingModule( {
        imports : [ AppModule ]
    } )
        .compileComponents();
} ) );

And that's where I'm at with the Error: [$injector:unpr] Unknown Provider $$angularInjectorProvider error.

The test itself that is executing is purely an Angular 1 test, which passes if I don't inject Angular2Service into the run() method:

describe( 'testComponent', () => {
    var $compile,
        $scope;

    beforeEach( module( 'testApp' ) );

    beforeEach( inject( $injector => {
        $compile = $injector.get( '$compile' );
        $scope = $injector.get( '$rootScope' ).$new();
    } ) );


    it( 'should show the text "Test Component" in the DOM', () => {
        var element = $compile( '<test-component></test-component>' )( $scope );
        $scope.$apply();

        expect( element[ 0 ].innerHTML ).toBe( 'Test Component' );
    } );

} );

I'm hoping that this can be fixed so that all of our pure Angular 1 tests can continue to work unchanged, but I can update them too if need be. Any help on this would be greatly appreciated!

http://plnkr.co/edit/XuMYBr9xInqq18Kr6EAs?p=preview

I faced the same problem and came with a solution. It is not the best to be done, but it just solves the problem in the migration phase.

I'm sure that you use upgrade-static.umd.js dist file rather than upgrade.umd.js . The static one is intended to be used with ngc AoT compilation which we should use in production apps. If you dig into its code you can easily figure it defines new angularjs module named $$UpgradeModule which registers a value provider named $$angularInjector (the one in the error above) - this $$angularInjector thing is responsible for injecting Angular modules into angularjs.

The problem

Your old angularjs test don't initiate $$UpgradeModule so its providers are not injectable. Note that angular-mocks (which defines both global module and inject fns) do some patches for jasmine and mocka to provide some injection isolation between suites.

The solution

You simply need to call module('$$UpgradeModule') in the first beforeEach every time you will test a code that will inject (directly or indirectly) the downgraded provider.

However, this is not that simple :). Actually $$UpgradeModule is declared in UpgradeModule.boorstrap function. which means you need to bootstrap the whole app (both angularjs and Angular) before any test (or in more strict form - bootstrap the root module). This is not nice because we want to test modules and providers in complete isolation.

Another solution

An alternative may be to use the UpgradeAdapter defined in upgrade.umd.js . Honestly, I didn't try it - but from the code it seems it can initiate just the modules that we want without bootstrapping the whole app. But I think we may need to redo downgrades in tests using UpgradeAdapter methods to have things work.


EDIT

Example:

To bootstrap, you need to import your app module is SystemJs before importing test files. So ur karma-stest-shim.js will contain something like that

System.import('systemjs.config.js')
  .then(importSystemJsExtras)
  .then(importApp)
  .then(initTestBed)
  .then(initTesting);

function importApp() {
    return Promise.all(
        [
            System.import('app'),
        ]
    )
}

This will do the normal bootstrap for the both versions and make $$upgradeModule defined.

Then in any test that will inject the downgraded provider

beforeEach(function () {
    // the ng1 module for the downgraded provider 
    module('services');
    // the trick is here
    module('$$UpgradeModule');
});

Another option is to just mock out the service being downgraded. When you have in your angular part something like this:

angular.module('ng1module')
  ).factory('service', downgradeInjectable(MyService) as any);

then you can mock the service with the $provide -syntax as follows:

module(function ($provide) {
        $provide.value('service',{});
})

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM