mocha-api-integration

1.0.4 • Public • Published

mocha-integration

mocha-integration (далее MI) - расширение фреймворка тестирование mocha, направленное на работу с интергационным тестирование, в частности в CI.

Наборы тестов

Для создания набора тестов существует глобальная функция MISuite, единственным аргументом которой является другая функция, которая содержит асинхронные задачи, блок тестов и завершающие функции.

В качестве имени набора берется имя файла, в котором он находится. Поэтому желательно иметь всего один экземпляр функции MISuite в файле.

Вложенность MISuite запрещена. Если по ходу выполнения будет замечена вложенность, программы завершит свое выполнение с ошибкой.

Основные инструменты

  • MISuite - основная функция создающая набор тестов
  • asyncJob - асинхронная задача
  • main - блок с Mocha тестами
  • emerge - блок завершения
  • scope - область видимости тестов

Далее каждый инструмент будет рассмотрен отдельно.

Поведение при ошибках

MI предоставляет три режима работы:

  • ALLOW_TO_FAIL (= 0). Тесты запустятся, даже если одна из задач завершится с ошибкой.
  • INTERRUPT_SUITE (= 1). Если хотя бы одна из задач завершится с ошибкой, конкретный набор тестов не будет запущен
  • INTERRUPT_RUN (= 2). Если хотя бы одна из задач в таком наборе завершится с ошибкой, тесты не будут запущены и программы завершится с кодом 1

И два варианта поведения:

  • CONTINUE (= 0). Продолжает выполенение следующей задачи, если предыдущая завершилась с ошибкой.
  • INTERRUPT (= 1). Дальнейшие задачи не выполняются, если предыдущая завершилась с ошибкой.

По-умолчанию MI используюет режим INTERRUPT_SUITE и поведение INTERRUPT

Блок тестов

Представлен функцией main внутри MISuite. Блок тестов начинает свое выполенение только после того, как все асинхронные задачи будут выполнены и только в том случае, если режим работы асинхронных зачад позволяет это сделать.

MISuite(() => {
	main(() => {
		describe("Test suite", () => {
			//Tests here
		})
	})
})

Область видимости

Блок тестов в качестве второго аргумента принимает объект с параметром scope, который отвечает в какой области видимости целиком будет выполнен блок main. Подробнее про области видимости читайте с разделе, посвященном им.

Асинхронные задачи

Mocha не поддерживает асинхронные предзагрузки данных напрямую, вместо этого можно запускать ее с опцией delay, чтобы отложить запуск до тех пор, пока все данные не будут готовы. Выглядит это примерно следующим образом:

(async function() {
	await asyncTask();
	await bar = asyncTask2();

	describe("Test suite", () => {
		// Tests here
	})
	run();
})();

Контекст выполнения

MI вместо этого предлагает специальные блоки для асинхронной предзагрузки данных asyncJob, а так же отслеживание их статуса и различные действия в зависимости от успешности выполения. Внутри MI так же использует опцию delay для отложенного запуска mocha runner'а. Вот так выглядит та же самая структура с использованием MI:

MISuite(() => {
	asyncJob(async () => {
		await asyncTask();
	})
	asyncJob("Load bar", async (context) => {
		context.bar = await asyncTask2();
	})
	main((context) => {
		describe("Test suite", () => {
			//Tests here
		})
	})
})

Немного подробнее о том, что здесь происходит. Блок asyncJob отражает одну асинхронную задачу, например авторизация или загрузка статических данные на сервер. Если вам важен контекст выполнения, например нужно сохранить полученный токен, стоит использовать первый аругмент функции asyncJob. Этот аргумент является контекстом выполнения для всех блоков асинхронных задача, блока тестов и блоков завершающих функций. С помощью него можно сохранить переменную внутри асинхронной задачи и позже получить доступ к ней в блоке тестов.

MISuite(() => {
	asyncJob(async (context) => {
		context.bar = await Promise.resolve(5);
	})
	main((context) => {
		describe("Test suite", () => {
			it("context check", () => {
				expect(context.bar).to.be.equal(5) //true
			})
		})
	})
})

