Как использовать nock для записи запросов и ответов на файлы и использовать их для воспроизведения в приемочном тесте мокко?

Я унаследовал проект typescript@2, в котором нет тестов.

По сути, это средство запуска задач cli, и задача несколько раз запрашивает внешний API для создания файла. В качестве первого отказа я хочу настроить приемочные тесты.

Поэтому я хочу издеваться над вызовами внешнего API и получать ответ из локального файла. Как мне этого добиться?

Я изучил nock, поскольку он предоставляет эту функциональность, но как мне ее использовать?

(Я не привожу пример, так как собираюсь сам ответить на свой вопрос, так как совсем недавно прошел через все испытания.)


person k0pernikus    schedule 04.12.2016    source источник


Ответы (1)


Я реорганизовал свое приложение, чтобы все вызовы внешнего API происходили, когда объект Task выполняет свой метод execute. Такая задача реализует интерфейс ITask:

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

Это позволило мне обернуть задачу либо внутри записывающего устройства, либо в декораторе воспроизведения. (Я также больше не позволяю execute создавать файл, но он возвращает Promise of a Stream. В моем обычном рабочем процессе я бы выгружал этот поток в файловую систему (или загружал его куда угодно).

Рекорддекоратор:

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();
    }
}

Оформление задачи

Кроме того, я представил пользовательский тип MockMode:

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

который я затем могу внедрить в свою функцию 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);
}

Проведение приемочного испытания:

Теперь мне нужно было добавить эталонные файлы и настроить тест 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');               
            });
        });
    });
});

В зависимости от размера записанного на пленку запроса/ответа json может потребоваться увеличить размер запуска с помощью timeout, так как по умолчанию это 2 секунды, и такие тесты могут выполняться медленнее.

mocha --recursive dist/tests -t 10000

Этот подход также позволяет легко обновлять ленты, можно просто передать параметр mockMode в качестве аргумента, и он обновит файл tape.json.

Недостатком является то, что tape.json может быть огромным в зависимости от объема трафика, но это было преднамеренно, поскольку в качестве первого шага я хотел убедиться, что мое приложение ведет себя одинаково при любых изменениях в его кодовой базе.

person k0pernikus    schedule 04.12.2016