Angular + Mocha memory leak

2019-02-23 13:21发布

I'm getting the following error when running the entire suite of tests:

timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.

After some investigation, I found out to be a memory leak issue. Looking at some heap profiling snapshot, objects still seem to be referenced and not getting garbaged collected.

Anyone know a solution that would prevent it from happening? There's some options such as going through each one of my 1000ish specs and adding afterEach to do some clean up, but that seems like a lot of work.

Here's a sample layout of how most of my tests look like

describe('MyClassCtrl', function() {

  var $httpBackend, $rootScope, ctrl;
  ctrl = $rootScope = $httpBackend = null;

  beforeEach(function() {
    module('myApp');
    inject(function($controller, $rootScope, _$httpBackend_, $stateParams) {
      var $scope;
      $stateParams.id = 1;
      $httpBackend = _$httpBackend_;
      $scope = $rootScope.$new();
      ctrl = $controller('MyClassCtrl', {
        $scope: $scope
      });
    });
  });

  describe('#_getMyList', function() {
    beforeEach(function() {
      $httpBackend.expectGET("/my/app/url").respond({
        my_list: [1, 2, 3]
      });
      ctrl._getMyList();
      $httpBackend.flush();
    });

    it('does this', function() {
      expect(ctrl.my_list).to.eql([1, 2, 3]);
    });
  });
});

Below are some profiling screenshots:

enter image description here

enter image description here

enter image description here

UPDATE

I was able to trigger a memory leak by simply wrapping one of my it in a loop.

e.g.:

for (i = 0; i < 200; i++) {
  it('does this', function() {
    expect(ctrl.my_list).to.eql([1, 2, 3]);
  });
}

In my tests, I set all objects within a container object and cleaned it up in an afterEach (like the solution here) but no luck. Memory allocation still keep increasing on Chrome's Dev Timeline tool.

Thanks!

3条回答
Rolldiameter
2楼-- · 2019-02-23 13:39

Not sure but this could be related to the memory leak in Mocha that has been fixed recently. Make sure to update your local mocha to 2.4.5 or newer to get the fix and try running the test.

查看更多
干净又极端
3楼-- · 2019-02-23 13:50

It looks like ui-router leaks memory in unit tests. I added the following code in the run block of my application:

$rootScope.$on('$destroy', function() {
    var clearAllMembers = function(obj, depth) { // prevent infinite recursion because of circular references
        depth = depth || 0;
        if (depth > 10) {
            return;
        }
        for (var i in obj) {
            if (obj.hasOwnProperty(i)) {
                if (typeof(obj[i]) == 'object') {
                    clearAllMembers(obj[i], ++depth);
                }
                if (obj) {
                    if (obj[i] && (obj[i] instanceof Array) && obj[i].length) {
                        obj[i].splice(0);
                    }
                    delete obj[i];
                }
            }
        }
    }

    setTimeout(function() {
        clearAllMembers($stateRegistry); // ui-router 1.0+
        clearAllMembers($urlRouter);
        clearAllMembers($urlService); // ui-router 1.0+
        clearAllMembers($state);
    });
});

and the memory consumption in unit tests is 7-8 times smaller (I have around 350 states).

查看更多
你好瞎i
4楼-- · 2019-02-23 13:52

I don't see you cleaning up your mocked http backend. I have no idea how it functions, but I would expect that all your requests and responses are being stored in memory. That would cause it to increase gradually, test by test.

If this was using Sinon I would expect that you would create a stub/spy before the tests and clean it up afterwards (or run it in a sandbox).

Try taking a heap snapshot before and after running a test and diff the snapshots to find which objects keep increasing in numbers.

查看更多
登录 后发表回答