Издевательство над сложной зависимостью (доступ S3 для Papa.parse) в шутку

#node.js #amazon-web-services #unit-testing #amazon-s3 #jestjs

#node.js #amazon-веб-сервисы #модульное тестирование #amazon-s3 #jestjs

Вопрос:

Я пишу модульный тест для фрагмента кода, который загружает файл из корзины AWS S3 и обрабатывает его. Обработка выполняется Papa.parse через createReadStream. Боюсь, мне, возможно, придется издеваться над взаимодействием между файлом S3 и Papa.parse.

Мой код:

 const { reader, s3 } = require('../util');

file = await reader(
  SOURCE_BUCKET_NAME,
  SOURCE_BUCKET_PREFIX,
  FILE_PREFIX,
  FILE_SUFFIX,
);

const s3file = s3.getObject({ Bucket: SOURCE_BUCKET_NAME, Key: file.Key });

return new Promise((resolve, reject) => {
  Papa.parse(s3file.createReadStream().pipe(zlib.createGunzip()), {
    encoding: 'utf8',
    header: true,
    step: (line) => {
      const d = line.data[0];
      // handling irrelevant to the mock issue
    },
    complete: async () => {
      // more handling
    },
  });
});
  

reader() — это служебная функция, которая обертывает некоторые проверки и запрос s3 и возвращает файл, который мы хотим загрузить. s3 — это фактический объект AWS s3, экземпляр которого был создан импортированной утилитой.

В моих тестах я вообще не хочу использовать настоящий s3, поэтому я хочу издеваться как над функцией reader(), так и над объектом s3, из которых я вызываю только s3.GetObject .

Так вот как я издеваюсь над этим:

 const util = require('../util');

describe('blah', () => {
  beforeEach(() => {
    jest.mock('../util', () => jest.fn());

    const mockReadStream = jest.fn().mockImplementation(() => {
      const readable = new Readable();
      readable.push('fieldA, fieldBn');
      readable.push('value A1, value B1n');
      readable.push('value A2, value B2n');
      readable.push(null);
      return readable;
    });

    s3GetObject = jest.fn(() => ({
      createReadStream: fn(() => ({
        pipe: mockReadStream,
      })),
    }));
    util.reader = jest.fn((bucketName, bucketPrefix, filePrefix, fileSuffix) => ({
      Key: `/${filePrefix}__20201021.${fileSuffix}`,
    }));
    util.s3 = jest.fn(() => ({
      getObject: s3GetObject,
    }));
  });
});
  

Насколько я могу найти в Интернете, это должно сработать, но это не так. Модульный код загружает фактический файл из реальной корзины S3, а не мой макет.

Дело в том, что я использую тот же способ издевательства ( const {x} = require(y) и в тесте y.x = jest.fn() , и там он работает нормально. Хотя я также использовал его где-то, где он не работал, если я издевался над одним импортом, но он работал, если я издевался над вторичным импортом, от которого зависел первый импорт. Я понятия не имею, почему, но мой обходной путь сработал, поэтому я не беспокоился об этом. Но на этот раз это вообще не работает, и я действительно не хочу вторичной зависимости, потому что тогда мне пришлось бы издеваться над всем интерфейсом S3. (Интерфейс S3, который я импортирую здесь, представляет собой простую оболочку.)

Комментарии:

1. Во-первых, ваш тестируемый код не завершен.

2. Публиковать здесь весь файл не очень полезно. Это соответствующий бит, который показывает, как он вызывается. Я полагаю, я мог бы расширить немного больше, потому что есть больше функций из того же импорта, которые я пытаюсь издеваться, но они проще. Если я смогу разобраться с этим, остальные не будут проблемой.

Ответ №1:

Я сам нашел решение: ручные насмешки.

Создайте __mocks__ папку рядом с файлом, который я хочу издеваться, поместите в нее файл с тем же именем и это содержимое:

 const { Readable } = require('stream');

const mockReadStream = jest.fn().mockImplementation(() => {
  const readable = new Readable();
  readable.push('fieldA, fieldBn');
  readable.push('value A1, value B1n');
  readable.push('value A2, value B2n');
  readable.push(null);
  return readable;
});

const s3GetObject = () => ({
  createReadStream: () => ({
    pipe: mockReadStream,
  }),
});

const s3 = {
  getObject: s3GetObject,
};

const reader = async (bucketName, dirPrefix = '/', filePrefix, fileSuffix) => ({
  Key: `/${filePrefix}__20201021_2020102112345.${fileSuffix}`,
});

module.exports = {
  reader,
  s3,
};
  

Затем в файле модульного теста начните с:

 jest.mock('../../datamigrations/util');
  

Удалите весь другой издевательский код и оригинал require . Теперь jest загрузит издевательскую утилиту вместо реальной утилиты.

Основной недостаток: я не могу проверить, как часто вызывались различные методы, но для моего конкретного случая это не проблема. (Потому что я также издеваюсь над доступом к базе данных, в которую я отправляю эти данные, и я все еще могу передать этот макет a jest.fn() ).