Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Terraform By James Joyner IV · · 9 min read

Passing mock_provider Through the providers Block in Terraform Tests

Quick answer

Wire mock_provider into modules with the providers = { aws = aws.mock } block in .tftest.hcl. Fix aliased mock providers not reaching child modules in Terraform 1.7+ tests.

  • #terraform
  • #testing
  • #mock_provider
  • #troubleshooting
Free toolkit

Fixing errors like this? Get 500 free DevOps AI prompts

500 copy-paste AI prompts for the stack you actually run — one PDF, free.

The Problem

You declared a mock_provider in your .tftest.hcl file, but your test still tries to reach the real cloud, or fails with a provider-configuration error inside a child module:

Error: Provider configuration not present

  To work with module.network.aws_vpc.main its original provider
  configuration at module.network.provider["registry.terraform.io/hashicorp/aws"].mock
  is required, but it has been removed.

The mock is defined, but it is not flowing into the module that actually creates resources. The missing piece is the providers wiring inside the run block.

How mock_provider Configurations Flow

A mock_provider block does two things. It registers a provider that returns fake data instead of calling a real API, and, when you give it an alias, it creates a named configuration you can pass around explicitly, exactly like a real aliased provider.

Terraform does not automatically inject an aliased mock everywhere. Child modules receive provider configurations either implicitly (by provider type) or explicitly through a providers = { ... } map. When your module expects a specific configuration, or when you have both a real and a mock configuration in play, you must hand the mock to the run and, if needed, into the module.

The Two-Level Wiring

There are two places providers get wired in a test:

  1. Run to root module — the providers = { ... } argument in a run block maps provider references to the configurations the root module should use.
  2. Root module to child module — the standard providers = { ... } argument on a module call inside your configuration.

Both use the same type = type.alias syntax. Getting a mock all the way down means setting it at whichever level breaks.

How to Wire a Mock Through the providers Block

Step 1: Declare an aliased mock

Give the mock an alias so it is a distinct, referenceable configuration:

mock_provider "aws" {
  alias = "mock"
}

Without an alias, the mock replaces the default aws configuration and flows implicitly. With an alias, you control exactly where it goes, which is what you want when a module is picky about its provider.

Step 2: Map it in the run block

Inside the run block, use the providers argument to bind the module’s expected aws reference to your aws.mock configuration:

run "vpc_is_created_offline" {
  command = plan

  providers = {
    aws = aws.mock
  }

  assert {
    condition     = aws_vpc.main.cidr_block == "10.0.0.0/16"
    error_message = "VPC CIDR did not match the expected value"
  }
}

Now every aws resource in the root module uses the mock. No credentials, no network.

Step 3: Pass the provider into child modules in your config

The test wires run to root, but your root module still has to pass the provider to its children. In your regular configuration:

provider "aws" {
  region = var.region
}

module "network" {
  source = "./modules/network"

  providers = {
    aws = aws
  }
}

Because the test maps aws = aws.mock at the run level, the aws configuration that module.network receives is already the mock. The child module never knows the difference.

Step 4: Handle modules that require an aliased provider

If a child module declares a configuration_aliases requirement, you must map that specific alias. Given a module that expects aws.primary:

# modules/network/versions.tf
terraform {
  required_providers {
    aws = {
      source                = "hashicorp/aws"
      configuration_aliases = [aws.primary]
    }
  }
}

Wire the mock to that alias in the run block, then forward it in the module call:

run "network_with_primary_alias" {
  command = plan

  providers = {
    aws         = aws.mock
    aws.primary = aws.mock
  }

  assert {
    condition     = module.network.vpc_id != ""
    error_message = "network module did not produce a vpc_id"
  }
}
module "network" {
  source = "./modules/network"
  providers = {
    aws.primary = aws.primary
  }
}

Step 5: Run it

terraform test
tests/network.tftest.hcl... in progress
  run "vpc_is_created_offline"... pass
  run "network_with_primary_alias"... pass
tests/network.tftest.hcl... pass

Success! 2 passed, 0 failed.

Mixing Real and Mock Providers

A useful pattern is to mock one provider while keeping another real, or to mock a provider only in some runs. Because the mock is a named alias, you switch per run:

provider "aws" {
  region = "us-east-1"
}

mock_provider "aws" {
  alias = "mock"
}

run "unit_plan" {
  command   = plan
  providers = { aws = aws.mock }   # offline
  assert { condition = aws_vpc.main.tags["Env"] == "test", error_message = "tag missing" }
}

run "integration_apply" {
  command = apply                  # uses the real aws configuration
  assert { condition = aws_vpc.main.id != "", error_message = "no VPC id" }
}

The first run is a fast offline unit test; the second uses the real provider for an integration check. Selecting the mock is a one-line change in providers.

You can scaffold these run blocks and provider maps quickly with the DevOps AI prompt library.

Common Pitfalls

  • No alias, then referencing aws.mock. If you write providers = { aws = aws.mock } you must declare alias = "mock" on the mock_provider. Otherwise the reference does not resolve.
  • Forgetting the child module map. Wiring the run to the root does nothing for a child that needs an explicit configuration_aliases mapping. Map every alias the child requires.
  • Assuming implicit inheritance for aliases. Default (unaliased) configurations inherit implicitly; aliased ones never do. Always pass aliases explicitly.
  • Mismatched alias names. The key on the left of providers = { aws.primary = ... } must match the alias the module declares in configuration_aliases.

Frequently Asked Questions

How do I pass a mock_provider into a child module? Give the mock an alias, map it in the run block with providers = { aws = aws.mock }, and make sure your configuration forwards the provider to the child module via its own providers argument. Aliased configurations must be passed explicitly at every level.

Why does my mock work in the root but not in a submodule? Aliased providers do not inherit implicitly. If the submodule declares configuration_aliases, you must map that exact alias in both the run block and the module call. A missing map produces a “provider configuration not present” error.

Do I need an alias on mock_provider? Only if you want to reference it explicitly or run it alongside a real configuration. An unaliased mock_provider "aws" {} replaces the default and flows implicitly, which is simpler for pure unit tests.

Can I use a real provider and a mock in the same test file? Yes. Declare both, give the mock an alias, and choose per run with the providers argument. Runs without the map use the real configuration.

What syntax does the providers map use? It maps a provider reference to a configuration: providers = { aws = aws.mock } or providers = { aws.primary = aws.mock }. The left side is what the module expects; the right side is the configuration you supply. For more patterns, see the Terraform guides.

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.