Angular.js: angular-mock 1.5.1 TypeError: undefined is not a constructor (evaluating 'angular.element.cleanData(cleanUpNodes)')

Created on 16 Mar 2016  ·  67Comments  ·  Source: angular/angular.js

My Tests working fine with angular-mock 1.5.0 but with 1.5.1 all Tests fail with:
TypeError: undefined is not a constructor (evaluating 'angular.element.cleanData(cleanUpNodes)') in ../node_modules/angular-mocks/angular-mocks.js (line 2776) $$cleanup@../node_modules/angular-mocks/angular-mocks.js:2776:32 $$afterEach@../node_modules/angular-mocks/angular-mocks.js:2746:23

investigation more info regression bug

Most helpful comment

I was seeing this problem as well with our tests after trying to upgrade to v1.5.2. Changing line 2776 of angular-mocks.js from:
angular.element.cleanData(cleanUpNodes);
To:
if (angular.element.cleanData) angular.element.cleanData(cleanUpNodes);

Fix the issue. In our case, we're trying to spy on angular.element for various reasons.

All 67 comments

That is strange, since no constructor is being called in that bit of code. Perhaps you can provide some more information? A copy of a failing test, preferably one that we can run.

Here you can find a test, it does nothing but every test we have fails (only for version 1.5.1, 1.5.0 is fine).
Test Runner Karma, Test Framework Jasmine. Project Setup "Foundation for Apps"
https://github.com/HamburgOOU/hoou-webapp/blob/master/test/controllerTest/AboutControllerTest.js

It looks like you are using ui-router, is that right?
Can you try to create a test that runs here: http://plnkr.co/edit/tpl:vojSWqpk1yBjQbbPeXRH?

As a suggestion, you should also try to remove dependencies from your test, and see what exactly triggers the error (you have a lot of dependencies in the application module)

Yes, using angular-ui-router(0.2.18).
Trommorow I will try to find the dependency who causes it.

+1, I am receiving a very similar error trying to test a provider.

TypeError: 'undefined' is not a function (evaluating 'angular.element.cleanData(cleanUpNodes)')
at ../node_modules/angular-mocks/angular-mocks.js:2776
at ../node_modules/angular-mocks/angular-mocks.js:2746

Edit: This error occurs with ver 1.5.1, I switched back to 1.5.0 and everything works again.

@petebacondarwin undefined is not a constructor is the cryptic error PhantomJS gives when you try to call a function that is not defined. It's equivalent to Chrome's a.something is not a function error.

We're getting a similar error on Angular/Mock 1.5.1, which is not an issue on 1.5.0:

# PhantomJS
TypeError: undefined is not a constructor (evaluating 'angular.element('<div ng-app></div>').data('$injector', $injector)') 
    in /bower_components/angular-mocks/angular-mocks.js (line 2102)
    /bower_components/angular-mocks/angular-mocks.js:2102:69
    invoke@/bower_components/angular/angular.js:4443:22
    /bower_components/angular/angular.js:4261:43
    getService@/bower_components/angular/angular.js:4402:46
    invoke@/bower_components/angular/angular.js:4434:23
    /bower_components/angular/angular.js:4261:43
    getService@/bower_components/angular/angular.js:4402:46
    invoke@/bower_components/angular/angular.js:4434:23
    /bower_components/angular/angular.js:4261:43
    getService@/bower_components/angular/angular.js:4402:46
    invoke@/bower_components/angular/angular.js:4434:23
    /bower_components/angular/angular.js:4265:85
    forEach@/bower_components/angular/angular.js:336:24
    createInjector@/bower_components/angular/angular.js:4265:10
    workFn@/bower_components/angular-mocks/angular-mocks.js:2922:60

# Chrome
TypeError: angular.element(...).data is not a function
        at $get (/bower_components/angular-mocks/angular-mocks.js:2102:65)
        at Object.invoke (/bower_components/angular/angular.js:4443:17)
        at /bower_components/angular/angular.js:4261:37
        at getService (/bower_components/angular/angular.js:4402:39)
        at Object.invoke (/bower_components/angular/angular.js:4434:13)
        at /bower_components/angular/angular.js:4261:37
        at getService (/bower_components/angular/angular.js:4402:39)
        at Object.invoke (/bower_components/angular/angular.js:4434:13)
        at /bower_components/angular/angular.js:4261:37
        at getService (/bower_components/angular/angular.js:4402:39)

Note that we use jQuery alongside Angular.

Could this be at all related to https://github.com/angular/angular.js/commit/75373dd4bdae6c6035272942c69444c386f824cd?

