I have a very simple jasmine unit test that targets testing a simple operation on an AngularJS controller as follows:
/// <reference path="../src/jasmine.js"/>
/// <reference path="../../Scripts/angular.js"/>
/// <reference path="../../Scripts/angular-mocks.js"/>
/// <reference path="../../Scripts/angular-ui-router.js"/>
/// <reference path="../../Scripts/angular-route.js"/>
/// <reference path="../../Scripts/angular-resource.js"/>
/// <reference path="../../Scripts/angular-ui/ui-bootstrap.js"/>
/// <reference path="../../app/app.js"/>
/// <reference path="../../app/controllers/myController.js"/>
describe("my Controller", function () {
var scope, ctrl, vm;
beforeEach(module("app"));
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
ctrl = $controller(MyController, { $scope: scope });
vm = ctrl;
}));
it("sets the title", function() {
expect(vm.title).toBe("This is the title");
});
});
To begin the above test passes when using either of the following:
Open SpecRunner.html which contains the same references and displays a passed unit test in the jasmine default test runner
Test without modification passes if running via ReSharper test runner (this tool is not important other than to show the test passes using another test runner; the test isn't the culprit)
However no matter whenever I do any of the following using Chutzpah the test fails:
Run from the command line using chutzpah.console.exe
Use the right-click menu open in VS.NET to Run JS Tests
Each time the following error is given:
Error: [$injector:modulerr] Failed to instantiate module
app due to: [$injector:nomod] Module
'app' is not available! You either misspelled the
module name or forgot to load it. If registering a module ensure that
you specify the dependencies as the second argument.
So this is a very typical error if the AngularJS references are not included. However as you can see above they are for sure included and the other test runners see them and the test passes. The error is identical regardless if I run the unit test from the command line or if I view the output in VS.NET.
I even turned on the /trace parameter from the command line and viewed the chutzpah.log file. It sure enough is finding the Angular reference files so I'm not sure why the test is failing.
I've seen a ton of posts surrounding this and the solution is always that the references are missing. That's not the case here and I'm stuck. What am I missing to make this test pass using Chutzpah?
I recommend using a chutzpah.json settings file for angular projects. I placed mine at the root of my project, next to the web.config. You can specify the dependencies in one place this way, which is very helpful as the angular project grows rapidly. I can't know for sure, but I think yours would look something like the following:
{
"Framework": "jasmine",
"FrameworkVersion": "1",
"References": [
{ "Path": "./Scripts/angular/angular.js" },
{
"Path": "./Scripts/angular",
"Includes": [ "*.js" ]
},
{ "Path": "./Scripts/angular-ui/ui-bootstrap.js" },
{ "Path": "./app/app.js" },
{
"Path": "./app",
"Includes": [ "*.js" ]
}
],
"Tests": [
{ "Includes": [ "*.spec.js" ] }
]
}
This will run on jasmine version 1. I'm not sure how you It will include everything that matches "./Scripts/angular*.js" and everything that matches "./app/*.js" (including sub-directories). As you add new angular references and app modules, you should not need to modify this configuration (once you get it working).
I believe through thorough testing I have determined the cause. The issue is most likely that I'm using ES6 classes in my Angular .js files being used in my tests.
The issue is PhantomJS which Chutzpah uses does not support ES6 and the class keyword and thus it fails (see this for validation). I was able to reproduce in a non Angular sample. I used an old fashioned ES5 style IIFE and the simple test now passed. This is why it worked in Chrome and in ReSharper which used Chrome as the default test browser and Chrome supports the ES6 class keyword. If I make ReSharper use PhantomJS instead of Chrome, then my simple test also fails.
The root problem appears to be PhantomJS and its lack of support for ES6 syntax.
Here is a blog post I wrote on my findings after pulling my hair out on this for a couple of weeks. It explains more in detail about the issues with writing non-ES5 complaint code and using that with Chutzpah and PhantomJS:
Chutzpah and non-ES5 JavaScript for Unit Testing is Problematic
Related
I searched a lot of posts and also the official Angular documentation, but I'm not able to get an AngularJS service running in Angular. I finally came to this page https://angular.io/api/upgrade/static/UpgradeModule#examples which seems to explain exactly what I need, but when doing all those steps I'm getting:
ERROR Error: Trying to get the AngularJS injector before it being set.
My impression is that this example is not quite complete. E.g. there is no hint were the (old) AngularJS framework must be loaded. My service looks like angular.module('my-module').service('my-service', ... thus angular needs to be defined, otherwise I'm getting an error. Furthermore many examples assume that the AngularJS code is written in TypeScript. In my case this is not true (just plain Javascript).
Unfortunately with Angular 9 there is an additional issue with the #angular/upgrade module which is not mentioned anywhere and can only be solved by disabling the new Ivy compiler in tsconfig.app.json, otherwise the compiler will throw Error: Error on worker #1: Error: getInternalNameOfClass() called on a non-ES5 class: expected UpgradeComponent to have an inner class declaration:
"angularCompilerOptions": {
"enableIvy": false
}
I'd really appreciate if somebody could post a complete example on what exactly must be done in order to run an AngularJS service in an Angular component.
UPDATE [6th July 2020]
Here you can find a GitHub repo which you can clone, to reproduce the behavior: https://github.com/berkon/angularjs-service-upgrade-test. I should also mention that I'm using the Electron framework and started based on this repo https://github.com/maximegris/angular-electron but I guess that shouldn't matter in this case.
Finally I got it working! It was really really cumbersome to figure this all out. A lot of things aren't mentioned in most tutorials and even in the official Angular guide there are only code snippets which make it hard for Angular newbies to guess where to put all that stuff. Also the bootstraping is not explained correctly. Furthermore all tutorials assume that the "old" AngularJS code is already written in TypeScript, which makes it even harder to find the right way/order to load/bootstrap/import all that stuff. Finally there seems to be an issue with the #angular/upgrade module in combination with the new Ivy compiler in Angular 9. It throws the error mentioned below. Thus it must be disabled to get things working. A real pain!!!
So roughly these are the steps:
install the angular and #angular/upgrade node modules
load all .js modules including AngularJS in the script section of angular.json
interrupt the regular Angular bootstrap process by removing the bootstrap section from #NgModule and bootstrap AngularJS via ngDoBootstrap manually. First bootstrap
AngularJS, afterwards bootstrap the AppComponent class. This way the service is available at AppComponent initialization. Otherwise you'll get an injection error!
Add a new provider in providers [] section to get access to the new service
Now the new (upgraded) service can be injected in the constructor of AppComponent
Its quite a lot of work to perform all steps below manually, but I listed them for reference. Here you can find a GitHub repo where you can clone a working app. Don't be surprised! This repo uses the Electron framework (electronjs.org). But don't worry this doesn't have any influence on my findings: https://github.com/berkon/angularjs-service-upgrade-test
And here is the step-by-step guide:
Prerequistes
execute npm install angular --save
execute npm install #angular/upgrade --save
in tsconfig.app.json add "enableIvy": false to angularCompilerOptions to avoid getting:
Error: getInternalNameOfClass() called on a non-ES5 class: expected UpgradeComponent to have an inner class declaration
add "node_modules/angular/angular.js" and the Javascript file which contains your AngularJS service (in this case "src/app/angular-js-service.js") to the scripts [] array in angular.json
app.module.ts
add ApplicationRef to the import brackets of #angular/core
add import { UpgradeModule } from '#angular/upgrade/static'
add UpgradeModule to imports [] array of #NgModule
remove bootstrap section completely from #NgModule and replace it with this: entryComponents: [AppComponent]
add this to the providers [] array in #NgModule and make sure to replace myService with the correct name of your service:
{ provide: 'myService', useFactory: (i: any) => { return i.get('myService') }, deps: ['$injector'] }
replace the constructor of AppModule with this:
constructor ( public upgradeModule: UpgradeModule ) {}
add this to the AppModule class and make sure to replace ajsAppModule with the name of your AngularJS main app module:
ngDoBootstrap ( appRef: ApplicationRef ) {
this.upgradeModule.bootstrap(document.body, ['ajsAppModule'], { strictDi: true } )
appRef.bootstrap ( AppComponent )
}
app.component.ts
add Inject to the import brackets at #angular/core
in the AppComponent class change the constructor to this and make sure to replace myService with the name of your AngularJS service
constructor ( #Inject('myService') myService: any ) {
myService.doSomething()
}
I had this same error and I solved it in my app, however I cannot remember exactly why this was happening (sorry, it was a long time ago). I wasn't upgrading services, instead I was downgrading.
Here's my app.module.ts I've added comments to the parts that were critical to get this working, I hope there may be a hint for you here. Note that I used the Angular CLI to generate the app.
setAngularJSGlobal(angular);
// Configure the angularjs app (yours might be defined elsewhere)
const app = angular.module('app', [MyFormsModule, AngularMaterialModule]);
app.run(RunAddressAutocompleteConfig);
app.run(RunDynamicQueryConfig);
// Downgrade Angular AppComponent so AngularJS can render it after bootstrapping
// my app used an Angular component as the root
app.directive('appRoot', downgradeComponent({ component: AppComponent }));
// Downgrade Angular services
app.factory('api', downgradeInjectable(ApiService));
app.factory('dynamicQuery', downgradeInjectable(DynamicQueryService));
#NgModule({
declarations: [
AppComponent,
FormDirective,
FormPageComponent,
FormsListPageComponent,
RouterLinkPreserveQueryParamsDirective,
FormEmptyStatePageComponent,
],
imports: [BrowserModule, UpgradeModule, AppRoutingModule, HttpClientModule, CommonModule],
// This was absolutely necessary for bootstrapping my app in this way
// I encountered errors otherwise
providers: [
{
provide: '$scope',
useExisting: '$rootScope',
},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
entryComponents: [AppComponent],
})
export class AppModule implements DoBootstrap {
constructor(private readonly upgrade: UpgradeModule) {}
ngDoBootstrap(appRef: ApplicationRef) {
this.upgrade.bootstrap(document.body, [app.name], { strictDi: true });
appRef.bootstrap(AppComponent);
}
}
index.html
<body>
<app-root></app-root>
</body>
I'm using yeoman generator for scaffolding angular web application with requirejs. Its working fine but when I tried to concat and minifying all the js file into a single file through grunt task runner its started giving me above mentioned error. I've researched online about the issue and common solution is I may be mis-spelled any service injecting in the module or service does not exists, I've cross checked again all the spelling, quotation marks etc everything seems fine but still I'm unable to resolve this issue.
Here is my app.js file where my main module with dependencies is listed.
return angular
.module('arteciateYeomanApp', [
'arteciateYeomanApp.controllers.MainCtrl',
'arteciateYeomanApp.controllers.AboutCtrl',
'arteciateYeomanApp.services.Xhr',
'arteciateYeomanApp.services.Common',
'arteciateYeomanApp.controllers.ArtworkCtrl',
'arteciateYeomanApp.controllers.AddAccountCtrl',
'arteciateYeomanApp.controllers.AddArtgroupCtrl',
'arteciateYeomanApp.controllers.AddArtistCtrl',
'arteciateYeomanApp.controllers.AddArtworkCtrl',
'arteciateYeomanApp.controllers.AddCampaignsCtrl',
'arteciateYeomanApp.controllers.AddGenreCtrl',
'arteciateYeomanApp.controllers.AddInstitutionCtrl',
'arteciateYeomanApp.controllers.AdminSignupCtrl',
'arteciateYeomanApp.controllers.ArtistInfoCtrl',
'arteciateYeomanApp.controllers.DirectUserSignupCtrl',
'arteciateYeomanApp.controllers.ErrorCtrl',
'arteciateYeomanApp.controllers.ForgotPasswordCtrl',
'arteciateYeomanApp.controllers.GroupBuyingCtrl',
'arteciateYeomanApp.controllers.LoginCtrl',
'arteciateYeomanApp.controllers.AdminLoginCtrl',
'arteciateYeomanApp.controllers.ResetPasswordCtrl',
'arteciateYeomanApp.controllers.SignupCtrl',
'arteciateYeomanApp.controllers.UnblockUserCtrl',
'arteciateYeomanApp.controllers.UpdatePasswordCtrl',
'arteciateYeomanApp.controllers.DashboardCtrl',
'ngRoute','ngResource']).config(.....);
here is grunt task which I'm running for minifying the js files.
registering task
grunt.registerTask('dev', ['requirejs' ]);
Here is task running script
requirejs : {
compile : {
options : {
baseUrl : "<%= yeoman.app %>/scripts",
mainConfigFile : "<%= yeoman.app %>/scripts/main.js",
name : "main",
out : "requireArterciate.js"
}
}
}
Please let me know if I'm doing something wrong here.
If you need to minify the angularjs code, then use the following standard format syntax to define the controller and to inject the dependencies. Refer Dependency Injection
angular.module('test').controller('testController', testController);
testController.$inject = ['$scope', '$rootScope'];
function testController($scope, $rootScope) {};
I'm trying to setup restangular for a SPA project but I'm unable to resolve it as a dependency with grunt-tsng. I've installed it using bower (and tsd for typescript definitions from definitelyTyped).
grunt task configuration
tsng: {
options: {
extension: ".ng.ts"
},
dev: {
files: [
// TODO: Automate the generation of this config based on convention
{
src: ["Client/app/**/*.ts", "!**/*.ng.ts"],
dest: "Client/app"
}
]
}
},
Module app.ts
/// <reference path="../../typings/tsd.d.ts" />
module App {
var dependencies = [
"ui.router",
"ui.bootstrap",
"restangular"
...
];
function configuration(
$stateProvider: ng.ui.IStateProvider,
$urlRouterProvider: ng.ui.IUrlRouterProvider,
RestangularProvider: restangular.IProvider, <-- here it fails
...
ts.d.ts (reference file)
/// <reference path="restangular/restangular.d.ts" />
grunt error
Running "tsng:dev" (tsng) task
Warning: Error: Can't resolve dependency for module function App.config with name restangular.IProvider Use --force to continue.
Other dependencies, such as ui-router, ui-bootstrap etc. are working fine. What could be the cause of this? Are there any incompatibilities known with grunt-tsng?
Alright, I figured it out.
When executing grunt-tsng, it scans all dependencies/modules etc. to automagically hook them into angular so you don't have to do it yourself. Here two simple rules apply: Dependencies to inject that are your own (e.g. where you have typescript inplementation code present, such as classes in your modules) can be defined simply with their name. E.g.:
constructor(service: MyModule.IMyService) {}
External dependencies, e.g. from angular itself are declared with the '$' notation. grunt-tsng will assume their dependencies itself are resolved at runtime and thus just hooks them up with their name:
constructor($routeProvider: ng.IRouteService) {}
But now services in restangular aren't named '$restangular', but just 'restangular'. This leads to the following:
When declaring it as 'restangular', grunt-tsng will look for implementations in your modules, doesn't find any and fails
When declaring it as '$restangular', angular will look for providers called '$restangular', doesn't find any and fails
Conclusion:
This is retarded.
I am attempting to incorporate the humane.js notification library into my AngularJS app. I have wrapped the humane.js use into an Angular service, and the app is working correctly. But when I attempt to write unit tests for the Angular service, I get the following error when attempting to execute the first test of this service:
ReferenceError: humane is not defined
My karma.conf file contains the reference to the humane.js file:
files: [
'bower_components/angular/angular.js',
'bower_components/angular-route/angular-route.js',
'bower_components/angular-mocks/angular-mocks.js',
'bower_components/messageformat/messageformat.js',
'bower_components/angular-translate/angular-translate.js',
'bower_components/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.js',
'bower_components/angular-translate-loader-static-files/angular-translate-loader-static-files.js',
'bower_components/momentjs/moment.js',
'other_components/highcharts-ng/src/directives/highcharts-ng.js',
'other_components/keylines/keylines.js',
'other_components/logging/log4javascript.js',
'bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
'bower_components/humane-js/humane.js',
'scripts/*.js',
'scripts/*/*.js',
'resources/defaultTranslations.js',
'test/unit/*/*.js',
'views/*.html'
],
I can't figure out what's going wrong. If I replace Humane.js with an otherwise similar notification library - alertify.js - and do the exact same thing, I do not get the error.
What am I missing? Like I said, the application works, including the notification portion. Only the unit tests are affected. The Humane.js module uses some unusual (to me) self-executing module syntax:
;!function (name, context, definition) {
if (typeof module !== 'undefined') module.exports = definition(name, context)
else if (typeof define === 'function' && typeof define.amd === 'object') define(definition)
else context[name] = definition(name, context)
}('humane', this, function (name, context) {
...module definitions here...
return new Humane()
})
I'm wondering if this is confusing the karma loader somehow. The other library (alertify.js) does not have this structure.
Got here while trying to figure out a different Karma error. It might not be related but I see your karma file paths look different that to me. I usually use the default generated via the angular-generator and it products something like this in my karma kong:
files : [
'app/bower_components/angular/angular.js',
'app/bower_components/angular-mocks/angular-mocks.js',
'app/bower_components/angular-resource/angular-resource.js',
'app/bower_components/angular-cookies/angular-cookies.js',
'app/bower_components/angular-sanitize/angular-sanitize.js',
'app/bower_components/angular-route/angular-route.js'
...
],
I am successfully unit testing my coffeescript / angularjs code using karma / jasmine.
One of my tests is failing and so I would like to place breakpoints in my coffeescript code.
I understand that I will need to provide source-maps to allow WebStorm 7 to correlate the transpiled javascript to the original coffeescript (see karma config below).
These questions were useful to get me started but have not completely solved the problem:
webstorm 7/karma server looking for wrong sourcemap file when debugging coffeescript
Using Karma-runner with AngularJS, Jasmine, CoffeScript
Now, when I debug, any coffeescript file that has a breakpoint in it will suspend but not on the breakpoint, typically on the first line of the file or the function that contains the breakpoint.
Continuing the execution simply runs to the end and the inner breakpoint is not hit.
Attempting to step through the coffeescript takes me into the jasmine.js code. As I continue to step-through the jasmine code the test suite eventually completes but the breakpoint is never hit.
Here is the relevant portion of my karma config.
karma.config.js
files: [
'App.UnitTests/lib/http_ajax.googleapis.com_ajax_libs_angularjs_1.2.0-rc.2_angular.js',
'App.UnitTests/lib/http_ajax.googleapis.com_ajax_libs_angularjs_1.2.0-rc.2_angular-sanitize.js',
'App.UnitTests/lib/http_angular-ui.github.io_ui-router_release_angular-ui-router.js',
'App.UnitTests/lib/http_ajax.googleapis.com_ajax_libs_angularjs_1.2.0-rc.2__angular-mocks.js',
'App.FrontEnd/coffee/**/*.coffee',
'App.UnitTests/tests/**/*.spec.coffee'
],
preprocessors : {
'App.FrontEnd/coffee/**/*.coffee':'coffee',
'App.UnitTests/tests/**/*.spec.coffee':'coffee'
},
coffeePreprocessor: {
options: { bare: true, sourceMap: true },
transformPath: function ( path ) { return path.replace( /.js$/, '.coffee' ); }
},