2013-02-24 37 views
22

Tôi đang sử dụng hoa nhài để kiểm tra đơn vị một bộ điều khiển angularjs mà bộ biến trên phạm vi để kết quả của cách gọi một phương pháp dịch vụ mà trả về một đối tượng lời hứa:Angularjs lời hứa không được giải quyết trong kiểm tra đơn vị

var MyController = function($scope, service) { 
    $scope.myVar = service.getStuff(); 
} 

bên trong dịch vụ:

function getStuff() { 
    return $http.get('api/stuff').then(function (httpResult) { 
     return httpResult.data; 
    }); 
} 

Điều này làm việc tốt trong bối cảnh ứng dụng góc cạnh của tôi, nhưng không hoạt động trong thử nghiệm đơn vị hoa nhài. Tôi đã xác nhận rằng "sau đó" gọi lại là thực hiện trong thử nghiệm đơn vị, nhưng $ scope.myVar lời hứa không bao giờ được thiết lập để giá trị trả về của cuộc gọi lại.

kiểm tra đơn vị của tôi:

describe('My Controller', function() { 
    var scope; 
    var serviceMock; 
    var controller; 
    var httpBackend; 

    beforeEach(inject(function ($rootScope, $controller, $httpBackend, $http) { 
    scope = $rootScope.$new(); 
    httpBackend = $httpBackend; 
    serviceMock = { 
     stuffArray: [{ 
     FirstName: "Robby" 
     }], 

     getStuff: function() { 
     return $http.get('api/stuff').then(function (httpResult) { 
      return httpResult.data; 
     }); 
     } 
    }; 
    $httpBackend.whenGET('api/stuff').respond(serviceMock.stuffArray); 
    controller = $controller(MyController, { 
     $scope: scope, 
     service: serviceMock 
    }); 
    })); 

    it('should set myVar to the resolved promise value', 
    function() { 
     httpBackend.flush(); 
     scope.$root.$digest(); 
     expect(scope.myVar[0].FirstName).toEqual("Robby"); 
    }); 
}); 

Ngoài ra, nếu tôi thay đổi bộ điều khiển như sau bài kiểm tra đơn vị đi:

var MyController = function($scope, service) { 
    service.getStuff().then(function(result) { 
     $scope.myVar = result; 
    }); 
} 

Tại sao lời hứa gọi lại giá trị kết quả không được tuyên truyền đến $ phạm vi .myVar trong bài kiểm tra đơn vị? Xem jsfiddle sau để biết mã hoạt động đầy đủ http://jsfiddle.net/s7PGg/5/

Trả lời

21

Tôi đoán rằng chìa khóa cho "bí ẩn" này là thực tế là AngularJS sẽ tự động giải quyết lời hứa (và trả kết quả) nếu được sử dụng trong chỉ thị nội suy trong mẫu. Những gì tôi có nghĩa là cho điều khiển này:

MyCtrl = function($scope, $http) { 
    $scope.promise = $http.get('myurl', {..}); 
} 

và mẫu:

<span>{{promise}}</span> 

AngularJS, sau khi hoàn thành $ http cuộc gọi, sẽ "nhìn thấy" rằng một lời hứa đã được giải quyết và sẽ tái làm mẫu với kết quả đã được giải quyết. Đây là những gì mơ hồ được đề cập trong $q documentation: q lời hứa

$ được công nhận bởi các công cụ khuôn mẫu trong góc, mà có nghĩa là trong các mẫu bạn có thể đối xử với những lời hứa gắn liền với một phạm vi như nếu họ là những giá trị kết quả .

Mã nơi phép thuật này xảy ra có thể được nhìn thấy here.

NHƯNG, "ảo thuật" này chỉ xảy ra khi có mẫu ($parse dịch vụ, chính xác hơn) khi phát. Trong thử nghiệm đơn vị của bạn không có mẫu liên quan đến độ phân giải lời hứa không được tự động lan truyền.

Bây giờ, tôi phải nói rằng độ phân giải kết quả/độ phân giải tự động này rất thuận tiện nhưng có thể gây nhầm lẫn, như chúng ta có thể thấy từ câu hỏi này. Đây là lý do tại sao tôi thích để tuyên truyền một cách rõ ràng kết quả giải quyết như bạn đã làm:

var MyController = function($scope, service) { 
    service.getStuff().then(function(result) { 
     $scope.myVar = result; 
    }); 
} 
+0

câu trả lời tuyệt vời, tôi dường như đã bị mất rằng bit trong các tài liệu. – robbymurphy

+0

Nếu bạn đang chế nhạo phần đầu sau như tôi, kết quả sẽ là một tổng hợp với thuộc tính "dữ liệu" giữ nội dung phản hồi thực tế. – Gepsens

+3

Trong Angular 1.2, lời hứa không còn được giải quyết tự động (AKA, unwrapped). – zhon

