Đưa một mô hình vào dịch vụ AngularJS


114

Tôi đã viết một dịch vụ AngularJS và tôi muốn thử nghiệm đơn vị nó.

angular.module('myServiceProvider', ['fooServiceProvider', 'barServiceProvider']).
    factory('myService', function ($http, fooService, barService) {

    this.something = function() {
        // Do something with the injected services
    };

    return this;
});

Tệp app.js của tôi đã đăng ký:

angular
.module('myApp', ['fooServiceProvider','barServiceProvider','myServiceProvider']
)

Tôi có thể kiểm tra DI đang hoạt động như vậy:

describe("Using the DI framework", function() {
    beforeEach(module('fooServiceProvider'));
    beforeEach(module('barServiceProvider'));
    beforeEach(module('myServiceProvder'));

    var service;

    beforeEach(inject(function(fooService, barService, myService) {
        service=myService;
    }));

    it("can be instantiated", function() {
        expect(service).not.toBeNull();
    });
});

Điều này đã chứng minh rằng dịch vụ có thể được tạo bởi khung DI, tuy nhiên tiếp theo tôi muốn kiểm tra đơn vị dịch vụ, có nghĩa là chế nhạo các đối tượng được đưa vào.

Làm thế nào để tôi làm điều này?

Tôi đã thử đưa các đối tượng giả của mình vào mô-đun, ví dụ:

beforeEach(module(mockNavigationService));

và viết lại định nghĩa dịch vụ thành:

function MyService(http, fooService, barService) {
    this.somthing = function() {
        // Do something with the injected services
    };
});

angular.module('myServiceProvider', ['fooServiceProvider', 'barServiceProvider']).
    factory('myService', function ($http, fooService, barService) { return new MyService($http, fooService, barService); })

Nhưng sau này dường như dừng dịch vụ do DI tạo ra.

Có ai biết cách tôi có thể giả mạo các dịch vụ được tiêm cho các bài kiểm tra đơn vị của tôi không?

Cảm ơn

David


Bạn có thể xem câu trả lời này của tôi cho câu hỏi khác, tôi hy vọng nó có thể hữu ích cho bạn.
remigio

Câu trả lời:


183

Bạn có thể đưa các mô hình vào dịch vụ của mình bằng cách sử dụng $provide.

Nếu bạn có dịch vụ sau với phần phụ thuộc có phương thức gọi là getSomething:

angular.module('myModule', [])
  .factory('myService', function (myDependency) {
        return {
            useDependency: function () {
                return myDependency.getSomething();
            }
        };
  });

Bạn có thể đưa vào một phiên bản giả lập của myDependency như sau:

describe('Service: myService', function () {

  var mockDependency;

  beforeEach(module('myModule'));

  beforeEach(function () {

      mockDependency = {
          getSomething: function () {
              return 'mockReturnValue';
          }
      };

      module(function ($provide) {
          $provide.value('myDependency', mockDependency);
      });

  });

  it('should return value from mock dependency', inject(function (myService) {
      expect(myService.useDependency()).toBe('mockReturnValue');
  }));

});

Lưu ý rằng vì cuộc gọi đến $provide.valuebạn thực sự không cần phải chèn myDependency một cách rõ ràng vào bất kỳ đâu. Nó xảy ra dưới mui xe trong quá trình tiêm myService. Khi thiết lập mockDependency ở đây, nó có thể dễ dàng trở thành một gián điệp.

Nhờ loyalBrown cho liên kết để mà video tuyệt vời .


13
Hoạt động tuyệt vời, nhưng hãy cẩn thận một chi tiết: beforeEach(module('myModule'));cuộc gọi ĐÃ đến trước beforeEach(function () { MOCKING })cuộc gọi, nếu không thì những trò giễu cợt sẽ bị các dịch vụ thực ghi đè!
Nikos Paraskevopoulos

1
Có cách nào để chế nhạo không phải dịch vụ nhưng không đổi theo cùng một cách không?
Artem

