简体   繁体   English

Jasmine单元测试不等待承诺解决

[英]Jasmine unit tests not waiting for promise resolution

I have an angular service that has an async dependency like this 我有一个像这样的异步依赖的角度服务

(function() {
    angular
        .module('app')
        .factory('myService', ['$q', 'asyncService',

    function($q, asyncService) {

        var myData = null;

        return {
            initialize: initialize,
        };

        function initialize(loanId){
            return asyncService.getData(id)
                .then(function(data){
                    console.log("got the data!");
                    myData = data;
            });
        }
    }]);
})();

I want to unit test the initialize function and I'm trying in jasmine like this: 我想单元测试initialize函数,我正在尝试像这样的茉莉:

describe("Rate Structure Lookup Service", function() {

    var $q;
    var $rootScope;
    var getDataDeferred;
    var mockAsyncService;
    var service;

    beforeEach(function(){
        module('app');

        module(function ($provide) {
            $provide.value('asyncService', mockAsyncService);
        });

        inject(function(_$q_, _$rootScope_, myService) {
            $q = _$q_;
            $rootScope = _$rootScope_;
            service = myService;
        });

        getDataDeferred = $q.defer();

        mockAsyncService = {
            getData: jasmine.createSpy('getData').and.returnValue(getDataDeferred.promise)
        };
    });

    describe("Lookup Data", function(){
        var data;

        beforeEach(function(){
            testData = [{
                recordId: 2,
                effectiveDate: moment("1/1/2015", "l")
            },{
                recordId: 1,
                effectiveDate: moment("1/1/2014", "l")
            }];
        });

        it("should get data", function(){
            getDataDeferred.resolve(testData);

            service.initialize(1234).then(function(){
                console.log("I've been resolved!");
                expect(mockAsyncService.getData).toHaveBeenCalledWith(1234);
            });

            $rootScope.$apply();
        });
    });
});

None of the console messages appear and the test seems to just fly on through without the promises ever being resolved. 没有任何控制台消息出现,测试似乎只是在没有承诺得到解决的情况下继续进行。 I though that the $rootScope.$apply() would do it but seems not to. 我虽然$rootScope.$apply()会这样做,但似乎没有。

UPDATE UPDATE

@estus was right that $rootScope.$appy() is sufficient to trigger resolution of all the promises. @estus是对的$rootScope.$appy()足以触发所有承诺的解决。 It seems that the issue was in my mocking of the asyncService. 似乎问题出在我对asyncService的模拟中。 I changed it from 我把它改成了

mockAsyncService = {
    getData: jasmine.createSpy('getData').and.returnValue(getDataDeferred.promise)
};

to

mockAsyncService = {
    getData: jasmine.createSpy('getData').and.callFake(
        function(id){
            return $q.when(testData);
    })
};

and I set testData to what I need to for the tests rather than calling getDataDeferred.resolve(testData) . 我将testData设置为我需要的测试,而不是调用getDataDeferred.resolve(testData) Prior to this change, the mockAsyncService was being injected but the promise for getDataDeferred was never being resolved. 这种变化之前,mockAsyncService被注射但是对于承诺getDataDeferred从来没有得到解决。

I don't know if this is something in the order of injection in the beforeEach or what. 我不知道这是否是在beforeEach中注入的beforeEach或什么。 Even more curious was that is has to be a callFake . 更奇怪的是,这必须是一个callFake Using .and.returnValue($q.when(testData)) still blocks promise resolution. 使用.and.returnValue($q.when(testData))仍会阻止承诺解析。

Angular promises are synchronous during tests, $rootScope.$apply() is enough to make them settled at the end of the spec. 角度承诺在测试期间是同步的, $rootScope.$apply()足以使它们在规范结束时结算。