3

@ pkozlowski.opensource đã trả lời câu hỏi tại sao (THANK YOU!), Nhưng không làm thế nào để có được xung quanh nó trong thử nghiệm.

Giải pháp tôi vừa đến là kiểm tra HTTP đang được gọi trong dịch vụ và sau đó theo dõi các phương thức dịch vụ trong kiểm tra điều khiển và trả về giá trị thực thay vì lời hứa.

Giả sử chúng ta có một dịch vụ người dùng mà nói chuyện với máy chủ của chúng tôi:

var services = angular.module('app.services', []); 

services.factory('User', function ($q, $http) { 

    function GET(path) { 
    var defer = $q.defer(); 
    $http.get(path).success(function (data) { 
     defer.resolve(data); 
    } 
    return defer.promise; 
    } 

    return { 
    get: function (handle) { 
     return GET('/api/' + handle); // RETURNS A PROMISE 
    }, 

    // ... 

    }; 
}); 

Testing rằng dịch vụ, chúng tôi không quan tâm những gì xảy ra với các giá trị trở về, duy nhất mà các cuộc gọi HTTP đã được thực hiện một cách chính xác.

describe 'User service', -> 
    User = undefined 
    $httpBackend = undefined 

    beforeEach module 'app.services' 

    beforeEach inject ($injector) -> 
    User = $injector.get 'User' 
    $httpBackend = $injector.get '$httpBackend' 

    afterEach -> 
    $httpBackend.verifyNoOutstandingExpectation() 
    $httpBackend.verifyNoOutstandingRequest()   

    it 'should get a user', -> 
    $httpBackend.expectGET('/api/alice').respond { handle: 'alice' } 
    User.get 'alice' 
    $httpBackend.flush()  

Bây giờ trong các kiểm tra điều khiển của chúng tôi, không cần phải lo lắng về HTTP. Chúng tôi chỉ muốn thấy rằng dịch vụ Người dùng đang được đưa vào hoạt động.

angular.module('app.controllers') 
    .controller('UserCtrl', function ($scope, $routeParams, User) { 
    $scope.user = User.get($routeParams.handle); 
    }); 

Để kiểm tra điều này, chúng tôi giám sát dịch vụ người dùng.

describe 'UserCtrl',() -> 

    User = undefined 
    scope = undefined 
    user = { handle: 'charlie', name: 'Charlie', email: '[email protected]' } 

    beforeEach module 'app.controllers' 

    beforeEach inject ($injector) -> 
    # Spy on the user service 
    User = $injector.get 'User' 
    spyOn(User, 'get').andCallFake -> user 

    # Other service dependencies 
    $controller = $injector.get '$controller' 
    $routeParams = $injector.get '$routeParams' 
    $rootScope = $injector.get '$rootScope' 
    scope = $rootScope.$new(); 

    # Set up the controller 
    $routeParams.handle = user.handle 
    UserCtrl = $controller 'UserCtrl', $scope: scope 

    it 'should get the user by :handle', -> 
    expect(User.get).toHaveBeenCalledWith 'charlie' 
    expect(scope.user.handle).toBe 'charlie'; 

Không cần giải quyết lời hứa. Hi vọng điêu nay co ich.

+0

Trong ví dụ đó khi bạn đang thử nghiệm dịch vụ, chúng tôi phải thêm thông tin sau đây trước khi gọi $ httpBackend.flush(): $ rootScope. $ Apply() – blaster

+2

@blaster ... thì bạn đang làm gì đó sai. Dịch vụ nên được kiểm tra bên ngoài bối cảnh của bộ điều khiển và phạm vi. –

8

Tôi gặp sự cố tương tự và để điều khiển của tôi gán $ scope.myVar trực tiếp cho lời hứa. Sau đó, trong bài kiểm tra, tôi xích lại lời hứa khác khẳng định giá trị kỳ vọng của lời hứa khi nó được giải quyết. Tôi đã sử dụng một phương pháp helper như thế này:

var expectPromisedValue = function(promise, expectedValue) { 
    promise.then(function(resolvedValue) { 
    expect(resolvedValue).toEqual(expectedValue); 
    }); 
} 

Note rằng tùy thuộc vào thứ tự khi bạn gọi expectPromisedValue và khi sự hứa hẹn được giải quyết bằng mã của bạn trong kiểm tra, bạn có thể cần phải tự kích hoạt tiêu hóa một thức chu kỳ để chạy để có được những phương thức sau đó() để kích hoạt - không có nó, thử nghiệm của bạn có thể vượt qua bất kể là resolvedValue bằng expectedValue hay không.

Để được an toàn, đưa cò trong một cuộc gọi afterEach(), do đó bạn không cần phải nhớ nó cho mỗi bài kiểm tra:

afterEach(inject(function($rootScope) { 
    $rootScope.$apply(); 
})); 
Các vấn đề liên quan