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.