AngularJS Jasmine 단위 테스트에서 약속을 반환하는 서비스를 어떻게 모방합니까?


152

나는 myService을 사용 myOtherService하여 원격 호출을하고 약속을 반환합니다.

angular.module('app.myService', ['app.myOtherService'])
  .factory('myService', [
    myOtherService,
    function(myOtherService) {
      function makeRemoteCall() {
        return myOtherService.makeRemoteCallReturningPromise();
      }

      return {
        makeRemoteCall: makeRemoteCall
      };      
    }
  ])

에 대한 단위 테스트를 만들려면 myService내가 조롱 할 필요 myOtherService는되도록, makeRemoteCallReturningPromise방법은 약속을 반환합니다. 이것이 내가하는 방법입니다.

describe('Testing remote call returning promise', function() {
  var myService;
  var myOtherServiceMock = {};

  beforeEach(module('app.myService'));

  // I have to inject mock when calling module(),
  // and module() should come before any inject()
  beforeEach(module(function ($provide) {
    $provide.value('myOtherService', myOtherServiceMock);
  }));

  // However, in order to properly construct my mock
  // I need $q, which can give me a promise
  beforeEach(inject(function(_myService_, $q){
    myService = _myService_;
    myOtherServiceMock = {
      makeRemoteCallReturningPromise: function() {
        var deferred = $q.defer();

        deferred.resolve('Remote call result');

        return deferred.promise;
      }    
    };
  }

  // Here the value of myOtherServiceMock is not
  // updated, and it is still {}
  it('can do remote call', inject(function() {
    myService.makeRemoteCall() // Error: makeRemoteCall() is not defined on {}
      .then(function() {
        console.log('Success');
      });    
  }));  

위에서 볼 수 있듯이 내 모의 정의는에 따라 $q로드 해야하는 에 따라 다릅니다 inject(). 또한 mock 주입은에서 module()진행되어야합니다 inject(). 그러나 일단 변경하면 모의 값이 업데이트되지 않습니다.

이를 수행하는 올바른 방법은 무엇입니까?


오류가 실제로 myService.makeRemoteCall()있습니까? 그렇다면, 문제는 함께 myService있지 않는 makeRemoteCall당신의 조롱 함께 할 수있는, 아니 아무것도 myOtherService.
dnc253

myService.myOtherService는이 시점에서 단지 빈 오브젝트이기 때문에 오류가 myService.makeRemoteCall ()에 있습니다 (값은 각도에 의해 업데이트되지 않았습니다)
Georgii Oleinikov

빈 오브젝트를 ioc 컨테이너에 추가 한 후, 스파이하는 새 오브젝트를 가리 키도록 참조 myOtherServiceMock을 변경하십시오. ioc 컨테이너의 내용은 참조가 변경됨을 반영하지 않습니다.
twDuke

답변:


175

왜 그렇게했는지 모르겠지만 일반적으로 spyOn기능 으로 수행합니다 . 이 같은:

describe('Testing remote call returning promise', function() {
  var myService;

  beforeEach(module('app.myService'));

  beforeEach(inject( function(_myService_, myOtherService, $q){
    myService = _myService_;
    spyOn(myOtherService, "makeRemoteCallReturningPromise").and.callFake(function() {
        var deferred = $q.defer();
        deferred.resolve('Remote call result');
        return deferred.promise;
    });
  }

  it('can do remote call', inject(function() {
    myService.makeRemoteCall()
      .then(function() {
        console.log('Success');
      });    
  }));

또한 당신이해야 할 것을 기억 $digest에 대한 호출을 then함수가 호출 될 수 있습니다. $ q 문서테스트 섹션을 참조하십시오 .

------편집하다------

내가하고있는 일을 자세히 살펴본 후에 코드에 문제가 있다고 생각합니다. 에서 beforeEach, 당신은 설정하는 myOtherServiceMock완전히 새로운 객체. 는 $provide이 참조를 참조하지 않습니다. 기존 참조를 업데이트하면됩니다.

beforeEach(inject( function(_myService_, $q){
    myService = _myService_;
    myOtherServiceMock.makeRemoteCallReturningPromise = function() {
        var deferred = $q.defer();
        deferred.resolve('Remote call result');
        return deferred.promise;   
    };
  }

1
어제 결과에 나타나지 않아서 날 죽였어 andCallFake ()의 아름다운 디스플레이. 감사합니다.
Priya Ranjan Singh

대신 (또는 Jasmine 2.0 이상에서) andCallFake사용할 수 있습니다 . 물론 을 호출하기 전에 정의 해야합니다 . andReturnValue(deferred.promise)and.returnValue(deferred.promise)deferredspyOn
Jordan 실행

1
$digest스코프에 액세스 할 수 없을 때이 경우 어떻게 전화 하시겠습니까?
Jim Aho

7
@JimAho 일반적으로 그냥 주입 $rootScope하고 호출 $digest합니다.
dnc253

1
이 경우 지연 사용은 불필요합니다. 당신은 사용할 수 있습니다 $q.when() codelord.net/2015/09/24/$q-dot-defer-youre-doing-it-wrong
fodma1

69

우리는 또한 스파이로 직접 재스민의 귀환 약속 이행을 ​​쓸 수 있습니다.

spyOn(myOtherService, "makeRemoteCallReturningPromise").andReturn($q.when({}));

재스민 2 :

spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));

(ccnokes 덕분에 의견에서 복사)


12
Jasmine 2.0을 사용하는 사람들에게 .andReturn ()은 .and.returnValue로 대체되었습니다. 위의 예는 다음과 같습니다. spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));나는 그것을 알아내는 반 시간을 죽였습니다.
ccnokes

13
describe('testing a method() on a service', function () {    

    var mock, service

    function init(){
         return angular.mock.inject(function ($injector,, _serviceUnderTest_) {
                mock = $injector.get('service_that_is_being_mocked');;                    
                service = __serviceUnderTest_;
            });
    }

    beforeEach(module('yourApp'));
    beforeEach(init());

    it('that has a then', function () {
       //arrange                   
        var spy= spyOn(mock, 'actionBeingCalled').and.callFake(function () {
            return {
                then: function (callback) {
                    return callback({'foo' : "bar"});
                }
            };
        });

        //act                
        var result = service.actionUnderTest(); // does cleverness

        //assert 
        expect(spy).toHaveBeenCalled();  
    });
});

1
이것이 내가 과거에 한 일입니다. "그때"를 모방 한 가짜를 돌려주는 스파이를 만드십시오
Darren Corbett

보유한 전체 테스트의 예를 제공 할 수 있습니까? 약속을 반환하는 서비스를 갖는 것과 비슷한 문제가 있지만 약속을 반환하는 호출도합니다!
Rob Paddock

안녕 Rob, 왜 모의가 다른 서비스에 대한 호출을 조롱하고 싶을 지 확실하지 않다면 그 기능을 테스트 할 때 그것을 테스트하고 싶을 것입니다. 함수 호출이 모의 호출에서 서비스가 데이터를 얻는 경우 모의 약속이 가짜 영향을받는 데이터 세트를 반환한다는 데 영향을 미칩니다.
Darren Corbett

이 경로를 시작했으며 간단한 시나리오에 적합합니다. 나는 체인을 시뮬레이션하고 체인 유지를 위해 "keep"/ "break"헬퍼를 제공하는 모의 객체를 만들었 습니다. 내 경우에는 캐시에서 항목을 조건부로 반환하거나 (지연된) 요청하는 서비스가 있습니다. 그래서 그것은 자신의 약속을 창조하고있었습니다.
Mark Nadig

이 게시물 ng-learn.org/2014/08/Testing_Promises_with_Jasmine_Provide_Spy 는 가짜 "then"의 사용법을 전체적으로 설명합니다.
Custodio

8

sinon과 같은 스터 빙 라이브러리를 사용하여 서비스를 조롱 할 수 있습니다. 그런 다음 $ q.when ()을 약속으로 반환 할 수 있습니다. 범위 오브젝트의 값이 약속 결과에서 나온 경우 scope. $ root. $ digest ()를 호출해야합니다.

var scope, controller, datacontextMock, customer;
  beforeEach(function () {
        module('app');
        inject(function ($rootScope, $controller,common, datacontext) {
            scope = $rootScope.$new();
            var $q = common.$q;
            datacontextMock = sinon.stub(datacontext);
            customer = {id:1};
           datacontextMock.customer.returns($q.when(customer));

            controller = $controller('Index', { $scope: scope });

        })
    });


    it('customer id to be 1.', function () {


            scope.$root.$digest();
            expect(controller.customer.id).toBe(1);


    });

2
$rootScope.$digest()약속을 해결하기 위해 전화 를 잃어버린 조각입니다

2

사용하여 sinon:

const mockAction = sinon.stub(MyService.prototype,'actionBeingCalled')
                     .returns(httpPromise(200));

알고있을 httpPromise수 있습니다 :

const httpPromise = (code) => new Promise((resolve, reject) =>
  (code >= 200 && code <= 299) ? resolve({ code }) : reject({ code, error:true })
);

0

솔직히 .. 모듈 대신 서비스를 모의하기 위해 인젝션에 의존하여 잘못된 방법으로 가고 있습니다. 또한 beforeEach에서 inject를 호출하는 것은 테스트 기준으로 조롱하기가 어렵 기 때문에 안티 패턴입니다.

여기 내가 어떻게 할 것입니다 ...

module(function ($provide) {
  // By using a decorator we can access $q and stub our method with a promise.
  $provide.decorator('myOtherService', function ($delegate, $q) {

    $delegate.makeRemoteCallReturningPromise = function () {
      var dfd = $q.defer();
      dfd.resolve('some value');
      return dfd.promise;
    };
  });
});

이제 서비스를 주입하면 적절하게 조롱하여 사용할 수 있습니다.


3
before 각각의 요점은 각 테스트 전에 호출된다는 것입니다. 테스트 작성 방법을 모르지만 개인적으로 단일 함수에 대해 여러 테스트를 작성하므로 이전에 호출 할 공통 기본 설정을 갖습니다. 각 테스트. 또한 안티 엔지니어링이 소프트웨어 엔지니어링과 연관되어 있기 때문에 이해되는 안티 패턴의 의미를 찾아 볼 수 있습니다.
Darren Corbett 2016 년

0

유용하고 유용한 서비스 함수를 sinon.stub (). returns ($ q.when ({}))로 찾았습니다.

this.myService = {
   myFunction: sinon.stub().returns( $q.when( {} ) )
};

this.scope = $rootScope.$new();
this.angularStubs = {
    myService: this.myService,
    $scope: this.scope
};
this.ctrl = $controller( require( 'app/bla/bla.controller' ), this.angularStubs );

제어 장치:

this.someMethod = function(someObj) {
   myService.myFunction( someObj ).then( function() {
        someObj.loaded = 'bla-bla';
   }, function() {
        // failure
   } );   
};

그리고 테스트

const obj = {
    field: 'value'
};
this.ctrl.someMethod( obj );

this.scope.$digest();

expect( this.myService.myFunction ).toHaveBeenCalled();
expect( obj.loaded ).toEqual( 'bla-bla' );

-1

코드 스 니펫 :

spyOn(myOtherService, "makeRemoteCallReturningPromise").and.callFake(function() {
    var deferred = $q.defer();
    deferred.resolve('Remote call result');
    return deferred.promise;
});

더 간결한 형태로 작성 될 수 있습니다 :

spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue(function() {
    return $q.resolve('Remote call result');
});
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.