您可以让Supertest等待Express处理程序完成执行吗?

时间:2018-07-03 14:15:29

标签: node.js unit-testing express supertest

我使用Supertest测试我的Express应用程序,但是当我希望处理程序在发送请求后 进行异步处理时,我遇到了挑战。以下面的代码为例:

const request = require('supertest');
const express = require('express');

const app = express();

app.get('/user', async (req, res) => {
  res.status(200).json({ success: true });
  await someAsyncTaskThatHappensAfterTheResponse();
});

describe('A Simple Test', () => {
  it('should get a valid response', () => {
    return request(app)
      .get('/user')
      .expect(200)
      .then(response => {
          // Test stuff here.
      });
  });
});

如果someAsyncTaskThatHappensAfterTheResponse()调用引发错误,则此处的测试会受到竞争条件的影响,在该条件下,基于该错误它可能会失败,也可能不会失败。除了错误处理外,如果在设置响应后发生副作用,也很难检查副作用。假设您要在发送响应后触发数据库更新。您无法从测试中得知何时应该期望更新已完全完成。有什么方法可以使用Supertest等待处理程序函数完成执行吗?

2 个答案:

答案 0 :(得分:0)

要做到这一点并不容易,因为超级测试的行为就像一个客户端,并且您无权访问express中的实际req / res对象(请参见https://stackoverflow.com/a/26811414/387094)。

作为一个完整的解决方法,这对我有用。

创建一个包含回调/承诺的文件。例如,我的文件test-hack.js看起来像这样:

let callback = null
export const callbackPromise = () => new Promise((resolve) => {
  callback = resolve
})
export default function callWhenComplete () {
  if (callback) callback('hack complete')
}

所有处理完成后,调用回调callWhenComplete函数。例如,我的中间件看起来像这样。

import callWhenComplete from './test-hack'

export default function middlewareIpnMyo () {
  return async function route (req, res, next) {
    res.status(200)
    res.send()

    // async logic logic
    callWhenComplete()
  }
}

最后在您的测试中,像这样等待callbackPromise:

import { callbackPromise } from 'test-hack'

  describe('POST /someHack', () => {
    it.only('should handle a post request', async () => {

      const response = await request
        .post('/someHack')
        .send({soMuch: 'hackery'})
        .expect(200)

      const result = await callbackPromise()

      // anything below this is executed after callWhenComplete() is 
      // executed from the route

    })
})

答案 1 :(得分:0)

受@ travis-stevens的启发,这是使用setInterval的稍微不同的解决方案,因此您可以确保在进行超级测试呼叫之前已设置了promise。如果您想将该库用于许多测试而不会发生冲突,这还允许按id跟踪请求。

const backgroundResult = {};

export function backgroundListener(id, ms = 1000) {
  backgroundResult[id] = false;
  return new Promise(resolve => {
    // set up interval
    const interval = setInterval(isComplete, ms);
    // completion logic
    function isComplete() {
      if (false !== backgroundResult[id]) {
        resolve(backgroundResult[id]);
        delete backgroundResult[id];
        clearInterval(interval);
      }
    }
  });
}

export function backgroundComplete(id, result = true) {
  if (id in backgroundResult) {
    backgroundResult[id] = result;
  }
}

在您的supertest.request()呼叫之前进行呼叫以使听众答应(在这种情况下,使用代理)。

  it('should respond with a 200 but background error for failed async', async function() {
    const agent = supertest.agent(app);
    const trackingId = 'jds934894d34kdkd';
    const bgListener = background.backgroundListener(trackingId);

    // post something but include tracking id
    await agent
      .post('/v1/user')
      .field('testTrackingId', trackingId)
      .field('name', 'Bob Smith')
      .expect(200);

    // execute the promise which waits for the completion function to run
    const backgroundError = await bgListener;
    // should have received an error
    assert.equal(backgroundError instanceof Error, true);
  });

您的控制器应该期望跟踪ID,并在控制器后台处理结束时将其传递给完整功能。传递错误作为第二个值是稍后检查结果的一种方法,但是您可以传递false或任何您喜欢的东西。

// if background task(s) were successful, promise in test will return true
backgroundComplete(testTrackingId);

// if not successful, promise in test will return this error object
backgroundComplete(testTrackingId, new Error('Failed'));

如果任何人有任何评论或改进,将不胜感激:)