Connekt — это HTTP-клиент с открытым исходным кодом, который удобно встраивается в IDE на базе IntelliJ IDEA. Поставляется вместе с плагином Amplicode. Он помогает тестировать crud-приложения с помощью скриптов и готовить тестовые данные для дальнейшего ручного тестирования. Connekt призван расширить возможности привычных нам Postman и HTTP-клиента от Jet Brains. Postman имеет похожие возможности, но тесты там пишут на JavaScript, что для кого-то может быть неудобно. Кроме того, в Postman нет тесной связи с IDE. HTTP-клиент от Jet Brains не позволяет делать сложные тесты с использованием результатов предыдущих запросов, в нём отсутствует удобный Kotlin DSL. Connekt поддерживает сложные сценарии OAuth2-авторизации, переключая вас прямо в браузер, а также использование SSL-сертификатов, скачивание и загрузку файлов.
Инструмент находится на ранней стадии разработки (на момент написания статьи — версия 0.2.10 от 17 июля 2025 г.), тем не менее он уже обеспечивает удобство при работе в IDE и помогает оперативно решать потребности тестирования. Выполнение написанных сценариев можно также включать в ваш конвейер CI\CD.
Установка
Установить его очень просто: скачайте Amplicode с их сайта. Обратите внимание, что Connekt работает только в версии IDE 2025 г.
Скрипты можно создать в любом месте клиента, они получают расширение .connekt.kts.
Интерфейс

Интерфейс позволяет запустить все или выборочные сценарии из файла, выбрать окружение с переменными, импортировать Postman-коллекцию или запрос в форматe Intellij Client, справа есть ссылка на примеры кода. Также можно прямо из скрипта переходить к конечной точке вашего приложения.
Начнём с простых примеров.
Первый запрос
Разумеется, для использования API нужно получить токен. Если у вас есть постоянный токен, добавьте его в ваше окружение (если вам нужна авторизация, сделать это просто).
Для начала зарегистрируйте пользователя. Запрос для этого можно легко сгенерировать из приложения:

Получаем ответ со всеми заголовками и телом:
POST http://localhost:8080/api/auth/signup Content-Type: application/json User-Agent: connekt/0.0.1 Content-Length: 146 Host: localhost:8080 Connection: Keep-Alive Accept-Encoding: gzip { "firstName": "Alex", "lastName": "Pushkin", "username": "AlexPushkin", "email": "apushkin@habr.ru", "password": "password" } HTTP/1.1 201 Location: http://localhost:8080/api/users/1 X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: 0 X-Frame-Options: DENY Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 04 Nov 2025 11:32:15 GMT { "success" : true, "message" : "User registered successfully" } Response file saved. > C:\Users\ART PRONKIN\.connekt\response\2025-11-04T143215.json
Переменные окружения
Connekt поддерживает окружения, которые можно сохранять в обычном JSON-формате. Поэтому по всем канонам спрячем туда логин и пароль.

