#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()
).