@nikrolls

angular.element(...).data is not a function

Do I understand it right that angular.element(...) (which is aliased to jQuery(...) if you load jQuery before Angular) returns something that doesn't have the data method? That seems weird. Could you enable "break on rejections" in Chrome DevTools and then, when you hit this line, check what exactly return the following expressions:

  1. angular.element
  2. angular.element(...) (substitute the dots with what you really have there)
  3. ... (substitute the dots with what you had in angular.element)

Thanks!

We have a similar problem since angular v1.5.1.

should update data with success with redirect
          PhantomJS 2.1.1 (Linux 0.0.0)
        TypeError: undefined is not an object (evaluating '$location.path(interpolate(nextRoute.redirectTo, nextRoute.params)).search') in /project/app/bower_components/angular-route/angular-route.js (line 601)
        commitRoute@/project/app/bower_components/angular-route/angular-route.js:601:82
        $broadcast@/project/app/bower_components/angular/angular.js:17207:33
        afterLocationChange@/project/app/bower_components/angular/angular.js:13127:28
        /project/app/bower_components/angular/angular.js:13113:32
        $eval@/project/app/bower_components/angular/angular.js:16884:28
        $digest@/project/app/bower_components/angular/angular.js:16700:36
        flush@/project/app/bower_components/angular-mocks/angular-mocks.js:1779:45
        /project/app/modules/main/partners/partners.spec.js:338:31
        invoke@/project/app/bower_components/angular/angular.js:4625:24
        workFn@/project/app/bower_components/angular-mocks/angular-mocks.js:2933:26
        Error: [$rootScope:inprog] $digest already in progress
        http://errors.angularjs.org/1.5.1/$rootScope/inprog?p0=%24digest (line 17242)
        beginPhase@/project/app/bower_components/angular/angular.js:17242:88
        $digest@/project/app/bower_components/angular/angular.js:16680:19
        flush@/project/app/bower_components/angular-mocks/angular-mocks.js:1779:45
        /project/app/modules/main/partners/partners.spec.js:385:39
        invoke@/project/app/bower_components/angular/angular.js:4625:24
        workFn@/project/app/bower_components/angular-mocks/angular-mocks.js:2933:26
        inject@/project/app/bower_components/angular-mocks/angular-mocks.js:2902:46
        /project/app/modules/main/partners/partners.spec.js:381:23
        undefined

In this case the error is always thrown when the tests includes the inject() function. I guess the spyOn($location, 'path'); in the beforeEach function could be the problem.

describe('ctrl test', function () {
    beforeEach(inject(function ($controller, $location) {
        spyOn($location, 'path');
    }));

    describe('save()', function () {
        it('should work', function(){
            inject(function ($location) {
                // some test code
            });
        });
    });
});

@4kochi - you problem is actually that you have spied on a function but not provided an implementation for the spy. In other words, calling spyOn($location, path); overrides the $location.path method with a spy function but that spy function returns undefined. Later the router is trying to access $location.path().search, which is effectively saying undefined.search.

You need to provide an implementation for the spy: either a fake or a pass-through:

spyOn($location, 'path').andCallThrough();
spyOn($location, 'path').andCallFake(function(url) { return {...}; });

Thanks for the tip @petebacondarwin I had an similar idea. But I wonder then why the exact same test code works with Angular 1.5.0? Did I miss something?

I have the same issue when I use inject,e.g.

  beforeEach(inject(function($rootScope) {

  }));

@dagda1, could you provide the info mentioned in https://github.com/angular/angular.js/issues/14251#issuecomment-198276079 (as well as the exact stack trace for the error) ?

I was seeing this problem as well with our tests after trying to upgrade to v1.5.2. Changing line 2776 of angular-mocks.js from:
angular.element.cleanData(cleanUpNodes);
To:
if (angular.element.cleanData) angular.element.cleanData(cleanUpNodes);

Fix the issue. In our case, we're trying to spy on angular.element for various reasons.

In our case, we're trying to spy on angular.element for various reasons.

That explains the error.

angular.element is a pretty basic "feature" and relied upon heavily internally. I wouldn't recommend stubbing it out. (Spying on it and letting things pass through should be OK though).

BTW, changing the code to if (angular.element.cleanData) angular.element.cleanData(cleanUpNodes); leads to memory leaks that can cause karma to crash on huge testsuites.
(Yes, it has happened already :smiley:)

Thanks @gkalpak - unfortunately any sort of spy (even one that only calls through) seems to cause the same issue. Is there any other work around in the works or other suggestions to try?

