wake-up-neo.com

Jasmin testet ein Versprechen. Dann funktioniert es

Ich versuche meine App mit Jasmine zu testen und habe folgendes Problem:
Ich werde etwas in der then-Funktion meines Versprechens berechnen. Das ist der Punkt, an dem ich meinen Code testen muss. 

Hier ist der Code meines Controllers:

  TestCtrl.$inject = ["$scope", "TestService"];
  /* ngInject */
  function TestCtrl($scope, TestService) {
    $scope.loadData = function () {
      TestService.getData().then(function (response) {
        $scope.data = response.data;
        $scope.filtered = $scope.data.filter(function(item){
          if(item.id > 1000){
            return true;
          }
          return false;
        })
      });
    }
  }

Und mein Jasmin-Testcode:

describe('TestService tests', function () {
  var $q;
  beforeEach(function () {
    module('pilot.fw.user');
  });
  beforeEach(inject(function (_$q_) {
    $q = _$q_;
  }));
  describe('UserController Tests', function () {

    beforeEach(inject(function (_$httpBackend_, $rootScope, $controller) {
      this.scope = $rootScope.$new();
      this.$rootscope = $rootScope;
      this.$httpBackend = _$httpBackend_;
      this.scope = $rootScope.$new();
      var TestServiceMock = {
        getData: function () {
          var deferred = $q.defer();
          var result = [{
            "id": 1720,
            "user": 1132
          },
            {
              "id": 720,
              "user": 132
            }, {
              "id": 1721,
              "user": 1132
            }];
          deferred.promise.data = result;
          deferred.resolve(result);
          return deferred.promise;
        }
      };
      this.controller = $controller('TestCtrl', {
        '$scope': this.scope,
        'TestService': TestServiceMock
      });
    }));

    it('test', function(){
      this.scope.loadData();
      expect(true).toBeTruthy();
    })
  });
});

Das Seltsame, was ich nicht verstehe, ist (getestet mit Konsolenprotokollen): 

  • Mein Versprechen wird erstellt und zurückgegeben
  • Meine loadData-Funktion wird aufgerufen und ruft die getData () - Funktion vom TestService auf
  • Alles in der Then-Funktion wird nicht ausgeführt, obwohl ich das Versprechen als gelöst zurücksende

Wie kann ich also den Code in der Funktionthentesten?
Danke für die Hilfe

27
Jonas Hans

die Jasmin-Methode "it" benötigt einen done-Parameter, den Sie für den asynchronen Test aufrufen können

it('Should be async', function(done) {
  someAsyncFunction().then(function(result) {
    expect(result).toBe(true);
    done();
  });
});

Fühlen Sie sich frei, um so tief zu gehen, wie Sie möchten. Rufen Sie als erledigt auf, wenn ALLES abgeschlossen ist. Jasmines voreingestelltes Timeout beträgt 5 Sekunden pro Test. Wenn also das asynchrone Zeug nicht erledigt ist, stürzt Jasmin ab. Sie können diese Einstellung in den Configs ändern oder im Terminal einstellen.

Dies ist direkt aus den Jasmin-Dokumenten und zeigt Ihnen, wie Sie das Standard-Timeout-Intervall behandeln

describe("long asynchronous specs", function() {
  var originalTimeout;
  beforeEach(function() {
    originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
  });

  it("takes a long time", function(done) {
    setTimeout(function() {
      done();
    }, 9000);
  });

  afterEach(function() {
    jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
  });
});

Ich denke, wenn es nicht innerhalb von 10 Sekunden funktioniert, haben Sie möglicherweise fehlerhafte Methoden. BESONDERS, wenn Sie mit einem lokalen Server/DB sprechen. Dieses Zeug sollte nur so lange dauern, wenn Sie HEAVY-Berechnungen durchführen oder eine externe API mit einer nicht so großartigen Internetverbindung treffen. Wenn alles lokal ist (oder abgestumpft/verspottet!), Dann ist alles über 5-10 Sekunden eine eindeutige rote Flagge.

36
Dustin Stiles

hoffe diese lösung hilft. Ein Ansatz, den ich beim Testen als nützlich erwiesen habe, ist das Nachahmen von Abhängigkeiten. Ich habe versucht zu kommentieren, was ich so viel wie möglich gemacht habe. 

var returnMock, $scope, TestServiceMock, controller;

beforeEach(module('app'));

beforeEach(inject(function($controller) {
    returnMock = {
        then: jasmine.createSpy(),
    };
    $scope = {};
    // first assumption is You are testing TestService extensively,
    // I don't care about what getData has to do to get results
    // All I care about is it gets called when I call loadData
    TestServiceMock = {
        getData: jasmine.createSpy().and.returnValue(returnMock);
    };

    controller = $controller;
}));

it('should load data when loadData function is called and result set is 
under 1000', function() {
    controller('TestCtrl', {
        $scope,
        TestServiceMock
    });
    // another assumption is your data comes back in such a format
    // perhaps in the actual code check whether data exists and proceed
    // or do some other action
    var returnedData = {
        data: [
            {
                id: 1,
                name: 'item 1',
            },
        ]
    }
    // when I execute the function/method
    $scope.loadData();
    // I expect getData to be called
    expect(TestServiceMock.getData).toHaveBeenCalled();
    // I expect then to be called and the reason is I mocked it
    expect(returnMock.then).toHaveBeenCalledWith(jasmine.any(Function));
    returnMock.then.calls.mostRecent().args[0](returnedData);
    // expect data on scope to be equal to my mocked data
    expect($scope.data).toEqual(returnedData.data);
    // don't expect any result because 1 < 1000
    expect($scope.filtered).toEqual([]);
    expect($scope.filtered.length).toEqual(0);
});

it('should load data when loadData function is called and result set is over 1000', 
     function() {
    controller('TestCtrl', {
        $scope,
        TestServiceMock
    });
    var returnedData = {
        data: [
            {
                id: 1,
                name: 'item 1',
            },
            {
                id: 1000,
                name: 'item 1000',
            },
            {
                id: 1001,
                name: 'item 1000',
            },
            {
                id: 1002,
                name: 'item 1002',
            }
        ]
    }
    $scope.loadData();
    expect(TestServiceMock.getData).toHaveBeenCalled();
    expect(returnMock.then).toHaveBeenCalledWith(jasmine.any(Function));
    returnMock.then.calls.mostRecent().args[0](returnedData);
    expect($scope.data).toEqual(returnedData.data);
    // expect a result because some entries in the mocked data have id > 1000
    expect($scope.filtered).toEqual([
        {
            id: 1001,
            name: 'item 1000',
        },
        {
            id: 1002,
            name: 'item 1002',
        }]);
    expect($scope.filtered.length).toEqual(2);
});

Offizielle Jasmine Docs erklären die meisten Konzepte ausführlich. Hoffe die lösung hilft !!!!

4
mahadjr

Lassen Sie mich Ihnen sagen, was ich für Angular 1.x- und 2.x + -Projekte mache. Verwenden Sie die Winkel-Test-Tools, um Callbacks/Nester in Ihren Async-Tests zu entfernen. In Winkel 1.x bedeutet dies die Verwendung einer Kombination aus $ q und $ rootScope. $ Apply (). In eckigen 2.x + bedeutet das so etwas wie fakeAsync.

Aus den Angular 1.x-Dokumenten

it('should simulate promise', inject(function($q, $rootScope) {
  var deferred = $q.defer();
  var promise = deferred.promise;
  var resolvedValue;

  promise.then(function(value) { resolvedValue = value; });
  expect(resolvedValue).toBeUndefined();

  // Simulate resolving of promise
  deferred.resolve(123);
  // Note that the 'then' function does not get called synchronously.
  // This is because we want the promise API to always be async, whether or not
  // it got called synchronously or asynchronously.
  expect(resolvedValue).toBeUndefined();

  // Propagate promise resolution to 'then' functions using $apply().
  $rootScope.$apply();
  expect(resolvedValue).toEqual(123);
}));

Der Nachteil ist, dass Ihr Code an eckig gebunden ist. Die Vorteile sind, dass Ihr Code flach ist und zu 2.x + portierbar ist!

Ich war ein Fan des Mokka-Testläufers, der es mir ermöglichte, Versprechen in meinen Tests zurückzugeben. Sie könnten versuchen, das in Gang zu setzen, aber es gibt auch Nachteile, zum Beispiel müssen Sie Ihren Code speziell für einen Test ändern.

2
Jacob McKay

In Bezug auf Ihren Controller sollten Sie Werte so zurückgeben.

TestCtrl.$inject = ["$scope", "TestService"];
/* ngInject */
function TestCtrl($scope, TestService) {
  $scope.loadData = function () {
    // Return this call, since it will return a new promise
    // This is what let's you do $scope.loadData.then()
    return TestService.getData().then(function (response) {
      // What you return in here will be the first argument
      // of your then method, in the tests / any env
      // Ex. return 'foo'
      // will result in .then(result => result === 'foo') //=> true
      // return one of these, i suggest the data, go SRP!
      return $scope.data = response.data;

      // I would do this stuff in a separate function, but you
      // can return 'filtered' instead if you like.
      //
      // $scope.filtered = $scope.data.filter(function(item){
      //   if(item.id > 1000){
      //     return true;
      //   }
      //   return false;
      // });
    });
  }
}

Denken Sie daran, dass ein Aufruf von NACH "dann" nichts bedeutet. Werte müssen dann "INSIDE" genannt werden. Nicht danach oder davor. Aber drinnen. Wie Tom Green und dieser arme Elch in Freddy Got Fingered.

0
Dustin Stiles

Sie sollten dies besser beobachten https://codecraft.tv/courses/angular/unit-testing/asynchronous/

Sie haben tatsächlich 3 Möglichkeiten:

1) benutze regular it:

it('test', (done) => {
   const spy = spyOn(func, 'bar').and.returnValue(Promise.resolve(true));
   spy.calls.mostRecent().returnValue.then(res => {
      ...your expect here...
      done();
   })
} );

2) benutze async in beforeEach und es:

it('test', async(() => {
    spyOn(func, 'bar').and.returnValue(Promise.resolve(true));
    fixture.whenStable().then(res => {
       ...your expect here...
    })
 } ));

3) Verwenden Sie fakeAsync, wenn Sie keine Http- oder XHR-Aufrufe haben:

it('test', fakeAsync(() => {
    spyOn(func, 'bar').and.returnValue(Promise.resolve(true));
    tick();
    ...your expect here...
 } ));
0
Yevheniy Potupa