5
Tương tự như bình luận Nikos', bất kỳ $providecuộc gọi phải được thực hiện trước khi sử dụng $injectornếu không, bạn sẽ nhận được một lỗi:Injector already created, can not register a module!
providencemac

7
Điều gì sẽ xảy ra nếu mô hình của bạn cần $ q? Sau đó, bạn không thể đưa $ q vào mô hình trước khi gọi mô-đun () để đăng ký mô hình. Có suy nghĩ gì không?
Jake

4
Nếu bạn đang sử dụng coffeescript và bạn đang nhìn thấy Error: [ng:areq] Argument 'fn' is not a function, got Object, hãy đảm bảo đặt returnở dòng sau $provide.value(...). Trở về ngầm $provide.value(...)gây ra lỗi đó cho tôi.
yndolok

4

Theo cách tôi nhìn vào nó, không cần phải chế nhạo chính các dịch vụ. Đơn giản chỉ cần mô phỏng các chức năng trên dịch vụ. Bằng cách đó, bạn có thể đưa các dịch vụ thực của mình vào trong toàn bộ ứng dụng. Sau đó, giả lập các chức năng trên dịch vụ nếu cần bằng spyOnchức năng của Jasmine .

Bây giờ, nếu bản thân dịch vụ là một hàm chứ không phải đối tượng mà bạn có thể sử dụng spyOn, thì có một cách khác để xử lý nó. Tôi cần phải làm điều này và tìm thấy thứ gì đó hoạt động khá tốt cho tôi. Hãy xem Bạn chế nhạo dịch vụ Angular là một hàm như thế nào?


3
Tôi không nghĩ điều này trả lời câu hỏi. Điều gì sẽ xảy ra nếu nhà máy của dịch vụ bị giả mạo làm một việc không tầm thường, như tấn công máy chủ để lấy dữ liệu? Đó sẽ là một lý do chính đáng để muốn chế nhạo nó. Bạn muốn tránh cuộc gọi máy chủ và thay vào đó tạo phiên bản giả của dịch vụ bằng dữ liệu giả. Chế giễu $ http cũng không phải là một giải pháp tốt, vì khi đó bạn thực sự đang thử nghiệm hai dịch vụ trong một lần thử nghiệm, thay vì thử nghiệm đơn vị hai dịch vụ một cách riêng biệt. Vì vậy, tôi muốn lặp lại câu hỏi. Làm thế nào để bạn chuyển một dịch vụ giả sang một dịch vụ khác trong một bài kiểm tra đơn vị?
Patrick Arnesen

1
Nếu bạn lo lắng về việc dịch vụ tấn công máy chủ để lấy dữ liệu, thì $ httpBackend dành cho ( docs.angularjs.org/api/ngMock.$httpBackend ). Tôi không chắc điều gì khác sẽ là mối quan tâm trong nhà máy của dịch vụ yêu cầu chế nhạo toàn bộ dịch vụ.
dnc253

2

Một tùy chọn khác để giúp làm cho việc chế nhạo phụ thuộc trong Angular và Jasmine dễ dàng hơn là sử dụng QuickMock. Nó có thể được tìm thấy trên GitHub và cho phép bạn tạo các mô phỏng đơn giản theo cách có thể tái sử dụng. Bạn có thể sao chép nó từ GitHub thông qua liên kết bên dưới. README khá dễ hiểu, nhưng hy vọng nó có thể giúp ích cho những người khác trong tương lai.

https://github.com/tennisgent/QuickMock

