对调用承诺返回函数的递归方法进行单元测试

Posted

技术标签:

【中文标题】对调用承诺返回函数的递归方法进行单元测试【英文标题】:Unit testing a recursive method that calls a promise-returning function 【发布时间】:2017-05-08 05:22:33 【问题描述】:

下面是我正在尝试测试的代码的简化版本:一个定期清空的简单队列。对于队列中的每个号码,都会进行 HTTP POST 以将其发送到(在本例中为虚构的)API 地址。如果 POST 成功,则将号码移出队列并考虑下一个号码。

class Queue 
    queue: Array<number>;
    processingQueue: boolean;

    constructor(public $http: angular.IHttpService) 
        this.queue = new Array<number>();
        this.processingQueue = false;
    

    startQueueProcessor() 
        this.processingQueue = false;

        setInterval(() => 
            if (!this.processingQueue)
                this.processQueue();
        , 1000);
    

    postNumber(number: number): angular.IHttpPromise<> 
        return this.$http.post('http://mydummyurl.com/givemeanumber', number);
    

    processQueue(): void 
        this.processingQueue = true; // we are currently processing a queue, so make sure setInterval does not trigger another processing run

        if (this.queue.length !== 0)  // are there any numbers to deal with?
            const item = this.queue[0]; // get the first number in the queue

            this.postNumber(item) // POST it (returns a promise)
                .then(
                () =>  // if successful...
                    this.queue.shift(); // we've dealt with this item, so remove it from the queue
                    this.processQueue(); // check the queue again (recurses until all items have been dealt with)
                ,
                () =>  // if unsuccessful...
                    this.processingQueue = false; // something went wrong, so stop
                
                );
         else 
            this.processingQueue = false; // queue is empty, stop processing
        
    

    enqueue(number: number): void  // add a number to the queue
        this.queue.push(number);
    

我希望创建的测试将检查,在将三个项目添加到队列后,对 processQueue() 的一次调用将清空它。

类似这样的东西(在 Jasmine 中模拟):

describe('queueing', () => 
    var queueService;

    beforeEach(inject((_$httpBackend_, $injector) => 
        httpBackend = _$httpBackend_;
        httpBackend.whenPOST().respond(200);

        queueService = $injector.get('queue');
    ));

    it('should clear queue completely when processQueue() is called once', () => 

        queueService.enqueue(1);
        queueService.enqueue(2);
        queueService.enqueue(3);

        expect(queueService.queue.length).toBe(3);

        queueService.processQueue();

        // somehow wait for processQueue() to complete

        expect(queueService.queue.length).toBe(0);
    );
);

我的问题是第二个expect() 总是失败并显示消息Expected 3 to be 0。我认为这是由于没有等待 postNumber() 返回的承诺,因此在调用第二个 expect() 时队列不为空。

如何等待processQueue() 完成,然后再尝试断言队列已被清空?

【问题讨论】:

【参考方案1】:

您应该修改 processQueue 以返回 Promise。最简单的方法是使用 async 关键字标记方法,然后使用 await

async processQueue(): Promise<void> 

    this.processingQueue = true; // we are currently processing a queue, so make sure setInterval does not trigger another processing run

    if (this.queue.length !== 0) 
     // are there any numbers to deal with?
        const item = this.queue[0]; // get the first number in the queue

        try
        
            await this.postNumber(item); // POST it (returns a promise)
            // if successful...
            this.queue.shift(); // we've dealt with this item, so remove it from the queue
            this.processQueue(); // check the queue again (recurses until all items have been dealt with)
        
        catch
        
                this.processingQueue = false; // something went wrong, so stop
        
     
    else 
    
        this.processingQueue = false; // queue is empty, stop processing
    

然后在你的测试中:

it('should clear queue completely when processQueue() is called once', async (done) => 

    queueService.enqueue(1);
    queueService.enqueue(2);
    queueService.enqueue(3);

    expect(queueService.queue.length).toBe(3);

    await queueService.processQueue();

    // somehow wait for processQueue() to complete

    expect(queueService.queue.length).toBe(0);

    done();
);    

希望这会有所帮助。

【讨论】:

感谢@Amid 的建议。我已经尝试过了,但出现以下错误:Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. 第一个断言正确关闭,但随后await 部分似乎没有返回任何内容并且它超时。 我可以看到两个原因。 http.post 无法到达主机并且操作超时。或者(这更有可能)angular.IHttpPromise 与打字稿使用的通用 A+ 承诺不兼容。转换示例见以下两个链接:codeproject.com/articles/1019920/…和***.com/questions/24611078/…

以上是关于对调用承诺返回函数的递归方法进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章

如何模拟在 AngularJS Jasmine 单元测试中返回承诺的服务?

无法通过递归调用node.js中的函数从promises获得响应

在 node.js 中承诺一个递归函数

从函数返回承诺值[重复]

C++ 承诺/未来:从函数返回哪个?

异步函数返回undefined