@KeithPepin angular.element is a tricky beast. Do you really need to spy on it? Can you give an example of your tests?

i have the same issue.
get some error msg:

PhantomJS 2.1.1 (Linux 0.0.0) 首页单元测试 首页内容控制器测试 调用获取数据接口 encountered a declaration exception FAILED
    ReferenceError: Can't find variable: module in /client/modules/bootstrap/test/bootstrap.spec.js (line 4)
    /client/modules/bootstrap/test/bootstrap.spec.js:4:23
    global code@/client/modules/bootstrap/test/bootstrap.spec.js:3:9
PhantomJS 2.1.1 (Linux 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.041 secs / 0.001 secs)

my test as follows:

describe( '首页模块单元测试', function(){

    var $httpBackend,
        $indexFactory;

    beforeEach( module( 'Datatao.bootstrap' ) ); 

    beforeEach( inject( function( _$httpBackend_,  _IndexFactory_ ){ 
        $httpBackend    = _$httpBackend_;
        $indexFactory   = _IndexFactory_;
    }) ); 

    describe( '首页内容控制器测试', function(){
        it( '调用获取数据接口', function(){
            expect( true ).toBe( true ); 
        });
    });

});

and i use angular 1.4.8 and ui-router 0.2.15 angular-mocks 1.5.2, but after downgrade angular-mocks to 1.5.0, thing is fine.
if you need more infomation, please @me

@terminatorheart We don't support using versions of angular and angular-mock that do not match. Have you tried using 1.5.2 for both?

I have tried to recreate this problem here https://github.com/petebacondarwin/issue-14251
But karma start works fine for me. Can you suggest what else need to be in the project for it to fail?

I am are getting similar errors while moving from 1.5.0 to 1.5.1.

we have following code to do some additional work before application gets bootstrapped. now its failing with below error.

var injMods = ['ng', 'tcpInitModule', 'mpyInitModule'];
var initInjector = angular.injector(injMods);

var tcpMembershipSvc = initInjector.get('tcpMembershipSvc');
//do some work with tcpMembershipSvc provider

//finally bootstrap
angular.bootstrap(document, ['tcpMainModule']);

Error:
Uncaught Error: [$injector:unpr] Unknown provider: $rootElementProvider <- $rootElement <- $location <- $route <- $location
http://errors.angularjs.org/1.5.1/$injector/unpr?p0=%24rootElementProvider%…%24rootElement%20%3C-%20%24location%20%3C-%20%24route%20%3C-%20%24location

On unit tests it fails with below error.
Error: [$injector:modulerr] Failed to instantiate module ng due to:
TypeError: 'undefined' is not an object (evaluating 'Function.prototype.bind.apply')
at instantiate (c:/app/internal_packages/angular/angular.js:4640)

@mlakmal could you create a runnable demo of your issue that we could test?

@petebacondarwin i have added the plnker below.

https://plnkr.co/edit/J32cQo4Fo9fkiRH3pF3L?p=preview

I also noticed that unit test error comes on v1.5.0 as well ...

@mlakmal I think the issue in the plnkr is different from the unit test problem. It's probably got something to do with changes in ngRoute.

The unit test issue seems to be due to Function.prototype.bind being undefined. Are you using PhantomJS 1.x ?

Your other issue is indeed (sort of) related to the ngRoute change (5e37b2a) - it is actually exposing an issue that is already present.

A minimal reproduction is: angular.injector(['ng', 'ngRoute'])
Another minimal reproduction (not involving ngRoute) would be: angular.injector(['ng']).get('$location');

The problem is that the $location service, depends on $rootElement. The $rootElement is not provided by Angular until an app is bootstraped (because it is not known before that). Trying to instantiate $location outside of the context of a bootstraped app, will have the injector try to get the $rootElement and fail, because at that point there is no provider for $rootElement.

TBH, I can't think of a reason to load ngRoute from a custom injector (@mlakmal I would be very interested to know more details about your usecase, even if only OOC :smiley:).

A simple work-around, is to include a dummy extra module that provides a $rootElement:

angular.module('fakeRootElement', []).value('$rootElement', angular.element('<div></div>'));
var initInjector = angular.injector(['ng', 'InitModule', 'fakeRootElement']);

Updated plnkr

Now, on the actual issue, $location uses $rootElement to intercept all click events and handle them in HTML5 mode. We could make this an "optional" dependency (e.g. injecting $rootElement only if it is defined or provide a dummy $rootElement and replace it during bootstrap).
The question is should we ?

HI @gkalpak , you are correct, we are using Phantom JS 1.9.8 and trying to move to v2 but its getting delayed on ours side due to node version changes required on build agents (we are still on node v0.12.x). I locally upgraded PhantomJS to v2 and my unit tests looks good. any idea how to get the PhantomJS 1 issue fixed ?

Your workaround worked nicely to get the issue fixed on injector. I can live with that fix until, we move to AngularJS 2 in future (dreaming ...).

Regarding our use case for ngRoute: we have some services that uses providers from ngRoute on those CommonModule and InitModule. so we had to add ngRoute as module dependency for those modules while creating to support unit test executions. i think we can remove it out of those dependencies and add ngRoute only when its required by unit test.

PhantomJS 1 unit test issues was resolved after adding following polyfill for karma conf. Not sure why this error suddenly came after angular 1.5.x upgrade ...

https://www.npmjs.com/package/phantomjs-polyfill

@mlakmal, the reason why this issue with PhantomJS didn't appear before, is that Angular didn't use Function.prototype.bind. We do now, as it is necessary for supporting native Classes and all the supported browsers provide it. (Note that PhantomJS is not explicitly supported, which means that we don't test against it, but lots of people use it (including the ngMaterial project until some time ago) without problem afaik.)

Regarding relying on ngRoute in InitModule:
ngRoute only provides one provider ($routeProvider), which is useful only for registering routes. I still don't understand why you need to create your own injector including it (considering that each injector will create a different instance of the service - services are singletons with each injector).
If you are loading InitModule just so you can gt hold of some helper services, then it might be a better idea to extract these helper services into a standalone module (which InitModule depends on) and then only load that helper module into your extra injector.

@gkalpak we are using $routeParams from ngRoute in our directives. so thats why we added it to some of modules... but i guess we can add ngRoute module to our spec files separately.

@mlakmal, my point was that I can't see how ngRoute is necessary in your extra injector that you create manually (using angular.injector([...])), since that would be a separate injector (with separate service instances) from your app's injector.

Hey guys, sorry to bring this up again after a couple days, but I just encountered this.

I'm spying on angular.element with all call through, but it's still not working, unfortunately.

spyOn( angular, 'element' ).andCallThrough();

Any thoughts?

@mattgrande I'm thinking we can't do much without a demo. ;)

This is not an issue with Jasmine 2.x btw, because it copies all properties of the spied function onto the spy (see demo).

It is still a problem with Jasmine 1.x, which doesn't copy properties (see demo).
You can work around this, by copying the properties yourself (either all or only the ones that are necessary, e.g. cleanData):

spyOn(angular, 'element').andCallThrough();

// Copy all properties
angular.extend(angular.element, angular.element.originalValue);
// or
// Copy `cleanData` only
angular.element.cleanData = angular.element.originalValue.cleanData;

TBH, I lean towards closing it as works as expected / won't fix. angular.element() is too fundamental and if someone if spying on it, they better be prepared for BCs (or use Jasmine 2 :stuck_out_tongue:)

It would be also easy to work around this inside ngMock, e.g. change angular.element.cleanData(...) to something like:

var ngEl = angular.element;
(ngEl.cleanData || (ngEl.originalValue && ngEl.originalValue.cleanData) || angular.noop)(...)

But tomorrow there might be another testing library that breaks.

So, since:

(a) it is only an issue with Jasmine 1.x (not 2.x),
(b) it only breaks tests not application code and
(c) it is so easy to fix,

I (still) lean towards not "fixing" this.

@gkalpak I am inclined to agree with you. This is another case for the "known issues page", no?

used @KeithPepin solution,

if(angular.element.cleanData)
angular.element.cleanData(cleanUpNodes);

worked for me

Thanks for this guys. I'll try and update to Jasmine 2.x (there aren't _too_ many changes), but if that proves to be too big of a pain, I'll use Keith's solution.

Update: Turns out I was already on Jasmine 2.1, but I updated to the latest, and it didn't affect anything.

I ended up using @gkalpak's "extend" method. Thanks!

@mattgrande, what version of jasmine did you upgrade to ? It's strange that it didn't work, because it seems to work fine on my demo with jasmine v2.4.1 :confused:

same errors with angular-mocks 1.5.2 & 1.5.3
(Jasmine 2.4.1)

Jasmine 2.4.0; Angular version 1.5.2

Hi I'm also having undefined issues with PhantomJS2, karma and jasmine. I'm using the newest versions of karma and jasmine. My app also uses ngRoute.

My problem arises when I try to inject $rootScope and $controller. I'm using ngMock to mock one of my modules. scope and controller are always undefined. However, when I test just the module using angular.module, the tests pass. I'm not using spyOn.