{ "local": { "email": "apushkin@habr.ru", "password": "password" } }
В консоли видим наш запрос и ответ сервера со всеми заголовками. Также прилагается ссылка на сохранённый ответ.
Теперь с помощью указанного ниже запроса мы можем получить токен:
val email: String by env val password: String by env val token by POST("http://localhost:8080/api/auth/signin") { header("Content-Type", "application/json") body( """ { "usernameOrEmail": "$email", "password": "$password" } """.trimIndent() ) } then { jsonPath().readString("$.accessToken") }
Получаем ответ:
{ "accessToken" : "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwiaWF0IjoxNzYyMjU2MjcxLCJleHAiOjE3NjIyNTk4NzF9.3sj-ZtIWbkImWLPiLreR2hPKUx9DMSOl9QzzaoDH2tQLutOfDeNp_k3WRPruzIK5UWMqJEIqWekvaV7bRf4doQ", "tokenType" : "Bearer" }
Парсинг ответов с помощью JsonPath
С помощью jsonPath можно легко извлечь нужную часть ответа, не прибегая к сериализации всего объекта. Ссылка на документацию JsonPath.
jsonPath().readString("$.accessToken")
Кеширование запросов
Обратите внимание, что для сохранения ответа мы используем by, а не =.
val token by POST("http://localhost:8080/api/auth/signin")
Использование = здесь не сработает так, как вы, возможно, ожидаете. Если написать:
val token = POST("http://localhost:8080/api/auth/signin")
то в переменную сохранится не строка с токеном, а объект типа io.amplicode.connekt.MappedRequestHolder, что, по сути, является неким callback нашего запроса, отложенным вызовом. Это сделано для кеширования и ленивой инициализации переменных. Самостоятельно вызвать этот callback в скрипте не удастся. При необходимости выполнить запрос из коллекции, зависящий от данных другого запроса (например, токен авторизации), не требуется инициализация переменной. Надо просто вызвать ваш запрос и Connekt всё сделает сам. Результат сохранится в env вашего Connekt-скрипта, и повторные запросы не будут выполняться. В этом мы можем убедиться по журналу вызова. Если выполнить последовательно несколько вызовов, токен останется без изменений.
Авторизация
Для дальнейшего использования токена есть специальная функция bearerAuth, но вы можете указывать и обычный заголовок:
GET("http://localhost:8080/api/users/me") { bearerAuth(token) }
Каждый раз мы будем видеть тот же токен:
GET http://localhost:8080/api/users/me Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwiaWF0IjoxNzYyMjU2MjcxLCJleHAiOjE3NjIyNTk4NzF9.3sj-ZtIWbkImWLPiLreR2hPKUx9DMSOl9QzzaoDH2tQLutOfDeNp_k3WRPruzIK5UWMqJEIqWekvaV7bRf4doQ
{ "id" : 1, "username" : "alexpushkin", "firstName" : "alex", "lastName" : "pushkin" }
Принудительное обновление переменных
Если вам потребуется инициализировать переменную заново, следует просто выполнить запрос с получением её значения:
val token by POST("http://localhost:8080/api/auth/signin")
После его выполнения токен обновится.
Далее понадобится создать категорию для наших постов. Добавляем и сохраняем:
val categoryId by POST("http://localhost:8080/api/categories") { header("Content-Type", "application/json") bearerAuth(token) body( """ { "name": "Books" } """.trimIndent() ) } then { jsonPath().readInt("$.id") }
Теперь, наконец, напишем тест, создадим новый пост, используя существующую категорию, и выполним проверки с помощью библиотеки Assertj (документация).
Тесты
Параметры postBody и postTitle я передам в виде аргументов из env. Согласно бизнес-логике нашего приложения, создавать посты с одинаковым заголовками не получится, поэтому можно легко добавить генерирование случайного числа для главы.
val postTitle: String by env val postBody: String by env POST("http://localhost:8080/api/posts") { header("Content-Type", "application/json") bearerAuth(token) body( """ { "title": "$postTitle Глава ${Random.nextInt(0, 50)}", "body": "$postBody", "categoryId": $categoryId, "tags": ["Пушкин"] } """.trimIndent() ) } then { Assertions.assertThat(code).isEqualTo(201) jsonPath().doRead("$.category").also { Assertions.assertThat(it).isEqualTo("Books") } jsonPath().doRead>("$.tags").also { Assertions.assertThat(it).contains("Пушкин") } }
Сериализация в Kotlin-классы
Не обязательно использовать jsonPath. Можно целиком сериализовать наш ответ для дальнейших проверок. Для это создадим класс или просто скопируем его из нашего приложения. К сожалению, импортировать из соседних скриптов пока невозможно, поэтому придётся описать объект в этом же файле.
class PostResponse { val title: String? = null val body: String? = null val category: String? = null var tags: List? = null } POST("http://localhost:8080/api/posts") { header("Content-Type", "application/json") bearerAuth(token) body( """ { "title": "$postTitle Глава ${Random.nextInt()}", "body": "$postBody", "categoryId": $categoryId, "tags": ["Пушкин"] } """.trimIndent() ) } then { Assertions.assertThat(code).isEqualTo(201) jsonPath().doRead("$").also { post -> Assertions.assertThat(post.category).isEqualTo("Books") Assertions.assertThat(post.tags).contains("Пушкин") } }
Использование useCase и функций
Теперь рассмотрим функцию useCase. Ей можно дать имя, описать в ней сценарий и запустить целиком. Важно отметить, что эта возможность не описана в документации к Connekt: мы можем обернуть наши запросы в функции, что позволит параметризировать их и использовать повторно.
Помимо тестирования, Connekt может служить для агрегации данных из нескольких источников, автоматизации рутинной работы с API и анализа ответов этих API. Например, в нашем приложении не реализован поиск по тегам и категориям постов, а для POST-запросов всегда требуется указывать идентификатор нужной категории поста.
Без рабочего фронтенда подготовка данных, ручное тестирование и поиск по JSON-ответам могут быть утомительным занятием. Поэтому напишем сценарий, который решает эту задачу и покажет, как эффективно подготовить ваше приложение к тестированию, предварительно наполнив его данными.
Код
useCase("Поиск по тегу и категории") { data class Tag( val name: String ) data class Category( val name: String, val id: Long ) data class Post( val tags: List, val category: Category, val title: String ) val token by POST("http://localhost:8080/api/auth/signin") { header("Content-Type", "application/json") body( """ { "usernameOrEmail": "example@mail.ru", "password": "password" } """.trimIndent() ) } then { jsonPath().readString("$.accessToken") } fun addCategory(category: String): Category { return POST("http://localhost:8080/api/categories") { header("Content-Type", "application/json") bearerAuth(token) body( """ { "name": "$category" } """.trimIndent() ) } then { jsonPath().doRead("$") } } fun findByTagAndName(categoryName: String, tagName: String): List { return GET("http://localhost:8080/api/posts") { bearerAuth(token) queryParam("page", "0") queryParam("size", "10") } then { jsonPath().doRead>("$.content") .filter { it.tags.any { tag -> tag.name == tagName } && it.category.name == categoryName } } } fun getAllCategory(): List { return GET("http://localhost:8080/api/categories") { bearerAuth(token) queryParam("page", "0") queryParam("size", "10") } then { jsonPath().doRead>("$.content") } } fun findCategory(category: String) = getAllCategory().firstOrNull { it.name == category } fun findPostByCategory(category: String): List? { return findCategory(category)?.let { GET("http://localhost:8080/api/posts/category/{id}") { bearerAuth(token) pathParam("id", it.id) queryParam("page", "0") queryParam("size", "10") } then { jsonPath().doRead>("$.content") } } } fun addPost(postTitle: String, postBody: String, categoryName: String, tag: String) { val category = findCategory(categoryName) ?: addCategory(categoryName) POST("http://localhost:8080/api/posts") { header("Content-Type", "application/json") bearerAuth(token) body( """ { "title": "$postTitle Глава ${Random.nextInt(0, 20)}", "body": "$postBody", "categoryId": ${category.id}, "tags": ["$tag"] } """.trimIndent() ) } } addCategory("It") addCategory("Business") addCategory("Life-style") addPost("Сергей Александрович Есенин", postBody, "Стихи", "Поэзия") addPost("Как начать свой бизнес", postBody, "Business", "История") addPost("История первых ЭВМ", postBody, "It", "История") addPost("Unix системы", postBody, "It", "Unix") val allCategory = getAllCategory() val resultByCategory = findPostByCategory("It") val resultByBooks = findByTagAndName("Стихи", "Поэзия") val resultByIt = findByTagAndName("It", "История") println("Все категории : ") allCategory.forEach { category -> println(category.name) } println("Поиск по тегу It : ") resultByCategory?.forEach { post -> println(post.tags.map { it.name } + post.title) } println("Поиск по категории Стихи и тегу Поэзия : ") resultByBooks.forEach { post -> println(post.title) } println("Поиск по категории It и тегу История : ") resultByIt.forEach { post -> println(post.title) } }
Получаем следующий ответ:
Все категории : It Business Life-style Стихи Поиск по тегу It : [Unix, Unix системы Глава 3] [История, История первых ЭВМ Глава 6] Поиск по категории Стихи и тегу Поэзия : Сергей Александрович Есенин Глава 17 Поиск по категории It и тегу История : История первых ЭВМ Глава 6
Итоги
К достоинствам можно отнести:
Возможность работы прямо в IDE
Быстрое генерирование запросов прямо из класса с конечными точками
Интеграцию с отладчиком
Низкий порог входа
Однако есть и недостатки:
На текущем этапе невозможно импортировать код из других скриптов, что вынуждает нас писать всё в одном файле
Импортирование Postman-коллекций и других форматов реализовано неудовлетворительно
Документация слабо развита, а готовых примеров в сети практически нет
Периодически встречаются баги
Несмотря на эти недостатки, Connekt представляется интересным и перспективным проектом. Принципиальных проблем не обнаружено, и все выявленные моменты могут быть устранены по мере развития. Было бы очень интересно увидеть интеграцию Connekt и Kotlin notebook.
Рекомендации
Если результат запроса не требуется для дальнейших действий в сценарии, но важно убедиться в его корректности, всегда добавляйте проверку кода ответа. Это предотвратит сбои на более поздних этапах и сократит время на диагностику и отладку. В случае ошибки проверка остановит сценарий и зафиксирует несоответствие в журнале.

Чтобы избежать путанницы, всегда давайте вашим сценариям уникальные имена.
Если у вас есть опыт написания хороших промптов, попробуйте передать ИИ-агенту документацию Connekt и примеры из этой статьи для генерации сценариев тестирования. У меня это получалось, но не всегда стабильно.
Полезные ссылки
Более подробную информацию о Connekt вы можете получить на сайте Amplicode и в видеоразборе.
Инструкция по интеграции с CI/CD есть на Github.