如何使用nock记录对文件的请求和响应,并使用它在mocha验收测试中播放?

时间:2021-04-12 20:02:16

I inherited a typescript@2 project that has no tests in place.

我继承了一个没有测试的typescript @ 2项目。

It's basically a cli task runner, and a task requests an external api multiple time in order to create a file. As a a first failsafe, I want to set up acceptance tests.

它基本上是一个cli任务运行器,并且一个任务多次请求外部api以创建文件。作为第一个故障保护,我想建立验收测试。

Therefore, I want to mock the calls to the external api and to fetch the response from a local file. How do I achieve that?

因此,我想模拟对外部api的调用并从本地文件中获取响应。我如何实现这一目标?

I've looked into nock as it appears to provide this functionality, yet how do I use it?

我已经看了nock,因为它似乎提供了这个功能,但我该如何使用它?

(I don't provide an example as I intend to answer my question myself as I just recently have been through the entire ordeal.)

(我没有提供一个例子,因为我打算自己回答我的问题,因为我刚刚经历了整个考验。)

1 个解决方案

#1


2  

I have refactored my application that all the call to the to the external api happen when a Task object executes its execute method. Such a task implements the interface ITask:

我重构了我的应用程序,当一个Task对象执行其execute方法时,所有对外部api的调用都会发生。这样的任务实现了ITask接口:

import {ReadStream} from 'fs';
export interface ITask {
    execute(): Promise<ReadStream>;
}

This allowed me to wrap a Task inside either a recorder or playback decorator. (I also don't let the execute create a file anymore but it returns the Promise of a Stream. In my normal workflow I would dump that stream to the file ystem (or upload it where ever I wanted).

这允许我将一个Task包装在录音机或播放装饰器中。 (我也不让执行创建一个文件,但它返回一个Stream的Promise。在我的正常工作流程中,我会将该流转储到文件系统(或者将其上传到我想要的地方)。

RecordDecorator:

import {writeFile} from 'fs';
import {ITask} from './ITask';
import nock = require('nock');
import mkdirp = require('mkdirp');
import {ReadStream} from 'fs';

export class TaskMockRecorder implements ITask {
    constructor(private task: ITask, private pathToFile: string) {
    }

    public async execute(): Promise <ReadStream> {
        this.setupNock();
        const stream = await this.task.execute();
        this.writeRecordFile();

        return Promise.resolve(stream);
    }

    private writeRecordFile() {
        const nockCallObjects = nock.recorder.play();

        mkdirp(this.pathToFile, async() => {
            writeFile(`${this.pathToFile}`, JSON.stringify(nockCallObjects, null, 4));
        });
    }

    private setupNock() {
        nock.recorder.rec({
            dont_print: true,
            enable_reqheaders_recording: true,
            output_objects: true,
        });
    }
}

PlayBackDecorator

import {ITask} from './ITask';
import {ReadStream} from 'fs';
import {Partner} from '../Types';
import nock = require('nock');

export class TaskMockPlaybackDecorator implements ITask {
    constructor(private task: ITask, private pathToFile: string) {
    }

    public async execute(): Promise<ReadStream> {
        nock.load(this.pathToFile);
        nock.recorder.play();

        return this.task.execute();
    }
}

Decorating the task

I furthermore introduced the custom type MockMode:

我进一步介绍了自定义类型MockMode:

export type MockeMode = 'recording'|'playback'|'none';

which I then can inject into my appRunner function:

然后我可以注入我的appRunner函数:

export async function appRun(config: IConfig, mockMode: MockeMode): Promise<ReadStream> {
    let task: ITask;

    task = new MyTask(config);

    const pathToFile = `tapes/${config.taskName}/tape.json`;
    switch (mockMode) {
        case 'playback':
            console.warn('playback mode!');
            task = new TaskMockPlaybackDecorator(task, path);
            break;
        case 'recording':
            console.warn('recording mode!');
            task = new TaskMockRecorder(task, path);
            break;
        default:
            console.log('normal mode');
    }

    const csvStream = await task.execute();

    return Promise.resolve(csvStream);
}

Implementing the acceptance test:

I now had to add reference files and set up the mocha test that compares both the generated stream from a playback run with the reference file:

我现在必须添加参考文件并设置mocha测试,将来自回放运行的生成流与参考文件进行比较:

import nock = require('nock');
import {appRun} from '../../src/core/task/taskRunner';
import {createReadStream} from 'fs';
import {brands} from '../../src/config/BrandConfig';
import isEqual = require('lodash/isEqual');
const streamEqual = require('stream-equal');

describe('myTask', () => {
    const myConfig = { // myConfig // }
    const referencePath = `references/${myConfig.taskName}.csv`;
    it(`generates csv that matches ${referencePath}`, async() => {
        nock.load(`tapes/${config}.taskName}/tape.json`);
        nock.recorder.play();

        return new Promise(async(resolve, reject) => {
            const actual = await appRun(myConfig, 'playback');
            const expected = createReadStream(referencePath);
            streamEqual(actual, expected, (err: any, isEqual: boolean) => {
                if (err) {
                    reject(err);
                }
                if (isEqual) {                          
                    resolve('equals');
                    return;
                }

                reject('not equals');               
            });
        });
    });
});

Depending on the size of the taped json request/respones one might need to increase the run size via timeout, as the default is 2 seconds and these kind of test might run slower.

根据录制的json请求/响应的大小,可能需要通过超时增加运行大小,因为默认值为2秒,并且这些类型的测试可能运行得更慢。

mocha --recursive dist/tests -t 10000

This approach also makes it possible to easily update the tapes, one can just pass the mockMode parameter from as an argument and it will update the tape.json.

这种方法也可以轻松更新磁带,只需将mockMode参数作为参数传递,它就会更新tape.json。

Downside is that the tape.json might be huge depending on the amount of traffic, yet this was intentional as as a first step I wanted to be sure that my application behaves the same on any changes to its codebase.

缺点是tape.json可能是巨大的,具体取决于流量,但这是故意的,因为我希望确保我的应用程序在其代码库的任何更改上的行为相同。

#1


2  

I have refactored my application that all the call to the to the external api happen when a Task object executes its execute method. Such a task implements the interface ITask:

我重构了我的应用程序,当一个Task对象执行其execute方法时,所有对外部api的调用都会发生。这样的任务实现了ITask接口:

import {ReadStream} from 'fs';
export interface ITask {
    execute(): Promise<ReadStream>;
}

This allowed me to wrap a Task inside either a recorder or playback decorator. (I also don't let the execute create a file anymore but it returns the Promise of a Stream. In my normal workflow I would dump that stream to the file ystem (or upload it where ever I wanted).

这允许我将一个Task包装在录音机或播放装饰器中。 (我也不让执行创建一个文件,但它返回一个Stream的Promise。在我的正常工作流程中,我会将该流转储到文件系统(或者将其上传到我想要的地方)。

RecordDecorator:

import {writeFile} from 'fs';
import {ITask} from './ITask';
import nock = require('nock');
import mkdirp = require('mkdirp');
import {ReadStream} from 'fs';

export class TaskMockRecorder implements ITask {
    constructor(private task: ITask, private pathToFile: string) {
    }

    public async execute(): Promise <ReadStream> {
        this.setupNock();
        const stream = await this.task.execute();
        this.writeRecordFile();

        return Promise.resolve(stream);
    }

    private writeRecordFile() {
        const nockCallObjects = nock.recorder.play();

        mkdirp(this.pathToFile, async() => {
            writeFile(`${this.pathToFile}`, JSON.stringify(nockCallObjects, null, 4));
        });
    }

    private setupNock() {
        nock.recorder.rec({
            dont_print: true,
            enable_reqheaders_recording: true,
            output_objects: true,
        });
    }
}

PlayBackDecorator

import {ITask} from './ITask';
import {ReadStream} from 'fs';
import {Partner} from '../Types';
import nock = require('nock');

export class TaskMockPlaybackDecorator implements ITask {
    constructor(private task: ITask, private pathToFile: string) {
    }

    public async execute(): Promise<ReadStream> {
        nock.load(this.pathToFile);
        nock.recorder.play();

        return this.task.execute();
    }
}

Decorating the task

I furthermore introduced the custom type MockMode:

我进一步介绍了自定义类型MockMode:

export type MockeMode = 'recording'|'playback'|'none';

which I then can inject into my appRunner function:

然后我可以注入我的appRunner函数:

export async function appRun(config: IConfig, mockMode: MockeMode): Promise<ReadStream> {
    let task: ITask;

    task = new MyTask(config);

    const pathToFile = `tapes/${config.taskName}/tape.json`;
    switch (mockMode) {
        case 'playback':
            console.warn('playback mode!');
            task = new TaskMockPlaybackDecorator(task, path);
            break;
        case 'recording':
            console.warn('recording mode!');
            task = new TaskMockRecorder(task, path);
            break;
        default:
            console.log('normal mode');
    }

    const csvStream = await task.execute();

    return Promise.resolve(csvStream);
}

Implementing the acceptance test:

I now had to add reference files and set up the mocha test that compares both the generated stream from a playback run with the reference file:

我现在必须添加参考文件并设置mocha测试,将来自回放运行的生成流与参考文件进行比较:

import nock = require('nock');
import {appRun} from '../../src/core/task/taskRunner';
import {createReadStream} from 'fs';
import {brands} from '../../src/config/BrandConfig';
import isEqual = require('lodash/isEqual');
const streamEqual = require('stream-equal');

describe('myTask', () => {
    const myConfig = { // myConfig // }
    const referencePath = `references/${myConfig.taskName}.csv`;
    it(`generates csv that matches ${referencePath}`, async() => {
        nock.load(`tapes/${config}.taskName}/tape.json`);
        nock.recorder.play();

        return new Promise(async(resolve, reject) => {
            const actual = await appRun(myConfig, 'playback');
            const expected = createReadStream(referencePath);
            streamEqual(actual, expected, (err: any, isEqual: boolean) => {
                if (err) {
                    reject(err);
                }
                if (isEqual) {                          
                    resolve('equals');
                    return;
                }

                reject('not equals');               
            });
        });
    });
});

Depending on the size of the taped json request/respones one might need to increase the run size via timeout, as the default is 2 seconds and these kind of test might run slower.

根据录制的json请求/响应的大小,可能需要通过超时增加运行大小,因为默认值为2秒,并且这些类型的测试可能运行得更慢。

mocha --recursive dist/tests -t 10000

This approach also makes it possible to easily update the tapes, one can just pass the mockMode parameter from as an argument and it will update the tape.json.

这种方法也可以轻松更新磁带,只需将mockMode参数作为参数传递,它就会更新tape.json。

Downside is that the tape.json might be huge depending on the amount of traffic, yet this was intentional as as a first step I wanted to be sure that my application behaves the same on any changes to its codebase.

缺点是tape.json可能是巨大的,具体取决于流量,但这是故意的,因为我希望确保我的应用程序在其代码库的任何更改上的行为相同。