Any suggestions as to what could be the error?

I am also having this issue when upgrading from 1.4.6 to 1.5.3.

Can one of you provide a runnable demo of the problem?

@petebacondarwin here's the travis failed build: https://travis-ci.org/prestonvanloon/newrelic-angular/builds/118604417

Edit: here's the PR that caused it https://github.com/prestonvanloon/newrelic-angular/pull/28

@prestonvanloon, your problem is that you are using different versions of angular and angular-mocks.
You should always use matching versions.

@gkalpak thank you!

@prestonvanloon : Check the version of angular and angular-mocks you are using. I was facing the same issue. When I was using angular-mocks version 1.5.5 and angular version was 1.4.7, I got the same exception. When I upgraded my angular version to 1.5.5, I did not get any errors and all the tests ran successfully.

Thanks @varun85jobs and @gkalpak, that was exactly my issue

Hey guys, I have an Ionic project with angular version 1.5.3 and angular-mocks 1.5.3, but I still get the same error. What could it be?

Hi, same problem here, I have just tried with 1.5.5 and 1.5.3 and still with problems, as temporal solution we use 1.5.0.

KeithPepin's answer fixed it for me, but I prefer not to change a library's code

We still don't have a proper use case for spying on angular.element. Without this we can't move forward with a fix.

There is a question about this issue here as well.
My answer from there, in case it will help anyone here:

I had the same problem and what fixed it to me was to load jquery in my tests:

files: [
      'bower_components/jquery/dist/jquery.js',
      'bower_components/angular/angular.js',
      'bower_components/angular-mocks/angular-mocks.js',
...
]

took the idea from this comment because we also use jQuery alongside Angular.

We still need an actual running reproduction, where all the versions of angular modules used match.

I got this issue: angular.element.parent is not a function

@kristoff2016, this doesn't seem like the same problem. Please open a new issue, providing more info, e.g. the relevant code, the exact error message, your environament (browser, OS etc). Live reproductions (using CodePen, Plnkr etc) are highly appreciated as well :grin:

In my case, the issue was that I was using an old version of karma-phantomjs-launcher (^0.1.4 instead of ^1.0.0) which relied on an older version of PhantomJS. With [email protected], it is working properly.

+1 upgrading karma-phantomjs-launcher and phantomjs to 2+ did the trick

The only option that worked for me until now was downgrading to angular/angular-mocks 1.5.0

It's been months since this was first reported and there is still no actual reproduction (with matching versions) posted here!! We can't fix what we can't see. :worried:

I am going to close this, but would be happy to re-open if we get a reproduction.

Hi @gkalpak, thanks for taking the time. I was able to reproduce the problem, but apparently we were doing something really naughty in our code :see_no_evil:. I have saved in this plnkr. So the following code may not make much sense for you, but we did that as a work around for a very specific situation:

var elSelect = angular.element;
angular.element = function(id) {
        try {
            return elSelect.call(angular, id);
        } catch(err) {
            return $(id);
        }
    };

So this works for version 1.5.0 of angular and angular-mocks when testing, but not for 1.5.1-1.5.7. I don't know if it is this specific case or if other cases using angular.element could also break.

Thx @vitorarins. As mentioned before, it is a very "naughty" thing to overwrite such a certal component as angular.element and not at least preserve it's properties. Using the angular.extend() technique described above, you would be good though.

var ngElement = angular.element;
angular.element = angular.extend(function(id) {
  try {
    return ngElement.call(angular, id);
  } catch(err) {
    return $(id);
  }
}, ngElement);

Updated plnkr

Thank you very much @gkalpak! I hope that at least this serves as reference for other people having this problem. Again Thx a lot!

Hi, I am also facing the same issue. But using angular select. In my controller I am initialising select like this.
angular.element('select').select2();

Added spy on as follows :
var ngElement = angular.element('select');
angular.element = angular.extend(function(id) {
try {
return ngElement.call(angular, id);
} catch(err) {
return $(id);
}
}, ngElement);

spyOn(ngElement, 'select2').and.callFake(function() {

    });

PhantomJS 2.1.1 (Mac OS X 0.0.0) ERROR
TypeError: Attempted to assign to readonly property.
at /node_modules/angular/angular.min.js:9

I had gone through this post fully and used angular 1.5.7 and jasmine 2.4.1. But still issue is not solved.

@lathaMaramganti It's not:

var ngElement = angular.element('select');

but:

var ngElement = angular.element;

You need to save the original function to extend it, not its invocation.

Was this page helpful?
0 / 5 - 0 ratings