describe('NotificationService', function () {
    var notificationService;

    beforeEach(function(){
        notificationService = QuickMock({
            providerName: 'NotificationService', // the provider we wish to test
            moduleName: 'QuickMockDemo',         // the module that contains our provider
            mockModules: ['QuickMockDemoMocks']  // module(s) that contains mocks for our provider's dependencies
        });
    });
    ....

Nó tự động quản lý tất cả bảng soạn sẵn được đề cập ở trên, vì vậy bạn không cần phải viết ra tất cả mã tiêm giả đó trong mọi bài kiểm tra. Hy vọng rằng sẽ giúp.


2

Ngoài câu trả lời của John Galambos : nếu bạn chỉ muốn mô phỏng các phương pháp cụ thể của một dịch vụ, bạn có thể làm như sau:

describe('Service: myService', function () {

  var mockDependency;

  beforeEach(module('myModule'));

  beforeEach(module(function ($provide, myDependencyProvider) {
      // Get an instance of the real service, then modify specific functions
      mockDependency = myDependencyProvider.$get();
      mockDependency.getSomething = function() { return 'mockReturnValue'; };
      $provide.value('myDependency', mockDependency);
  });

  it('should return value from mock dependency', inject(function (myService) {
      expect(myService.useDependency()).toBe('mockReturnValue');
  }));

});

1

Nếu bộ điều khiển của bạn được viết để nhận một phụ thuộc như thế này:

app.controller("SomeController", ["$scope", "someDependency", function ($scope, someDependency) {
    someDependency.someFunction();
}]);

thì bạn có thể làm giả someDependencytrong bài kiểm tra Jasmine như thế này:

describe("Some Controller", function () {

    beforeEach(module("app"));


    it("should call someMethod on someDependency", inject(function ($rootScope, $controller) {
        // make a fake SomeDependency object
        var someDependency = {
            someFunction: function () { }
        };

        spyOn(someDependency, "someFunction");

        // this instantiates SomeController, using the passed in object to resolve dependencies
        controller("SomeController", { $scope: scope, someDependency: someDependency });

        expect(someDependency.someFunction).toHaveBeenCalled();
    }));
});

9
Câu hỏi là về các dịch vụ, không được khởi tạo trong bộ thử nghiệm với một cuộc gọi đến bất kỳ dịch vụ tương đương nào dưới dạng $ controller. Nói cách khác, người ta không gọi $ service () trong một khối beforeEach, truyền vào các phần phụ thuộc.
Morris Singer

1

Gần đây tôi đã phát hành ngImprovedTesting sẽ giúp kiểm tra thử trong AngularJS dễ dàng hơn.

Để kiểm tra 'myService' (từ mô-đun "myApp") với các phụ thuộc fooService và barService của nó, bạn có thể thực hiện như sau trong thử nghiệm Jasmine của mình:

beforeEach(ModuleBuilder
    .forModule('myApp')
    .serviceWithMocksFor('myService', 'fooService', 'barService')
    .build());

Để biết thêm thông tin về ngImprovedT Testing, hãy xem bài đăng trên blog giới thiệu của nó: http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/


1
Tại sao điều này lại bị bỏ phiếu? Tôi không hiểu giá trị của việc bỏ phiếu từ chối mà không có bình luận.
Jacob Brewer

0

Tôi biết đây là câu hỏi cũ nhưng có một cách khác dễ dàng hơn, bạn có thể tạo mô hình và vô hiệu hóa bản gốc được tiêm vào một chức năng, nó có thể được thực hiện bằng cách sử dụng spyOn trên tất cả các phương pháp. xem mã bên dưới.

var mockInjectedProvider;

    beforeEach(function () {
        module('myModule');
    });

    beforeEach(inject(function (_injected_) { 
      mockInjectedProvider  = mock(_injected_);
    });

    beforeEach(inject(function (_base_) {
        baseProvider = _base_;
    }));

    it("injectedProvider should be mocked", function () {
    mockInjectedProvider.myFunc.andReturn('testvalue');    
    var resultFromMockedProvider = baseProvider.executeMyFuncFromInjected();
        expect(resultFromMockedProvider).toEqual('testvalue');
    }); 

    //mock all service methods
    function mock(angularServiceToMock) {

     for (var i = 0; i < Object.getOwnPropertyNames(angularServiceToMock).length; i++) {
      spyOn(angularServiceToMock,Object.getOwnPropertyNames(angularServiceToMock)[i]);
     }
                return angularServiceToMock;
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.