Выжно, чтобы функция, передаваевая в asyncJob была асинхронной, иначе внутри у вас не будет доступа к await.

Как вы уже могли заметить asyncJob может принимать строковый параметр (name), он нужен только для отображения ее статуса и введен для удобства. Если не передавать этот парамерт, а передать сразу функцию, в качетсве имени будет выбрано имя набора тестов (имя файла).

Тайм-аут выполнения

asyncJob так же имеет третий аргумент - config, который пока содержит только один ключ timeout. Timeout позволяет устанавливать максимально время на выполнение асинхронной задачи. По его завершению, задача будет считаться невыполеннной. По-умолчанию, время на выполнение задачи равно 2000 (2 секундам).

MISuite(() => {
	//Эта задача выполнится
	asyncJob("Timeouted", async (context) => {
		context.bar = await new Promise((rs, rj) => setTimeout(rs, 2500));
	}, { timeout: 3000 });
	//А эта завершится с ошибкой
	asyncJob("Timeouted", async (context) => {
		context.bar = await new Promise((rs, rj) => setTimeout(rs, 2500));
	});
})

Завершающие функции

Представлены функцией emerge внутри MISuite. В отличие от after в Mocha emerge выполняется не по завершению конкретного набора тестов, а только после того, как завершатся все наборы. Эти функции так же вызываются, если где-то на этапе работы было брошено необработанное исключение. Такие функции могут быть использованы например для завершения поддерживаемых соединений.

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

MISuite(() => {
	asyncJob("Connect to IMAP server", async (context) => {
		context.mail = await getImapConnection();
	})
	main(/* Some code */);
	emerge((context) => {
		context.mail.close();
	})
})

Области видимости

Области видимости - интересная особенность для тех, кто собирается использовать тесты в CI. Механизм позволяет запустать только отдельные тесты в наборах. Может быть полезно, когда CI имееть разделение тестов на уже реализованную функциональность, которая не должна меняться и на функциональность, которую только предстоит реализовать в связи с чем тесты для нее могут не пройти (allow_to_fail в GitLab CI).

Функция scope имеет два обязательных аргумента: строка с областью (областями с резделителем пробелом) и функция, которая будет выполена, если области видимости совпадают.

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

Области видимости могут быть вложенными, хоть это и не имеет особого смысла.

MISuite(() => {
	//В примере показан вариант использования областей видимости, когда у вас есть две версии API авторизации
	//Первая уже реализована и не подледит изменению
	//Вторая только в разработке
	scope("legacy", () => {
		asyncJob("Get user by v1 API", async (context) => {
			context.user = await API.getUser("v1", data);
		})
	})
	scope("feature", () => {
		asyncJob("Get user by v2 API", async (context) => {
			context.user = await API.getUser("v2", data);
		})
	})
	//Задача без области видимости
	asyncJob("Get some data from user", async (context) => {
		context.data = await user.getSomeData();
	})

	main(() => {
		describe("User tests", () => {
			//Some user tests
		})
	})

	emerge((context) => {
		context.user.logout();
	})
})

Сущность Runner

Как и Mocha, MI имеет свой Runner, который ответственнен за выполнение асинхронных задача, завершающих блоков, выборку файлов, сценариев и т.д.

Конфигурирвоание

MIRunner принимает 3 аргумента командной строки при запуски:

  • --suite - область видимости. Если аргумент не задан MI выполнит все scope блоки
  • --scenario - сценарий тестирования. Может использоваться при микросервисной арихитектуру для запуска тестов только определенного микросервиса
  • -f - Используется для сброса сценария/области к значению по-умолчанию в том случае, если сценарий или область видимости переданы в аргументах, но не найдены в конфигурации Runner.

