AngularJS 서비스에 모의 객체 삽입


114

AngularJS 서비스가 작성되었으며 단위 테스트를 원합니다.

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

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

    return this;
});

내 app.js 파일에는 다음이 등록되어 있습니다.

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

DI가 다음과 같이 작동하는지 테스트 할 수 있습니다.

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();
    });
});

이것은 서비스가 DI 프레임 워크에 의해 생성 될 수 있음을 입증했지만, 다음에 서비스를 단위 테스트하고 싶습니다. 즉, 삽입 된 객체를 조롱하는 것을 의미합니다.

어떻게해야합니까?

모의 객체를 모듈에 넣어 보았습니다.

beforeEach(module(mockNavigationService));

서비스 정의를 다음과 같이 다시 작성합니다.

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); })

그러나 후자는 DI에 의해 생성되는 서비스를 모두 중단하는 것 같습니다.

아무도 내 단위 테스트를 위해 주입 된 서비스를 모의 할 수있는 방법을 알고 있습니까?

감사

데이비드


다른 질문에 대한 답변을 살펴볼 수 있습니다. 도움이 되었기를 바랍니다.
remigio 2013

답변:


183

를 사용하여 서비스에 모의를 삽입 할 수 있습니다 $provide.

getSomething이라는 메소드가있는 종속성이있는 다음 서비스가있는 경우 :

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

다음과 같이 myDependency의 모의 버전을 삽입 할 수 있습니다.

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');
  }));

});

호출로 인해 $provide.value실제로 어디에도 myDependency를 명시 적으로 주입 할 필요가 없습니다. myService를 삽입하는 동안 내부에서 발생합니다. 여기서 mockDependency를 설정할 때 스파이처럼 쉽게 할 수 있습니다.

훌륭한 비디오 링크 를 제공 해준 loyalBrown 에게 감사드립니다 .


13
좋은 작품,하지만 세부 사항을주의하십시오 beforeEach(module('myModule'));호출이 HAS 전과 올 beforeEach(function () { MOCKING })전화, 그렇지 않으면 망신 시켰 실제 서비스로 덮어 얻을 것이다!
Nikos Paraskevopoulos 2014

1
서비스가 아닌 동일한 방식으로 지속적으로 조롱하는 방법이 있습니까?
Artem

5
Nikos의 의견과 유사하게, 다른 방법으로 $provide사용하기 전에 모든 호출을해야합니다 $injector. 다음과 같은 오류 메시지가 표시됩니다.Injector already created, can not register a module!
Providencemac

7
당신의 모의가 $ q를 필요로한다면? 그러면 mock을 등록하기 위해 module ()을 호출하기 전에 $ q를 mock에 삽입 할 수 없습니다. 이견있는 사람?
Jake

4
당신이 coffeescript를 사용하고 있고 당신이 본다면, 다음 줄에 Error: [ng:areq] Argument 'fn' is not a function, got Object를 넣어야합니다 . 암시 적으로 반환 하면 해당 오류가 발생했습니다. return$provide.value(...)$provide.value(...)
yndolok

4

제가보기에는 서비스 자체를 조롱 할 필요가 없습니다. 서비스의 기능을 조롱하기 만하면됩니다. 이렇게하면 앱 전체 에서처럼 실제 서비스를 각도 주입 할 수 있습니다. 그런 다음 Jasmine의 spyOn기능을 사용하여 필요에 따라 서비스의 기능을 모의합니다 .

이제 서비스 자체가 함수이고 spyOn함께 사용할 수있는 객체가 아니라면 다른 방법이 있습니다. 이 작업을 수행해야했고 저에게 잘 맞는 것을 찾았습니다. 함수 인 Angular 서비스를 어떻게 모의합니까?를 참조하십시오 .


3
나는 이것이 질문에 답하지 않는다고 생각합니다. 조롱되는 서비스의 팩토리가 데이터를 위해 서버에 접속하는 것과 같이 사소하지 않은 일을하면 어떨까요? 그것은 그것을 조롱하고 싶은 좋은 이유가 될 것입니다. 서버 호출을 피하고 대신 가짜 데이터를 사용하여 모의 버전의 서비스를 만들고 싶습니다. $ http를 조롱하는 것도 좋은 해결책이 아닙니다. 두 서비스를 분리하여 단위 테스트하는 대신 실제로 한 테스트에서 두 서비스를 테스트하고 있기 때문입니다. 그래서 나는 질문을 다시 반복하고 싶습니다. 단위 테스트에서 모의 ​​서비스를 다른 서비스에 어떻게 전달합니까?
Patrick Arnesen

1
서비스가 데이터를 위해 서버에 도달하는 것이 걱정된다면 $ httpBackend의 용도입니다 ( docs.angularjs.org/api/ngMock.$httpBackend ). 전체 서비스를 조롱해야하는 서비스 공장에서 다른 문제가 무엇인지 잘 모르겠습니다.
dnc253 2013-06-13

2

Angular 및 Jasmine에서 종속성을 모의하는 데 도움이되는 또 다른 옵션은 QuickMock을 사용하는 것입니다. GitHub에서 찾을 수 있으며 재사용 가능한 방식으로 간단한 모의를 만들 수 있습니다. 아래 링크를 통해 GitHub에서 복제 할 수 있습니다. README는 꽤 자명하지만, 앞으로 다른 사람들에게 도움이되기를 바랍니다.

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
        });
    });
    ....

위에서 언급 한 모든 상용구를 자동으로 관리하므로 모든 테스트에서 모의 ​​주입 코드를 모두 작성할 필요가 없습니다. 도움이되기를 바랍니다.


2

John Galambos의 답변 외에도 서비스의 특정 방법을 조롱하고 싶다면 다음과 같이 할 수 있습니다.

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

컨트롤러가 다음과 같은 종속성을 갖도록 작성된 경우 :

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

그런 다음 다음 someDependency과 같이 Jasmine 테스트에서 가짜 를 만들 수 있습니다 .

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
문제는 $ controller와 동일한 서비스를 호출하여 테스트 스위트에서 인스턴스화되지 않는 서비스에 관한 것입니다. 즉, beforeEach 블록에서 $ service ()를 호출하지 않고 종속성을 전달합니다.
Morris Singer

1

나는 최근에 AngularJS에서 모의 ​​테스트를 더 쉽게 만들어 줄 ngImprovedTesting을 출시했습니다.

fooService 및 barService 종속성이 모의 된 'myService'( "myApp"모듈에서)를 테스트하려면 Jasmine 테스트에서 다음을 수행하면됩니다.

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

ngImprovedTesting에 대한 자세한 내용은 소개 블로그 게시물을 확인하십시오. http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/


1
왜 이것이 반대표를 받았습니까? 나는 코멘트없이 하향 투표의 가치를 이해하지 못한다.
Jacob Brewer

0

나는 이것이 오래된 질문이라는 것을 알고 있지만 또 다른 쉬운 방법이 있습니다. 모의를 만들고 하나의 기능에 주입 된 원본을 비활성화 할 수 있습니다. 모든 방법에서 spyOn을 사용하여 수행 할 수 있습니다. 아래 코드를 참조하십시오.

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;
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.