# Mock-провайдеры: mock_provider, override_resource, override_data _Тестирование · TerraformLab Knowledge Base_ **TL;DR:** Mock-провайдер заменяет реальный AWS на синтезированные ответы, тест бежит без облака, секунды вместо минут. Объявляется в `*.tftest.hcl` через `mock_provider "aws"`. Точечно подменить отдельный ресурс или data-source, `override_resource` и `override_data`. Без mock'ов любой `command = apply` будет требовать LocalStack. ## Зачем mock'и `terraform test` с `command = apply` стандартно ходит в провайдера. AWS деньги и латентность, LocalStack, нужно поднять и ждать. Для unit-тестов модуля это перебор: проверить генерацию ARN из имени бакета можно без единого API-вызова. Mock-провайдер отвечает на вызовы провайдера синтетическими данными. Доступен с Terraform 1.7. ## mock_provider целиком ```hcl # 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` и из него строит другие имена) задай дефолты: ```hcl 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`: ```hcl 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: ```hcl 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" }`. ## Команды ```bash terraform test ``` Стандартный запуск. С mock_provider гоняется быстро. ```bash terraform test -verbose ``` Видишь какие override применились на каждом run. ```bash terraform plan -input=false ``` Mock-блоки игнорируются вне тестов. Этот plan пойдёт в реальный provider. ## См. также - [Terratest: интеграционные тесты Terraform на Go](/terraform/kb/terratest-basics.md) - [Что тестировать в Terraform, а что: не надо](/terraform/kb/iac-testing-theory.md) - [data-блок: читаем то, что уже есть в облаке](/terraform/kb/tf-data-source.md) - [LocalStack: учебный AWS, который живёт в Docker](/terraform/kb/localstack-provider.md)