В качестве аргумента конструктора принмает объект с определенными полями:

  • testDir - директория, в которой находятся все файлы тестов. По-умолчанию 'tests'
  • postfix - постфикс файлов тестов. По-умолчанию '.js'
  • recursive - при отсутсвии аргумента командной строки scenario, MI будет искать все файлы, которые заканчиваются на postfix в директории testDir. Все найденные файлы будут исполнены. Если recursive будет равен true - поиск по директориям будет рекурсивным (так же будут просматриваться все сложенные директории), иначе только файлы в testDir. По-умолчанию true
  • includePath - директория с файлами, которые будут подключены в global перед началом исполнения файлов тестов. Нерекурсивно находятся все файлы, оканчивающиеся на '.js' в директории и с помощью require подключаются в объект global. Имя файла/класса будет без '.js'. Например, файл ${includePath}/User.js будет доступен из global.User. По-умолчанию 'classes'
  • timeout - Длительность таймаута по-умолчанию для асинхронных задач
  • suites - Доступные области видимости. По-умолчанию []
  • scenarios - Доступные сценарии тестированию. Поле обязательно должно быть объектом. По умолчанию {}
  • scenariosFiles - Файлы тестов для каждого сценария. Объект, где ключами явзяются значения объекта scenarios

Пояснение структуры

//MI для задания сценариев использует объекты, из-за одной их особенности - вычисляемых свойств
//Вычисляемые свойства позволяют в большинстве случаев исключить конфликты имен
const scenarios = {
	AUTH: "auth",
	FILES: "files",
	OTHER: "other"
}
const scenariosFiles = {
	[scenarios.AUTH]: [
		'path/to/auth/test1',
		'path/to/auth/test2',
	]
	[scenarios.USER]: [
		'path/to/user/test1',
	]
	[scenarios.OTHER]: [
		'path/to/other/test1',
		'path/to/other2/test1',
		'path/to/other2/test2',
	]
}
  • follow - Булевое значение, которое включает или выключает MochaFollower
  • hello - Сообщение при запуске
  • mochaOptions - Объект с конфицурацие Mocha Runner'а. delay все равно не удастся выставить равным false. Это ключевое свойство функционирования MI, поэтому, даже если в mochaOptions вы передадите delay: true, это не будет иметь эффекта
  • asyncErrorMode - Режим работы при ошибках по-умолчанию для асинхронных задач
  • asyncErrorBehaviour - Поведение при ошибках по-умолчанию для асинхронных задач

asyncErrorMode и asyncErrorBehaviour заданные в конфиге Runner будут действовать на все наборы тестов, когда задание этих свойств в конфиге отдельного MISuite будет действовать только в пределах этого набора. Значению по-умолчанию для отдельного набора имеет большую силу, чем значение по-умолчанию для Runner

Mocha Runner

Mocha Runner возващается Mocha после вызова метода run на сущности Mocha. Это происходит в процессе запуска тестов, и, хоть MIRunner имеет поле mochaRunner, оно не всегда будет содержать Runner, в какой-то момент времени это может быть только сущность Mocha. Для получения именно сущности Runner стоит использовать событие MIRunner 'mocha-runner', в качетсве аргумента функции будет возвращетн Mocha Runner.

const runner = new MIRunner();
//В этом месте доступа к событиям Mocha нет
console.log(runner.mochaRunner.on) //undefined
runner.on("mocha-runner", function (mochaRunner) => {
	//Здесь, например, будут доступны события для mochaRunner
})
runner.run();

События

MIRunner предоставляет доступ к некоторому количесву свобыйти (большинство событий уже есть в Mocha Runner)

  • class-require - Событие с одним аргументом. Срабатывает в момент подключения файла из includePath. Аргументом является результат require();
  • classes-loaded - Событие без аргументов. Срабатывает, когда все файлы из includePath подключены.
  • mocha-runner - Событие с одним аргументов - Mocha Runner. Срабатывает в момент вызова mochaRunner.run()

Readme

Keywords

none

Package Sidebar

Install

npm i mocha-api-integration

Weekly Downloads

0

Version

1.0.4

License

ISC

Unpacked Size

56.9 kB

Total Files

20

Last publish

Collaborators

  • evecat