linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Введение
  • Уроки
  • How it works
  • База знаний
  • Шпаргалка
  • Capstone
  • Собеседование
home/terraform/lessons/tf-production-04-mock-providers

lesson ── terraform-production ── ~14 мин ── 5 шагов

Mock providers, tests without the cloud

Apply tests are expensive, and even LocalStack costs seconds on every run. Mock providers swap the real AWS for synthesized answers; command = apply becomes fast and offline. There are three tools: mock_provider, override_resource, override_data. Available from Terraform 1.7+.

▶ интерактивный sandbox

Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.

запустить sandbox →

stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя

Шаги

  1. 01

    A module with two cross-referencing resources

    To show off a mock with defaults you need a module where one resource references another through its id.

    bash
    cd /home/student/tf-mocks/modules/audited-bucket
    cat > main.tf <<'EOF'
    resource "aws_s3_bucket" "this" {
      bucket = var.name
    }
    resource "aws_s3_bucket_versioning" "this" {
      bucket = aws_s3_bucket.this.id
      versioning_configuration {
        status = "Enabled"
      }
    }
    resource "aws_s3_bucket_public_access_block" "this" {
      bucket = aws_s3_bucket.this.id
      block_public_acls       = true
      block_public_policy     = true
      ignore_public_acls      = true
      restrict_public_buckets = true
    }
    EOF
    cat > variables.tf <<'EOF'
    variable "name" {
      type = string
    }
    EOF
    cat > outputs.tf <<'EOF'
    output "id" {
      value = aws_s3_bucket.this.id
    }
    EOF

    Three resources: the bucket, versioning, and a public-access-block. Versioning and the ACL both reference aws_s3_bucket.this.id.

    ✓ The module with cross-references is ready.

  2. 02

    A bare mock_provider, what you get

    bash
    cat > /home/student/tf-mocks/modules/audited-bucket/tests/mock_bare.tftest.hcl <<'EOF'
    mock_provider "aws" {}
    run "with_bare_mock" {
      command = apply
      variables {
        name = "test-mocked"
      }
      assert {
        condition     = aws_s3_bucket.this.bucket == "test-mocked"
        error_message = "bucket name not propagated"
      }
    }
    EOF
    cd /home/student/tf-mocks/modules/audited-bucket
    terraform init -backend=false
    terraform test -filter=tests/mock_bare.tftest.hcl

    You should see a pass. mock_provider "aws" {} generates defaults from the schema, with no cloud at all. The apply is instant.

    ✓ The bare mock works. One test without LocalStack.

  3. 03

    mock_resource with defaults, stable ids

    A bare mock returns "foo" for every string attribute. If two resources reference aws_s3_bucket.this.id, both get "foo". That is fine, but opaque. Let's pin an explicit id:

    bash
    cat > /home/student/tf-mocks/modules/audited-bucket/tests/mock_with_defaults.tftest.hcl <<'EOF'
    mock_provider "aws" {
      mock_resource "aws_s3_bucket" {
        defaults = {
          id  = "mocked-id-001"
          arn = "arn:aws:s3:::mocked-id-001"
        }
      }
    }
    run "versioning_references_correct_bucket" {
      command = apply
      variables {
        name = "test-with-defaults"
      }
      assert {
        condition     = aws_s3_bucket_versioning.this.bucket == "mocked-id-001"
        error_message = "versioning should reference bucket id mocked-id-001"
      }
      assert {
        condition     = aws_s3_bucket_public_access_block.this.bucket == "mocked-id-001"
        error_message = "PAB should reference bucket id mocked-id-001"
      }
    }
    EOF
    terraform test -filter=tests/mock_with_defaults.tftest.hcl

    You should see a pass. Versioning and the PAB reference mocked-id-001 correctly, because the mock pinned that for the bucket.

    ✓ Cross-resource references are verified without the cloud.

  4. 04

    override_resource, a targeted swap

    Sometimes the base mock defaults fit most runs, but one run needs a different value. An override_resource inside a run takes priority over the file-level mock.

    bash
    cat > /home/student/tf-mocks/modules/audited-bucket/tests/override.tftest.hcl <<'EOF'
    mock_provider "aws" {
      mock_resource "aws_s3_bucket" {
        defaults = {
          id = "default-id"
        }
      }
    }
    run "default_mock_used" {
      command = apply
      variables { name = "x" }
      assert {
        condition     = aws_s3_bucket_versioning.this.bucket == "default-id"
        error_message = "expected default-id"
      }
    }
    run "override_for_this_run" {
      command = apply
      variables { name = "y" }
      override_resource {
        target = aws_s3_bucket.this
        values = {
          id = "specific-override-id"
        }
      }
      assert {
        condition     = aws_s3_bucket_versioning.this.bucket == "specific-override-id"
        error_message = "expected override id"
      }
    }
    EOF
    terraform test -filter=tests/override.tftest.hcl

    You should see 2 passed. The first run uses the file-level default, the second uses its own override. This is handy for "one shared test plus special cases" without duplicating the mock config.

    ✓ Mock and override work together.

    The same thing on OpenTofu

    OpenTofu keeps the CLI and state compatible with Terraform for the commands in this step: migration usually goes through mv .terraform .terraform.bak; tofu init -upgrade. On a first switch, though, back up the state and do a run on a feature branch. The differences cluster in the newer features (variables in backend, state encryption, OCI registry-backed modules). See tf-opentofu-parity for the full matrix.

    • → OpenTofu parity
  5. 05

    Compare the speed of mock vs LocalStack apply

    Let's show the difference briefly. Run all the mock tests under time:

    bash
    time terraform test \
      -filter=tests/mock_bare.tftest.hcl \
      -filter=tests/mock_with_defaults.tftest.hcl \
      -filter=tests/override.tftest.hcl

    Note down the real time (1.2s, say).

    Create an equivalent non-mock test:

    bash
    cat > tests/realstack.tftest.hcl <<'EOF'
    provider "aws" {
      region                      = "us-east-1"
      access_key                  = "test"
      secret_key                  = "test"
      s3_use_path_style           = true
      skip_credentials_validation = true
      skip_metadata_api_check     = true
      skip_requesting_account_id  = true
      endpoints {
        s3  = "http://localstack:4566"
        iam = "http://localstack:4566"
        sts = "http://localstack:4566"
      }
    }
    run "real_apply" {
      command = apply
      variables { name = "real-test-bucket" }
      assert {
        condition     = aws_s3_bucket.this.id == "real-test-bucket"
        error_message = "id mismatch"
      }
    }
    EOF
    time terraform test -filter=tests/realstack.tftest.hcl

    Compare them. LocalStack takes longer because of the real provider and the HTTP calls. Mock is 5 to 20 times faster. On a large test suite the difference is minutes.

    This is what mocks exist for. In CI: the bulk of your unit tests on mocks; a couple of integration tests on LocalStack or AWS.

    ✓ Mock vs real: the difference is noticeable. Mock for the bulk of CI, LocalStack selectively.

    When a mock is the wrong choice

    Mock vs real is not dogma. Cases where mock loses:

    1. A test on HCL correctness that hits a real API. Take aws_iam_policy_document, a data source that renders JSON locally. A mock returns a placeholder, the real data source returns the actual policy. If your assert is length(jsondecode(...)) == 5, you need the real one.

    2. Cross-resource through a computed attribute. If resource A returns a computed arn in a non-standard shape, and B parses it, the mock returns "foo", B fails to parse, and the test fails by mistake. An override saves you, but it is boilerplate.

    3. You are testing the provider itself. A mock checks that "the HCL describes what you want correctly". If you want to be sure AWS accepts your config, you need the real one.

    Rule of thumb: mock for "a test on your own code", real for "a test on the integration with the cloud".

    • → Mock providers in full
    • → What to test

Что ты узнал

mock_provider "aws" {} in a *.tftest.hcl file replaces the provider with a fake. mock_resource "X" { defaults = {...} } sets values for one specific type. override_resource / override_data swap a single value inside one run block.

команды

  • terraform test -filter=tests/mocked.tftest.hclmock tests are faster, every apply is offline.
  • terraform test -verboseyou see which overrides were applied.

концепции

  • · mock_provider expands attributes into defaults from the schema
  • · defaults pin id/arn so cross-resource references work
  • · override_resource inside a run block takes priority over file-level mock_resource

← предыдущий

dynamic blocks: repeated subblocks

следующий →

Blue-green migration of legacy infrastructure in Terraform

Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies