Зачем mock'и
terraform test с command = apply стандартно ходит в провайдера. AWS
деньги и латентность, LocalStack, нужно поднять и ждать. Для unit-тестов
модуля это перебор: проверить генерацию ARN из имени бакета можно без
единого API-вызова.
Mock-провайдер отвечает на вызовы провайдера синтетическими данными. Доступен с Terraform 1.7.
mock_provider целиком
# tests/unit.tftest.hcl
mock_provider "aws" {}run "naming" {command = apply # apply ок, mock не пойдёт в облако
variables {name = "test-bucket"
}
assert {condition = aws_s3_bucket.this.bucket == "test-bucket"
error_message = "name not propagated"
}
}
mock_provider "aws" {} без аргументов, каждый ресурс/data-source AWS
получает дефолтные mock-значения. Terraform сам генерирует placeholder'ы:
строки, "unknown", числа, 0, bool, false, sets/lists, пустые.
Apply «выполняется», но в облако ничего не идёт.
Default-значения для конкретных типов
Если генерированные дефолты ломают логику (например модуль читает
aws_s3_bucket.this.id и из него строит другие имена) задай дефолты:
mock_provider "aws" { mock_resource "aws_s3_bucket" { defaults = {id = "mocked-bucket-id"
arn = "arn:aws:s3:::mocked-bucket"
}
}
mock_data "aws_caller_identity" { defaults = {account_id = "111111111111"
arn = "arn:aws:iam::111111111111:user/test"
user_id = "AIDA1234567890"
}
}
}
Теперь aws_s3_bucket.this.id стабильно вернёт mocked-bucket-id во всех
тестах в этом файле.
Точечный override на один run
Файл-level mock'и одинаковы для всех run. Если в одном сценарии нужно
специфичное значение, override_resource/override_data внутри run:
run "private_bucket_acl" {command = apply
override_resource {target = aws_s3_bucket.this
values = {id = "specific-private-bucket"
arn = "arn:aws:s3:::specific-private-bucket"
}
}
assert {condition = aws_s3_bucket_acl.this.bucket == "specific-private-bucket"
error_message = "ACL refers to wrong bucket"
}
}
target, адрес ресурса в модуле, который тестируешь. Override приоритетнее
чем mock_resource ... defaults на уровне файла.
Без mock_provider, только override
Если основная масса тестов реальная (LocalStack), а нужно подменить один data-source чтобы не зависеть от внешнего источника, mock_provider не объявляй, используй только override:
run "with_fixed_account" {command = plan
override_data {target = data.aws_caller_identity.current
values = {account_id = "999999999999"
}
}
assert {condition = local.bucket_name == "logs-999999999999"
error_message = "account_id not woven into name"
}
}
Остальные ресурсы пойдут к реальному провайдеру (или LocalStack, куда
настроен). Только aws_caller_identity подменён.
Какие тесты, mock, какие, real
| Тестирую | Тип теста |
|---|---|
| Генерация имени из переменных | mock, облако не нужно |
Условную логику (count = var.enabled ? 1 : 0) | mock |
| Передачу значений между ресурсами модуля | mock |
| Что модуль реально создаёт ресурс с правильным config | real (LocalStack или AWS) |
| Что cross-resource политики (bucket policy) подтягиваются | real |
Правило: если ассерт можно написать без атрибутов «known after apply» делай mock. Сэкономишь часы CI.
Mock'ам не нужен init -upgrade
terraform init с mock'ом пройдёт быстрее, Terraform не качает реальный
провайдер, только schema из официального плагина (для type-checking).
В CI экономия минут.
Поведение по умолчанию: что mock возвращает
Если не задал defaults, mock-провайдер придумывает значения по schema
атрибута:
| Тип атрибута | Mock-значение |
|---|---|
string | "foo" |
number | 0 |
bool | false |
list/set/map | пустое |
object | объект с дефолтами вложенных полей |
| Optional + не required | null |
Это значит: атрибуты типа id/arn получат строку "foo". Если две
reference'а строятся на разные aws_s3_bucket.X.id, они получат
одинаковое "foo". Логика, зависящая от уникальности, сломается.
Лекарство, задать defaults для каждого ресурса.
Подводные камни
-
Mock'и работают только в
*.tftest.hcl. В обычном.tfнет блокаmock_provider. Нельзя «протестировать через terraform plan руками». Толькоterraform test. -
Атрибуты, вычисляемые провайдером сложной логикой, не моделируются. Например
aws_iam_policy_document, это data-source, который локально рендерит JSON. Mock вернёт placeholder JSON, а не правильную политику. Для проверки IAM-политик нужен реальный data-source или явный override на каждое поле. -
Mock-default'ы, не fixture'ы. Они не сохраняют состояние между run'ами. Если первый run меняет
aws_s3_bucket.this, второй run получит снова mock-default, а не изменённое значение. Это runner'у нравится, тестировщику, не всегда. -
override_resourceиспользует module-локальный адрес. Неmodule.X.aws_s3_bucket.this, а простоaws_s3_bucket.thisпредполагается, что тест запущен в директории модуля. -
Provider-level mock не покрывает sources, объявленные через
dataв одноименных провайдерах разных alias'ов.aws.euнужен отдельныйmock_provider "aws" { alias = "eu" }.