Используйте Sinon.fakeServer с обещаниями и мокко - PullRequest
0 голосов
/ 27 ноября 2018

Моя проблема заключается в следующем: я хочу протестировать метод, который загружает пакет данных в корзину AWS S3.Проблема в том, что я не хочу действительно загружать данные каждый раз, когда я тестирую, и я не хочу заботиться о учетных данных, которые находятся в env.Поэтому я хочу настроить модуль поддельного сервера Sinon, чтобы имитировать загрузку и возвращать те же результаты, что и S3.К сожалению, кажется трудным найти рабочий пример с кодом, использующим async / await.

Мой тест выглядит следующим образом:

import {skip, test, suite} from "mocha-typescript";
import Chai from "chai";
import {S3Uploader} from "./s3-uploader.class";
import Sinon from "sinon";

@suite
class S3UploaderTest {

    public server : Sinon.SinonFakeServer | undefined;

    before() {
        this.server = Sinon.fakeServer.create();
    }

    after() {
        if (this.server != null) this.server.restore();
    }

    @test
    async "should upload a file to s3 correctly"(){

        let spy = Sinon.spy();

        const uploader : S3Uploader = new S3Uploader();
        const upload = await uploader.send("HalloWelt").toBucket("onetimeupload.test").toFolder("test/hw.txt").upload();

        Chai.expect(upload).to.be.a("object");
    }

}

Внутри метода uploader.upload () я выполнил обещание из обратного вызова.Так как я могу симулировать процесс загрузки?

Редактировать: Вот код загрузки s3:

import AWS from "aws-sdk";

export class S3Uploader {
    private s3 = new AWS.S3({ accessKeyId : process.env.ACCESS_KEY_ID,  secretAccessKey : process.env.SECRET_ACCESS_KEY });
    private params = {
        Body: null || Object,
        Bucket: "",
        Key: ""
    };

    public send(stream : any) {
        this.params.Body = stream;
        return this;
    }

    public toBucket(bucket : string) {
        this.params.Bucket = bucket;
        return this;
    }

    public toFolder(path : string) {
        this.params.Key = path;
        return this;
    }

    public upload() {
        return new Promise((resolve, reject) => {

            if (process.env.ACCESS_KEY_ID == null || process.env.SECRET_ACCESS_KEY == null) {
                return reject("ERR_NO_AWS_CREDENTIALS");
            }

            this.s3.upload(this.params, (error : any, data : any) => {
                return error ? reject(error) : resolve(data);
            });
        });
    }
}

1 Ответ

0 голосов
/ 06 декабря 2018

Поддельные серверы Sinon - это то, что вы можете использовать для разработки клиента, который сам выполняет запросы, вместо оболочки вокруг существующего клиента, такого как AWS.S3, как вы делаете.В этом случае лучше просто заглушить поведение AWS.S3, а не проверять фактические запросы, которые он делает.Таким образом, вы можете избежать тестирования деталей реализации AWS.S3.

Поскольку вы используете TypeScript и сделали свой клиент s3 private, вам нужно будет внести некоторые изменения, чтобы выставитьэто к вашим тестам.В противном случае вы не сможете заглушить его методы без жалоб компилятора TS.Вы также не сможете писать утверждения с использованием объекта params по тем же причинам.

Поскольку я не использую TS регулярно, я не слишком знаком с его распространенными методами внедрения зависимостей, ноодна вещь, которую вы можете сделать, это добавить необязательные аргументы конструктора в ваш класс S3Uploader, который может перезаписывать свойства s3 и arguments по умолчанию, например:

constructor(s3, params) {
    if (s3) this.s3 = s3;
    if (params) this.params = params;
}

После чего вы можете создать заглушкуи передайте его в свой тестовый экземпляр следующим образом:

const s3 = sinon.createStubInstance(AWS.S3);
const params = { foo: 'bar' };
const uploader = new S3Uploader(s3, params);

После того, как у вас есть экземпляр заглушки, вы можете написать подтверждений , чтобы убедиться, что метод upload был названтак, как вы хотите:

sinon.assert.calledOnce(s3.upload);
sinon.assert.calledWith(s3.upload, sinon.match.same(params), sinon.match.func);

Вы также можете повлиять на поведение метода upload, используя sinon stub api .Например, чтобы заставить его потерпеть неудачу, вот так:

s3.upload.callsArgWith(1, null);

Или сделайте так, чтобы он был успешным, вот так:

const data = { whatever: 'data', you: 'want' };
s3.upload.callsArgWith(1, null, data);

Вы, вероятно, захотите совершенно отдельный тест для каждого из этих случаев,используя экземпляр before ловушку, чтобы избежать дублирования общих настроек.Тестирование на успех будет включать просто await обещание и проверку того, что его результатом являются данные.Тестирование на сбой будет включать try/catch, который гарантирует, что обещание было отклонено с соответствующей ошибкой.

Кроме того, поскольку вы, похоже, выполняете здесь реальные модульные тесты, я рекомендую протестировать каждый метод S3Uploader отдельно вместо того, чтобы называть их всех в один большой тест.Это значительно сокращает количество возможных случаев, которые вам нужно охватить, делая ваши тесты намного проще.Примерно так:

@suite
class S3UploaderTest {
    params: any; // Not sure the best way to type this.
    s3: any; // Same. Sorry, not too experienced with TS.
    uploader: S3Uploader | undefined;

    before() {
        this.params = {};
        this.s3 = sinon.createStubInstance(AWS.S3);
        this.uploader = new S3Uploader(this.s3, this.params);
    }

    @test
    "send should set Body param and return instance"() {
        const stream = "HalloWelt";
        const result = this.uploader.send(stream);
        Chai.expect(this.params.Body).to.equal(stream);
        Chai.expect(result).to.equal(this.uploader);
    }

    @test
    "toBucket should set Bucket param and return instance"() {
        const bucket = "onetimeupload.test"
        const result = this.uploader.toBucket(bucket);
        Chai.expect(this.params.Bucket).to.equal(bucket);
        Chai.expect(result).to.equal(this.uploader);
    }

    @test
    "toFolder should set Key param and return instance"() {
        const path = "onetimeupload.test"
        const result = this.uploader.toFolder(path);
        Chai.expect(this.params.Key).to.equal(path);
        Chai.expect(result).to.equal(this.uploader);
    }

    @test
    "upload should attempt upload to s3"() {
        this.uploader.upload();
        sinon.assert.calledOnce(this.s3.upload);
        sinon.assert.calledWith(
            this.s3.upload,
            sinon.match.same(this.params),
            sinon.match.func
        );
    }

    @test
    async "upload should resolve with response if successful"() {
        const data = { foo: 'bar' };
        s3.upload.callsArgWith(1, null, data);
        const result = await this.uploader.upload();
        Chai.expect(result).to.equal(data);
    }

    @test
    async "upload should reject with error if not"() {
        const error = new Error('Test Error');
        s3.upload.callsArgWith(1, error, null);
        try {
            await this.uploader.upload();
            throw new Error('Promise should have rejected.');
        } catch(err) {
            Chai.expect(err).to.equal(err);
        }
    }
}

Если бы я делал это с собственно мокко, я бы сгруппировал тесты каждого метода во вложенный блок describe.Я не уверен, поощряется ли это или даже возможно с mocha-typescript, но если это так, вы можете рассмотреть это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...