How to test Angularjs route's resolve value on

2019-06-14 12:03发布

问题:

I'm using angular@1.5.8 and resolve some resource at the route level for one of my component. The component works as expected but my test fails now.

Error

PhantomJS 2.1.1 (Linux 0.0.0) module ag.expense-claim ExpenseClaimController: should load data FAILED
        TypeError: undefined is not an object (evaluating 'vm.attachedReceipts.results') (line 16)
        ExpenseClaimViewController@app/expenses/expense_claim.js:16:50
        ExpenseClaimViewController@[native code]
        instantiate@node_modules/angular/angular.js:4733:61
        $controller@node_modules/angular/angular.js:10369:39
        node_modules/angular-mocks/angular-mocks.js:2221:21
        $componentController@node_modules/angular-mocks/angular-mocks.js:2264:25
        test/expenseSpec.js:18:40
        invoke@node_modules/angular/angular.js:4718:24
        workFn@node_modules/angular-mocks/angular-mocks.js:3085:26
        loaded@http://localhost:9876/context.js:151:17
        inject@node_modules/angular-mocks/angular-mocks.js:3051:28
        test/expenseSpec.js:14:26
        test/expenseSpec.js:11:13
        global code@test/expenseSpec.js:1:9
        Error: No pending request to flush ! in node_modules/angular-mocks/angular-mocks.js (line 1799)
        flush@node_modules/angular-mocks/angular-mocks.js:1799:76
        test/expenseSpec.js:53:31
        loaded@http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0): Executed 43 of 43 (1 FAILED) (0.257 secs / 0.387 secs)
error Command failed with exit code 1.

Test

describe('ExpenseClaimController:', function () {
    var $scope, ctrl, attachedReceipts;

    beforeEach(inject(function ($rootScope, $componentController, $stateParams) {
        var attachedReceipts = {results: [{}]};
        $scope = $rootScope.$new();
        $stateParams.expenseClaimId = 1;
        ctrl = $componentController('expenseClaim', {
            $scope: $scope,
            attachedReceipts: attachedReceipts
        });
    }));

    it('should load data', function () {
       …
    });

Component

angular.module('ag.expenses')
    .component('expenseClaim', {
        templateUrl: '…',
        controller: ExpenseClaimViewController,
        controllerAs: 'vm',
        bindings: {
            attachedReceipts: "<"
        }
    });

function ExpenseClaimViewController($stateParams, $uibModal, API, gettextCatalog, alert) {
    var vm = this;
    vm.attachedReceipts = vm.attachedReceipts.results;
    …
}

Route

 .state('expense-claim', {
        url: '/home_expense_report/:expenseClaimId',
        template: '<expense-claim attached-receipts="$resolve.attachedReceipts"></expense-claim>',
        resolve: {
            attachedReceipts: function (API, $stateParams) {
                return API.TransportCostAttachment.query({expenseClaimId: $stateParams.expenseClaimId}).$promise;
            }
        }
    })

Question

I implement my solution based on How can I mock ui-router's resolve values when testing a state's configuration? but still can't get it to works. What am I missing?

回答1:

the resolve in angular 1.5 is not DI not like angular 2, so for 1.5 you should pass it as a binding, something like this:

var bindings = { attachedReceipts: attachedReceipts };
ctrl = $componentController('expenseClaim', {
            $scope: $scope
        }, bindings);


回答2:

In addition to Ameer response, I'd like to provide a few more details as I struggled a lot to find them, if you have a component and you need to unit test the controller directly or if you want to test the template, you have two ways of creating the controller that have bindings (whether they are coming from a resolve from ui-router or not doesn't matter)

- Unit testing the component controller:

Like Ameer said, you should pass it as a binding:

beforeEach(inject(function(_$componentController_) {
  $componentController = _$componentController_;
}));

it('should expose a `hero` object', function() {
  // Here we are passing actual bindings to the component
  var bindings = {hero: {name: 'Wolverine'}};
  var ctrl = $componentController('heroDetail', null, bindings);

  expect(ctrl.hero).toBeDefined();
  expect(ctrl.hero.name).toBe('Wolverine');
});

$onInit method:

If your controller has $onInit() method, it won't be executed automatically like you expect, you need to manually call the method after creating the controller, more details on this issue here

- Unit testing the template of the component:

When unit testing the template of a component, you still need to instantiate the controller, this is how you can do:

beforeEach(inject(($injector) => {
  $rootScope = $injector.get('$rootScope');
  $compile = $injector.get('$compile');
  scope = $rootScope.$new();
  scope.attachedReceipts = {results: [{}]};;

  template = $compile('<expense-claim attached-receipts="attachedReceipts"></expense-claim>')(scope);
  scope.$apply();
});

it('should test something', () => {
  //Example of something you could test
  expect(template.find('h3').html()).to.eq('this is our first content page');
});

With this second method, the $onInit() method will be called, so you don't need to call it manually. A full tutorial on how to unit test a template within a component can be found here