skip to content

What does declaring `mock_provider "aws"` in a Terraform test file change about how the tests execute, and what stops being tested?

level: seniorimportance: nice to knowfreq 26%

answer

  1. the graph runs, the network does not
  2. computed attributes become generated values
  3. defaults pin the values a test depends on
  4. overrides are the scalpel, mocks the hammer
  5. a hand-written snapshot goes stale

basics

~20 s

A mock_provider block replaces the real provider, so Terraform walks the full plan and apply graph while generated values stand in for computed attributes. Nothing is created, no credentials are needed, and tests run in seconds — but no API ever validates the request.

solid answer

~50 s

With `mock_provider "aws"` declared in a `.tftest.hcl` file, Terraform still evaluates the configuration and still executes `command = apply` runs, but the provider never talks to AWS: computed attributes come back as generated values. That turns a suite that provisions real infrastructure into one that needs no credentials, costs nothing and finishes in seconds, which is what makes it viable on every pull request. You pin specific values with `mock_resource` and `mock_data` blocks carrying `defaults`, or override individual instances with `override_resource`, `override_data_source` and `override_module` while keeping the real provider elsewhere. What you give up is everything only the API knows: a malformed IAM policy, a name that violates a service's rules, a quota, a permission denial, and the true shape of computed values. Mocked defaults are hand-written and drift from reality, so most teams keep a small unmocked suite alongside.

code

hcl · 20 lines
hcl
mock_provider "aws" {
  mock_resource "aws_s3_bucket" {
    defaults = {
      arn = "arn:aws:s3:::test-bucket"
    }
  }
}

run "creates_one_bucket_per_environment" {
  command = apply

  variables {
    environments = ["dev", "staging"]
  }

  assert {
    condition     = length(aws_s3_bucket.this) == 2
    error_message = "expected one bucket per environment"
  }
}

go deeper

for a junior

Know that Terraform test files can mock a provider so tests run with no cloud credentials and create nothing, using mock_provider in a .tftest.hcl file.

for a middle

Explain that evaluation still happens end to end and only the provider's computed values are generated, and know the difference between mocking a whole provider and overriding a single resource, data source or module.

for a senior

Be specific about the coverage you lose — API-side validation, permissions, quotas, real value formats — and describe the mixed suite you would actually run, with fast mocked tests on every commit and a small real one before release.

for a principal

Own the assurance argument: a green mocked suite is evidence about configuration logic only, so decide explicitly what still has to be proven against real providers before a module is published for other teams to depend on.

## The problem mocking solves The native test framework's default behaviour is honest and expensive: `command = apply` runs use the real provider and create real infrastructure. That makes a suite slow, credentialed, and unable to run on a fork's pull request. Provider mocking, added in Terraform 1.7, keeps the whole evaluation path while removing the network. ```hcl mock_provider "aws" { mock_resource "aws_s3_bucket" { defaults = { arn = "arn:aws:s3:::test-bucket" } } mock_data "aws_ami" { defaults = { id = "ami-0123456789abcdef0" } } } ``` Declared at file level (or per run), this substitutes the provider entirely. Terraform builds the same graph, evaluates the same expressions, resolves the same `for_each`, and runs your `assert` conditions — but any attribute the provider would have computed is filled in with a generated value, or with the `defaults` you specified when a test depends on a particular shape. ## Targeted overrides Mocking the entire provider is the blunt instrument. Three finer tools exist: - `override_resource` — replace one resource's computed values while everything else behaves normally; - `override_data_source` — return canned data for a lookup, which is the common case: a data source that would need a real VPC or a real AMI in the account; - `override_module` — stub out an entire child module, useful when the module under test composes something slow or expensive. Each carries a `values` map, and each can be declared at file level or inside a single `run` block, so one file can mix a fast mocked run with a real one. ## What is still genuinely tested More than people expect. Mocking removes the API, not the configuration language, so a mocked suite still catches: - naming and string composition logic; - `count` and `for_each` producing the wrong number of instances, or the wrong keys; - conditional resources that appear or vanish for the wrong input; - outputs wired to the wrong attribute, or not exported at all; - input rejection, via `expect_failures`; - module inputs plumbed to the wrong variable. That set is the bulk of the defects a module actually ships, and catching it in seconds on every pull request is the point. ## What silently stops being tested Everything whose only judge is the provider or the API: - an IAM policy document that is syntactically fine but rejected as malformed; - a value that violates a service constraint the schema does not encode — a name too long, a character class the service forbids, a region where the resource type is unavailable; - permissions, quotas, and service limits; - the real shape of computed values: your mocked ARN is whatever you typed, so code that parses or slices an ARN can pass against a mock and fail in production; - provider-side plan behaviour such as an unexpected forced replacement. There is a second, slower failure mode: mocks rot. A `defaults` map is a hand-written snapshot of what a provider returns. Providers change, attributes are added, and nothing in the test suite notices. A green mocked suite is a statement about your configuration logic, never a statement about the cloud. ## The shape teams settle on A broad mocked suite that runs on every commit — fast, free, credential-free — plus a small unmocked suite exercising one or two representative paths, run on a schedule or before a release, in an isolated account. The mocked tests protect refactoring; the real ones prove the module still works against the provider as it exists today. Deciding where that line sits for a given module is the judgment the question is really probing. ## Answering it well State the mechanism first — the provider is replaced, computed attributes become generated values, apply creates nothing — then be specific about the lost coverage rather than saying "it is less realistic". Naming a concrete escape, such as an IAM policy the API would reject or an ARN whose real format your code parses, is what makes the answer credible.

  • If mocked tests are fast and free, why keep any unmocked ones at all?
    Because a mocked run proves your configuration logic, not that the cloud accepts it. Permissions, quotas, service-side name rules and malformed policy documents are only judged by the API, and your `defaults` are a hand-written snapshot that drifts as providers change. The usual compromise is a broad mocked suite on every commit plus one or two real paths run before a release.
  • Your module parses an ARN with a string function and the mocked test passes, but production breaks. What went wrong?
    The mocked ARN was whatever the `defaults` map said, so the parsing logic was validated against a value you invented rather than one the provider produces. Any code that depends on the internal format of a computed value needs either a realistic mock derived from a real response, or an unmocked test that gets the genuine article.

saying these in an interview costs you the question

  • Thinks mocking skips evaluation or turns apply into plan
  • Claims a fully mocked suite proves the infrastructure works
  • Believes mocks are validated against the provider schema over time
  • Assumes mocked ARNs and ids have realistic formats
  • Sees no need for any unmocked test once mocking exists

context