用笑话对 NodeJS 流进行单元测试 - 超时问题

Unit testing NodeJS stream with jest - timeout issue

给定以下 NodeJS 转换流:

class ObjectToCSVTransform extends Transform {
  private hasSetHeaders: boolean;

  constructor() {
    super({ objectMode: true });
    this.hasSetHeaders = false;
  }

  static create(): ObjectToCSVTransform {
    return new this();
  }

  _write(
    object: Record<string, unkwnown>,
    _encoding: BufferEncoding,
    callback: TransformCallback
  ): void {
    this.push(this.generateCSV(telemetry));
    callback();
  }

  private generateCSV(object: Record<string, unkwnown>): string {
    let csv = '';

    if (!this.hasSetHeaders) {
      csv += this.createCSVHeaders(object);
      this.hasSetHeaders = true;
    }

    csv += this.createRecord(object);

    return csv;
  }

  private createCSVHeaders(object: Record<string, unkwnown>): string {
    return `${Object.keys(object)}\n`;
  }

  private createCSVRecord(object: Record<string, unkwnown>): string {
    return `${Object.values(object)}\n`;
  }
}

我实现了以下测试用例(使用 jest),以测试给定 “相同类型” 的普通对象流] 预期输出是它们的有效 CSV 表示:

describe('object to csv stream', () => {
  const items: Record<string, unknown> = [
    { foo: 1, bar: 2, baz: 3 },
    { foo: 10, bar: 20, baz: 30 },
    { foo: 100, bar: 200, baz: 300 },
  ];

  it('should transform a list of items to csv', (done) => {
    const expectedCsv = 'foo,bar,baz\n1,2,3\n10,20,30\n100,200,300\n';
    let csv = '';

    Readable.from(items)
      .pipe(ObjectToCSVTransform.create())
      .on('data', (csvResult) => {
        csv += csvResult;
        console.log(csvResult); // just for debugging purposes
      })
      .on('end', () => {
        console.log('streaming ended'); // just for debugging purposes
        expect(csv).toEqual(expectedCsv);
        done();
      });
  });
});

显然,在测试用例实施期间,我想看看我的测试用例是如何失败的:流似乎工作正常,因为它记录了每个预期的 csv 结果行和 'streaming ended' 消息也在流结束后结束,但测试执行根本没有完成。开玩笑的是,超过默认超时时间居然执行完了:

thrown: "Exceeded timeout of 5000 ms for a test. Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."

我在这里错过了什么?

请注意,我目前正在使用 jest 回调来通知异步操作何时完成。

问题是csv的返回值与期望值不匹配。实际返回值在末尾包含一个 \n 。如果您将此添加到您的 expectedCsv 字符串,您的测试将通过。

超时错误是一个转移注意力的错误。

如果您在 'end' 函数中 console.log(csv.replace(/\n/g, '\n')),您将在字符串末尾看到额外的换行符。

import { Readable, Transform, TransformCallback } from 'stream';

class ObjectToCSVTransform extends Transform {
  private hasSetHeaders: boolean;

  constructor() {
    super({ objectMode: true });
    this.hasSetHeaders = false;
  }

  static create(): ObjectToCSVTransform {
    return new this();
  }

  // eslint-disable-next-line no-underscore-dangle
  _write(
    object: Record<string, unknown>,
    _encoding: 'utf8',
    callback: TransformCallback,
  ): void {
    this.push(this.generateCSV(object));
    callback();
  }

  private generateCSV(object: Record<string, unknown>): string {
    let csv = '';

    if (!this.hasSetHeaders) {
      csv += this.createCSVHeaders(object);
      this.hasSetHeaders = true;
    }

    csv += this.createCSVRecord(object);

    return csv;
  }

  private createCSVHeaders(object: Record<string, unknown>): string {
    return `${Object.keys(object)}\n`;
  }

  private createCSVRecord(object: Record<string, unknown>): string {
    return `${Object.values(object)}\n`;
  }
}

describe('object to csv stream', () => {
  const items: Record<string, unknown>[] = [
    { foo: 1, bar: 2, baz: 3 },
    { foo: 10, bar: 20, baz: 30 },
    { foo: 100, bar: 200, baz: 300 },
  ];

  it('should transform a list of items to csv', (done) => {
    const expectedCsv = 'foo,bar,baz\n1,2,3\n10,20,30\n100,200,300\n';
    let csv = '';

    Readable.from(items)
      .pipe(ObjectToCSVTransform.create())
      .on('data', (csvResult) => {
        csv += csvResult;
        console.log(csvResult); // just for debugging purposes
      })
      .on('end', () => {
        console.log('streaming ended'); // just for debugging purposes
        console.log(csv.replace(/\n/g, '\n'));
        expect(csv).toEqual(expectedCsv);
        done();
      });
  });
});

我的测试用例中缺少一些东西,它根本没有正确处理异步,因此使 Exceeded timeout exception 被开玩笑抛出:

  • 用 try-catch 包围断言,并在断言失败时调用 done() 回调。
.on('end', () => {
  try {
    expect(csv).toEqual(expectedCsv);
    done();
  } catch (error) {
    done(error);
  }
});

有关更多信息/参考,请查看笑话文档:https://jestjs.io/docs/asynchronous#callbacks