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
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',{});
})
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.
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.
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.
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.
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');
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With