Unless asyncService.getData returns a real promise instead of $q promise (and it doesn't in this case), asynchronicity is not a problem in Jasmine. 除非asyncService.getData返回一个真正的promise而不是$q promise(在这种情况下它不会),asynchronicity在Jasmine中不是问题。

Jasmine promise matchers library is exceptionally good for testing Angular promises. Jasmine promise matchers库非常适合测试Angular的承诺。 Besides the obvious lack of verbosity, it provides valuable feedback in such cases. 除了明显缺乏冗长之外,它还在这种情况下提供了有价值的反馈。 While this 虽然这个

rejectedPromise.then((result) => {
  expect(result).toBe(true);
});

spec will pass when it shouldn't, this 规范将在不应该的时候通过,这个

expect(pendingPromise).toBeResolved();
expect(rejectedPromise).toBeResolvedWith(true);

will fail with meaningful message. 将失败的有意义的消息。

The actual problem with the testing code is precedence in beforeEach . 测试代码的实际问题在beforeEach是优先的。 Angular bootstrapping process isn't synchronous. 角度自举过程不同步。

getDataDeferred = $q.defer() should be put into inject block, otherwise it will be executed before the module was bootstrapped and $q was injected. getDataDeferred = $q.defer()应该放入inject块,否则它将在模块被引导并且注入$q之前执行。 The same concerns mockAsyncService that uses getDataDeferred.promise . 同样的担忧mockAsyncService使用getDataDeferred.promise

In best-case scenario the code will throw an error because defer method was called on undefined . 在最好的情况下,代码会抛出错误,因为在undefined调用了defer方法。 And in worst-case scenario (which is the reason why spec properties like this.$q are preferable to local suite variables) $q belongs to an injector from the previous spec, thus $rootScope.$apply() will have no effect here. 在最坏的情况下(这就是为什么这样的规范属性this.$q比本地套件变量更可取的原因) $q属于前一个规范的一个注入器,因此$rootScope.$apply()在这里没有效果。

You need to pass the optional done parameter to the callback function in your it block. 您需要将可选的done参数传递给it块中的回调函数。 Otherwise jasmine has no way of knowing you're testing an async function -- async functions return immediately. 否则jasmine无法知道你正在测试异步函数 - 异步函数会立即返回。

Here's the refactor: 这是重构:

it("should get data", function(done){

    service.initialize(1234).then(function(){
        console.log("I've been resolved!");
        expect(mockAsyncService.getData).toHaveBeenCalledWith(1234);
        done();
    });  
});

Here are some (flaky, back-of-the-beer-mat) pointers. 这里有一些(片状,背面的啤酒垫)指针。 Unfortunately I have no way of knowing if they are actual mistakes or whether they are "typos" because you "simplified" the code. 不幸的是,我无法知道它们是否是实际错误,或者它们是否是“拼写错误”,因为您“简化”了代码。

First of all, there's no reason not to provide the asyncService as a service, and inline. 首先,没有理由不将asyncService作为服务和内联提供。 Try this: 试试这个:

$provide.service('asyncService', function() {
    // asyncService implementation
});

Also, I don't believe that this dependency injection would work. 另外,我不相信这种依赖注入会起作用。

inject(function(_$q_, _$rootScope_, myService) {
    $q = _$q_;
    $rootScope = _$rootScope_;
    service = myService;
});

Because the DI container doesn't know about myServiceProvider. 因为DI容器不知道myServiceProvider。 You could try this instead: 你可以试试这个:

inject(function(_$q_, _$rootScope_, _asyncService_) {
    $q = _$q_;
    $rootScope = _$rootScope_;
    service = _asyncService_;
});

Which would work because you called $provide earlier with 'asyncService' as a parameter. 哪个会起作用,因为您之前使用'asyncService'作为参数调用$ provide。

Also, you're not using the $promise api properly. 另外,你没有正确使用$ promise api。 You're not returning a resolve()'d promise to the .then() in your unit test. 你没有在单元测试中向.then()返回resolve()'s promise。 Try using an alternate implementation for asyncService similar to this: 尝试使用asyncService的替代实现,类似于:

$provide.service('asyncService', function() {
    this.getData = function() {
        return $q(function(resolve, reject) {
         resolve('Promise resolved');
        });
    }
});

Check the docs for $q 查看$ q文档

You could spy on this in your unit test like this. 你可以在你的单元测试中监视这个。 There's no reason to call the spy in your beforeEach() function. 没有理由在beforeEach()函数中调用间谍。

jasmine.spyOn(service, 'getData').and.callThrough();

Your expect() looks good. 你的期望()看起来不错。

Let me know if any of this helps you. 如果有任何这些对你有帮助,请告诉我。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM