diff --git a/content/terraform-plugin-testing/v1.14.x/data/plugin-testing-nav-data.json b/content/terraform-plugin-testing/v1.14.x/data/plugin-testing-nav-data.json new file mode 100644 index 0000000000..6768edb969 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/data/plugin-testing-nav-data.json @@ -0,0 +1,212 @@ +[ + { + "heading": "Testing" + }, + { + "title": "Overview", + "path": "" + }, + { + "title": "Acceptance Testing", + "routes": [ + { + "title": "Overview", + "path": "acceptance-tests" + }, + { + "title": "Test Cases", + "path": "acceptance-tests/testcase" + }, + { + "title": "Test Steps", + "routes": [{ + "title": "Overview", + "path": "acceptance-tests/teststep" + }, { + "title": "Import mode", + "path": "acceptance-tests/import-mode" + }, { + "title": "Query mode", + "path": "acceptance-tests/query-mode" + }] + }, + { + "title": "Terraform Version Checks", + "path": "acceptance-tests/tfversion-checks" + }, + { + "title": "Plan Checks", + "routes": [ + { + "title": "Overview", + "path": "acceptance-tests/plan-checks" + }, + { + "title": "Resource Plan Checks", + "path": "acceptance-tests/plan-checks/resource" + }, + { + "title": "Output Plan Checks", + "path": "acceptance-tests/plan-checks/output" + }, + { + "title": "Custom Plan Checks", + "path": "acceptance-tests/plan-checks/custom" + } + ] + }, + { + "title": "State Checks", + "routes": [ + { + "title": "Overview", + "path": "acceptance-tests/state-checks" + }, + { + "title": "Resource State Checks", + "path": "acceptance-tests/state-checks/resource" + }, + { + "title": "Output State Checks", + "path": "acceptance-tests/state-checks/output" + }, + { + "title": "Custom State Checks", + "path": "acceptance-tests/state-checks/custom" + } + ] + }, + { + "title": "Query Checks", + "routes": [ + { + "title": "Overview", + "path": "acceptance-tests/query-checks" + }, + { + "title": "Query Result Checks", + "path": "acceptance-tests/query-checks/query-result" + }, + { + "title": "Custom Query Checks", + "path": "acceptance-tests/query-checks/custom" + } + ] + }, + { + "title": "Known Value Checks", + "routes": [ + { + "title": "Overview", + "path": "acceptance-tests/known-value-checks" + }, + { + "title": "Bool", + "path": "acceptance-tests/known-value-checks/bool" + }, + { + "title": "Custom", + "path": "acceptance-tests/known-value-checks/custom" + }, + { + "title": "Float32", + "path": "acceptance-tests/known-value-checks/float32" + }, + { + "title": "Float64", + "path": "acceptance-tests/known-value-checks/float64" + }, + { + "title": "Int32", + "path": "acceptance-tests/known-value-checks/int32" + }, + { + "title": "Int64", + "path": "acceptance-tests/known-value-checks/int64" + }, + { + "title": "List", + "path": "acceptance-tests/known-value-checks/list" + }, + { + "title": "Map", + "path": "acceptance-tests/known-value-checks/map" + }, + { + "title": "NotNull", + "path": "acceptance-tests/known-value-checks/not-null" + }, + { + "title": "Null", + "path": "acceptance-tests/known-value-checks/null" + }, + { + "title": "Number", + "path": "acceptance-tests/known-value-checks/number" + }, + { + "title": "Object", + "path": "acceptance-tests/known-value-checks/object" + }, + { + "title": "Set", + "path": "acceptance-tests/known-value-checks/set" + }, + { + "title": "String", + "path": "acceptance-tests/known-value-checks/string" + }, + { + "title": "Tuple", + "path": "acceptance-tests/known-value-checks/tuple" + } + ] + }, + { + "title": "Value Comparers", + "routes": [ + { + "title": "Overview", + "path": "acceptance-tests/value-comparers" + } + ] + }, + { + "title": "Sweepers", + "path": "acceptance-tests/sweepers" + }, + { + "title": "Terraform JSON Paths", + "path": "acceptance-tests/tfjson-paths" + }, + { + "title": "Terraform Configuration", + "path": "acceptance-tests/configuration" + }, + { + "title": "Ephemeral Resources", + "path": "acceptance-tests/ephemeral-resources" + }, + { + "title": "Environment Variables", + "path": "acceptance-tests/environment-variables" + }, + { + "title": "Continuous Integration", + "path": "acceptance-tests/continuous-integration" + } + ] + }, + { + "title": "Testing Patterns", + "path": "testing-patterns" + }, + { + "title": "Unit Testing", + "path": "unit-testing" + }, + { + "title": "Migrating from SDK", + "path": "migrating" + } +] diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/configuration.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/configuration.mdx new file mode 100644 index 0000000000..f18c2a9401 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/configuration.mdx @@ -0,0 +1,277 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Terraform Configuration' +description: >- + Terraform Configuration specifies the configuration to be used during an acceptance test at the TestStep level. Terraform variables define the values to be used + in conjunction with Terraform configuration. +--- + +# Terraform Configuration + +The configuration used during the execution of an acceptance test can be specified at the [TestStep](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep) level by populating one of the following mutually exclusive fields: + +* [TestStep.Config](#teststep-config) +* [TestStep.ConfigDirectory](#teststep-configdirectory) +* [TestStep.ConfigFile](#teststep-configfile) + +Terraform configuration can be used in conjunction with Terraform variables defined via [TestStep.ConfigVariables](/terraform/plugin/testing/acceptance-tests/configuration#teststep-configvariables). + +## TestStep Config + +The `TestStep.Config` field accepts a string containing valid Terraform configuration. + +In the following example, the `Config` field specifies a resource which is used in combination with `ExternalProviders` to specify the version and source for the provider: + +```go +func TestAccResourcePassword_UpgradeFromVersion3_2_0(t *testing.T) { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + VersionConstraint: "3.2.0", + Source: "hashicorp/random", + }, + }, + Config: `resource "random_password" "min" { + length = 12 + override_special = "!#@" + min_lower = 2 + min_upper = 3 + min_special = 1 + min_numeric = 4 + }`, + +``` + +## TestStep ConfigDirectory + +The `TestStep.ConfigDirectory` field accepts a [TestStepConfigFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigFunc) which is a function that accepts a [TestStepConfigRequest](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigRequest) and returns a string containing a path to a directory containing Terraform configuration files. The path can be a relative or absolute path. + +There are helper methods available for generating a [TestStepConfigFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigFunc) including: + +* [StaticDirectory(directory string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#StaticDirectory) +* [TestNameDirectory()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestNameDirectory) +* [TestStepDirectory()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepDirectory) + +~> **Note**: `TestStep.ExternalProviders` cannot be specified when using ConfigDirectory. It is expected that [required_providers](/terraform/language/providers/requirements#requiring-providers) are defined within the configuration files. + +Custom functions can be written and used in the `TestStep.ConfigDirectory` field as long as the function is a [TestStepConfigFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigFunc) type. + +### StaticDirectory + +The [StaticDirectory(directory string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#StaticDirectory) function accepts a string specifying a path to a directory containing Terraform configuration. + +For example: + +```go +func Test_ConfigDirectory_StaticDirectory(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + ConfigDirectory: config.StaticDirectory(`testdata/directory_containing_config`), + /* ... */ + }, + }, + }) +} +``` + +In this instance, the testing configuration is expected to be in the `testdata/directory_containing_config` directory relative to the file containing the `Test_ConfigDirectory_StaticDirectory` test. + +### TestNameDirectory + +The [TestNameDirectory()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestNameDirectory) function will use the name of the executing test to specify a path to a directory containing Terraform configuration. + +For example: + +```go +func Test_ConfigDirectory_TestNameDirectory(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + ConfigDirectory: config.TestNameDirectory(), + /* ... */ + }, + }, + }) +} +``` + +In this instance, the testing configuration is expected to be in the `testdata/Test_ConfigDirectory_TestNameDirectory` directory relative to the file containing the `Test_ConfigDirectory_TestNameDirectory` test. + +### TestStepDirectory + +The [TestStepDirectory()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepDirectory) function will use the name of the executing test and the current test step number to specify a path to a directory containing Terraform configuration. + +For example: + +```go +func Test_ConfigDirectory_TestStepDirectory(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + ConfigDirectory: config.TestStepDirectory(), + /* ... */ + }, + }, + }) +} +``` + +In this instance, because this is the first test step in the test, the testing configuration is expected to be in the `testdata/Test_ConfigDirectory_TestStepDirectory/1` directory relative to the file containing the `Test_ConfigDirectory_TestStepDirectory` test. + +## TestStep ConfigFile + +The `TestStep.ConfigFile` field accepts a [TestStepConfigFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigFunc) which is a function that accepts a [TestStepConfigRequest](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigRequest) and returns a string containing a path to a file containing Terraform configuration. The path can be a relative or absolute path. + +There are helper methods available for generating a [TestStepConfigFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigFunc) including: + +* [StaticFile(file string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#StaticFile) +* [TestNameFile(file string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestNameFile) +* [TestStepFile(file string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepFile) + +~> **Note**: `TestStep.ExternalProviders` cannot be specified when using `ConfigFile`. It is expected that [required_providers](/terraform/language/providers/requirements#requiring-providers) are defined within the configuration file. + +Custom functions can be written and used in the `TestStep.ConfigFile` field as long as the function is a [TestStepConfigFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigFunc) type. + +### StaticFile + +The [StaticFile(file string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#Staticfile) function accepts a string specifying a path to a file containing Terraform configuration. + +For example: + +```go +func Test_ConfigFile_StaticFile(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + ConfigFile: config.StaticFile(`testdata/directory_containing_config/main.tf`), + /* ... */ + }, + }, + }) +} +``` + +In this instance, the testing configuration is expected to be in the `testdata/directory_containing_config/main.tf` file relative to the file containing the `Test_ConfigFile_StaticFile` test. + +### TestNameFile + +The [TestNameFile(file string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestNameFile) function will use the name of the executing test to specify a path to a file containing Terraform configuration. + +For example: + +```go +func Test_ConfigFile_TestNameFile(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + ConfigFile: config.TestNameFile("main.tf"), + /* ... */ + }, + }, + }) +} +``` + +In this instance, the testing configuration is expected to be in the `testdata/Test_ConfigFile_TestNameFile` directory relative to the file containing the `Test_ConfigFile_TestNameFile` test. + +### TestStepFile + +The [TestStepFile(file string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepFile) function will use the name of the executing test and the current test step number to specify a path to a file containing Terraform configuration. + +For example: + +```go +func Test_ConfigFile_TestStepFile(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + ConfigFile: config.TestStepFile("main.tf"), + /* ... */ + }, + }, + }) +} +``` + +In this instance, because this is the first test step in the test, the testing configuration is expected to be in the `testdata/Test_ConfigFile_TestStepFile/1/main.tf` file relative to the file containing the `Test_ConfigDirectory_TestNameDirectory` test. + +## TestStep ConfigVariables + +[Terraform input variables](https://developer.hashicorp.com/terraform/language/values/variables) allow customization of a Terraform configuration without altering the configuration itself. + +The `TestStep.ConfigVariables` field accepts a [Variables](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#Variables) type which is a key-value map of string to [Variable](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#Variable). + +The following functions return types implementing [Variable](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#Variable) that correlate with the [Terraform type constraints](https://developer.hashicorp.com/terraform/language/values/variables#type-constraints): + +* [BoolVariable(value bool)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#BoolVariable) +* [FloatVariable[T constraints.Float](value T)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#FloatVariable) +* [IntegerVariable[T constraints.Integer](value T)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#IntegerVariable) +* [func ListVariable(value ...Variable)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#ListVariable) +* [MapVariable(value map[string]Variable)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#MapVariable) +* [ObjectVariable(value map[string]Variable)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#ObjectVariable) +* [SetVariable(value ...Variable)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#SetVariable) +* [StringVariable(value string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#StringVariable) +* [TupleVariable(value ...Variable)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TupleVariable) + +The following example shows the usage of `TestStep.ConfigVariables` in conjunction with `TestStep.ConfigFile`: + +```go +func Test_ConfigFile_TestNameFile(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + ConfigFile: config.TestNameFile("random.tf"), + ConfigVariables: config.Variables{ + "length": config.IntegerVariable(8), + "numeric": config.BoolVariable(false), + }, + /* ... */ + }, + }, + }) +} +``` + +The configuration would be expected to be in the `testdata/Test_ConfigFile_TestNameFile/random.tf` file, for example: + +```terraform +terraform { + required_providers { + random = { + source = "registry.terraform.io/hashicorp/random" + version = "3.5.1" + } + } +} + +provider "random" {} + +resource "random_password" "test" { + length = var.length + numeric = var.numeric +} + +variable "length" { + type = number +} + +variable "numeric" { + type = bool +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/continuous-integration.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/continuous-integration.mdx new file mode 100644 index 0000000000..290f70226b --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/continuous-integration.mdx @@ -0,0 +1,149 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Continuous Integration' +description: +--- + +# Acceptance Tests: Continuous Integration + +## GitHub Actions Workflow + +If using [GitHub](https://github.com/), run acceptance testing via [GitHub +Actions](https://github.com/features/actions). Other continuous integration +runners are also supported. + +Ensure the [GitHub Organization settings for GitHub +Actions](https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization) +and [GitHub Repository settings for GitHub +Actions](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository) +allows running workflows and allows the `actions/checkout`, `actions/setup-go`, +and `hashicorp/setup-terraform` actions. + +Create a [GitHub Actions +workflow](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions) +file, such as `.github/workflows/test.yaml`, that does the following: + +- Runs when pull requests are submitted or on [other + events](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows) +as appropriate. +- Uses [`actions/checkout`](https://github.com/actions/checkout) to checkout + the provider codebase. +- Uses [`actions/setup-go`](https://github.com/actions/setup-go) to install Go. +- Uses + [`hashicorp/setup-terraform`](https://github.com/hashicorp/setup-terraform) +to install Terraform CLI. +- Runs the `go test` command with the appropriate environment variables and + flags. + +Use the +[`matrix`](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix) +strategy for more advanced configuration, such as running acceptance testing +against multiple Terraform CLI versions. + +The following example workflow runs acceptance testing for the provider using +the latest patch versions of the Go version in the `go.mod` file and Terraform +CLI 1.11: + +```yaml +--- +name: Terraform Provider Tests + +on: + pull_request: + paths: + - '.github/workflows/test.yaml' + - '**.go' + +permissions: + # Permission for checking out code + contents: read + +jobs: + acceptance: + name: Acceptance Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version-file: 'go.mod' + - uses: hashicorp/setup-terraform@v2 + with: + terraform_version: '1.11.*' + terraform_wrapper: false + - run: go test -v -cover ./... + env: TF_ACC: '1'r + + unit: + name: Unit Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version-file: 'go.mod' + - run: go test -v -cover ./... ``` +``` + +The following example workflow runs acceptance testing for the provider using +the latest patch versions of Go version in the `go.mod` file and Terraform CLI +0.12 through 1.11: + +```yaml +name: Terraform Provider Tests + +on: + pull_request: + paths: + - '.github/workflows/test.yaml' + - '**.go' + +permissions: + # Permission for checking out code + contents: read + +jobs: + acceptance: + name: Acceptance Tests (Terraform ${{ matrix.terraform-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + terraform-version: + - '0.12.*' + - '0.13.*' + - '0.14.*' + - '0.15.*' + - '1.0.*' + - '1.1.*' + - '1.2.*' + - '1.3.*' + - '1.4.*' + - '1.5.*' + - '1.6.*' + - '1.7.*' + - '1.8.*' + - '1.9.*' + - '1.10.*' + - '1.11.*' + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version-file: 'go.mod' + - uses: hashicorp/setup-terraform@v2 + with: + terraform_version: ${{ matrix.terraform-version }} + terraform_wrapper: false + - run: go test -v -cover ./... + env: + TF_ACC: '1' + unit: + name: Unit Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version-file: 'go.mod' + - run: go test -v -cover ./... +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/environment-variables.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/environment-variables.mdx new file mode 100644 index 0000000000..23eee51940 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/environment-variables.mdx @@ -0,0 +1,55 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Environment Variables' +description: |- + Reference for environment variables that control aspects of acceptance test + execution. +--- + +# Acceptance Testing: Environment Variables + +A number of environment variables are available to control aspects of +acceptance test execution. + +| Environment Variable Name | Default | Description | +|------------------------------|-------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `TF_ACC` | N/A | Set to any value to enable acceptance testing via the [`helper/resource.ParallelTest()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#ParallelTest) and [`helper/resource.Test()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#Test) functions. | +| `TF_ACC_PROVIDER_HOST`: | `registry.terraform.io` | Set the hostname of the provider under test, such as `example.com` in the `example.com/myorg/myprovider` provider source address. This is only required if any [`TestStep.Config`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep.Config) specifies a provider source address, such as in the [`terraform` configuration block `required_providers` attribute](/terraform/language/settings#specifying-provider-requirements). | +| `TF_ACC_PROVIDER_NAMESPACE` | `hashicorp` | Set the namespace of the provider under test, such as `myorg` in the `registry.terraform.io/myorg/myprovider` provider source address. This is only required if any [`TestStep.Config`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep.Config) specifies a provider source address, such as in the [`terraform` configuration block `required_providers` attribute](/terraform/language/settings#specifying-provider-requirements). | +| `TF_ACC_STATE_LINEAGE` | N/A | Set to `1` to enable state lineage debug logs, which are normally suppressed during acceptance testing. | +| `TF_ACC_TEMP_DIR` | Operating system specific via [`os.TempDir()`](https://pkg.go.dev/os#TempDir) | Set a temporary directory used for testing files and installing Terraform CLI, if installation is required. | +| `TF_ACC_TERRAFORM_PATH` | N/A | Set the path to a Terraform CLI binary on the local filesystem to be used during testing. It must be executable. If not found and `TF_ACC_TERRAFORM_VERSION` is not set, an error is returned. | +| `TF_ACC_TERRAFORM_VERSION` | N/A | Set the exact version of Terraform CLI to automatically install into `TF_ACC_TEMP_DIR`. For example, `1.1.6` or `v1.0.11`. | +| `TF_ACC_PERSIST_WORKING_DIR` | N/A | Set to any value to enable persisting the working directory and the files generated during execution of each `TestStep`. The location of each directory is written to the test output for each `TestStep` when the `go test -v` (verbose) flag is provided. | + +### Logging Environment Variables + +A number of environment variables available to control logging aspects during acceptance test execution. Some of these modify or replace the production behaviors defined in [managing provider log output](/terraform/plugin/log/managing) and [debugging Terraform](/terraform/internals/debugging). + +#### Logging Levels + +| Environment Variable Name | Default | Description | +|---------------------------|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `TF_ACC_LOG` | N/A | Set the `TF_LOG` environment variable used by Terraform CLI while testing. If set, overrides `TF_LOG_CORE`. Use `TF_LOG_CORE` and `TF_LOG_PROVIDER` to configure separate levels for Terraform CLI logging. | +| `TF_LOG` | N/A | Set the log level for the Go standard library `log` package. If set to any level, sets the `TRACE` log level for any SDK and provider logs written by [`terraform-plugin-log`](/terraform/plugin/log/writing). Use the `TF_LOG_SDK*` and `TF_LOG_PROVIDER_*` environment variables described in [managing log output](/terraform/plugin/log/managing) to decrease or disable SDK and provider logs written by [`terraform-plugin-log`](/terraform/plugin/log/writing). Use `TF_ACC_LOG`, `TF_LOG_CORE`, or `TF_LOG_PROVIDER` environment variables to set the logging levels used by Terraform CLI while testing. | +| `TF_LOG_CORE` | `TF_ACC_LOG` | Set the `TF_LOG_CORE` environment variable used by Terraform CLI logging of graph operations and other core functionality while testing. If `TF_ACC_LOG` is set, this setting has no effect. Use `TF_LOG_PROVIDER` to configure a separate level for Terraform CLI logging of external providers while testing (e.g. defined by the `TestCase` or `TestStep` type `ExternalProviders` field). | +| `TF_LOG_PROVIDER` | `TF_ACC_LOG` | Set the `TF_LOG_PROVIDER` environment variable used by Terraform CLI logging of external providers while testing (e.g. defined by the `TestCase` or `TestStep` type `ExternalProviders` field). If set, overrides `TF_ACC_LOG`. Use `TF_LOG_CORE` to configure a separate level for Terraform CLI logging of graph operations and other core functionality while testing. | + +#### Logging Output + +By default, there is no logging output when running the `go test` command. Use one of the below environment variables to output logs to the local filesystem or use the `go test` command `-v` (verbose) flag to view logging without writing file(s). + +| Environment Variable Name | Default | Description | +|---------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `TF_ACC_LOG_PATH` | N/A | Set a file path for all logs during testing. Use `TF_LOG_PATH_MASK` to configure individual log files per test. | +| `TF_LOG_PATH_MASK` | N/A | Set a file path containing the string `%s`, which is replaced with the test name, to write a separate log file per test. Use `TF_ACC_LOG_PATH` to configure a single log file for all tests. | + +The logs associated with each test can output across incorrect files as each +new test starts if the provider is using the Go standard library [`log` +package](https://pkg.go.dev/log) for logging, acceptance testing that uses +[`helper/resource.ParallelTest()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#ParallelTest), +and `TF_LOG_PATH_MASK`. To resolve this issue, choose one of the following +approaches: + +* Use [`terraform-plugin-log`](/terraform/plugin/log/writing) based logging. Each logger will be correctly associated with each test name output. +* Wrap testing execution so that each test is individually executed with `go test`. Since each `go test` process will have its own `log` package output handling,logging will be correctly associated with each test name output. +* Replace [`helper/resource.ParallelTest()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#ParallelTest) with [`helper/resource.Test()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#Test) and ensure [`(*testing.T).Parallel()`](https://pkg.go.dev/testing#T.Parallel) is not called in tests. This serializes all testing so each test will be associated with each test name output. diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/ephemeral-resources.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/ephemeral-resources.mdx new file mode 100644 index 0000000000..36c6fea291 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/ephemeral-resources.mdx @@ -0,0 +1,271 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Ephemeral Resources' +description: >- + Guidance on how to test ephemeral resources and data. +--- + + + +Ephemeral resource support is in technical preview and offered without compatibility promises until Terraform 1.10 is generally available. + + + +# Ephemeral Resources + +[Ephemeral Resources](/terraform/language/v1.10.x/resources/ephemeral) are an abstraction that allows Terraform to reference external data, similar to [data sources](/terraform/language/data-sources), without persisting that data to plan or state artifacts. The `terraform-plugin-testing` module exclusively uses Terraform plan and state artifacts for it's assertion-based test checks, like [plan checks](/terraform/plugin/testing/acceptance-tests/plan-checks) or [state checks](/terraform/plugin/testing/acceptance-tests/state-checks), which means that ephemeral resource data cannot be asserted using these methods alone. + +The following is a test for a hypothetical `examplecloud_secret` ephemeral resource which is referenced by a provider configuration that has a single managed resource. For this test to pass, the ephemeral `examplecloud_secret` resource must return valid data, specifically a kerberos `username`, `password`, and `realm`, which are used to configure the `dns` provider and create a DNS record via the `dns_a_record_set` managed resource. + +```go +func TestExampleCloudSecret_DnsKerberos(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // Ephemeral resources are only available in 1.10 and later + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_10_0), + }, + ExternalProviders: map[string]resource.ExternalProvider{ + "dns": { + Source: "hashicorp/dns", + }, + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "examplecloud": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + # Retrieves a secret containing user kerberos configuration + ephemeral "examplecloud_secret" "krb" { + name = "example_kerberos_user" + } + + # Ephemeral data can be referenced in provider configuration + provider "dns" { + update { + server = "ns.example.com" + gssapi { + realm = ephemeral.examplecloud_secret.krb.secret_data.realm + username = ephemeral.examplecloud_secret.krb.secret_data.username + password = ephemeral.examplecloud_secret.krb.secret_data.password + } + } + } + + # If we can create this DNS record successfully, then the ephemeral resource returned valid data. + resource "dns_a_record_set" "record_set" { + zone = "example.com." + addresses = [ + "192.168.0.1", + "192.168.0.2", + "192.168.0.3", + ] + } + `, + }, + }, + }) +} +``` + +See the Terraform [ephemeral documentation](http://localhost:3000/terraform/language/v1.10.x/resources/ephemeral#referencing-ephemeral-resources) for more details on where ephemeral data can be referenced in configurations. + +## Testing ephemeral data with `echo` provider + +Test assertions on [result data](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#OpenResponse.Result) returned by an ephemeral resource during [`Open`](/terraform/plugin/framework/ephemeral-resources/open) can be arranged using the `echoprovider` package. + +This package contains a [Protocol V6 Terraform Provider](/terraform/plugin/terraform-plugin-protocol#protocol-version-6) named `echo`, with a single managed resource also named `echo`. Using the `echo` provider configuration and an instance of the managed resource, ephemeral data can be "echoed" from the provider configuration into Terraform state, where it can be referenced in test assertions with [state checks](/terraform/plugin/testing/acceptance-tests/state-checks). For example: + +```terraform +ephemeral "examplecloud_secret" "krb" { + name = "example_kerberos_user" +} + +provider "echo" { + # Provide the ephemeral data we want to run test assertions against + data = ephemeral.examplecloud_secret.krb.secret_data +} + +# The ephemeral data will be echoed into state +resource "echo" "test_krb" {} +``` + + + +This provider is designed specifically to be used as a utility for acceptance testing ephemeral data and is only available via the `terraform-plugin-testing` Go module. + + + +### Using `echo` provider in acceptance tests + +First, we include the `echo` provider using the [`echoprovider.NewProviderServer`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/echoprovider#NewProviderServer) function in the `(TestCase).ProtoV6ProviderFactories` property: + +```go +import ( + // .. other imports + + "github.com/hashicorp/terraform-plugin-testing/echoprovider" +) + +func TestExampleCloudSecret(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // Ephemeral resources are only available in 1.10 and later + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_10_0), + }, + // Include the provider we want to test: `examplecloud` + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "examplecloud": providerserver.NewProtocol5WithError(New()), + }, + // Include `echo` as a v6 provider from `terraform-plugin-testing` + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "echo": echoprovider.NewProviderServer(), + }, + Steps: []resource.TestStep{ + // .. test step configurations can now use the `echo` and `examplecloud` providers + }, + }) +} +``` + +After including both providers, our test step `Config` references the ephemeral data from `examplecloud_secret` in the `echo` provider configuration `data` attribute: + +```go +func TestExampleCloudSecret(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // .. test case setup + + Steps: []resource.TestStep{ + { + Config: ` + ephemeral "examplecloud_secret" "krb" { + name = "example_kerberos_user" + } + + provider "echo" { + data = ephemeral.examplecloud_secret.krb.secret_data + } + + resource "echo" "test_krb" {} + `, + }, + }, + }) +} +``` + +The `echo.test_krb` managed resource has a single computed `data` attribute, which will contain the provider configuration `data` results. This data is then used in assertions with the [state check](/terraform/plugin/testing/acceptance-tests/state-checks) functionality: + +```go +func TestExampleCloudSecret(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // .. test case setup + + Steps: []resource.TestStep{ + { + Config: ` + ephemeral "examplecloud_secret" "krb" { + name = "example_kerberos_user" + } + + provider "echo" { + data = ephemeral.examplecloud_secret.krb.secret_data + } + + resource "echo" "test_krb" {} + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("echo.test_krb", tfjsonpath.New("data").AtMapKey("realm"), knownvalue.StringExact("EXAMPLE.COM")), + statecheck.ExpectKnownValue("echo.test_krb", tfjsonpath.New("data").AtMapKey("username"), knownvalue.StringExact("john-doe")), + statecheck.ExpectKnownValue("echo.test_krb", tfjsonpath.New("data").AtMapKey("password"), knownvalue.StringRegexp(regexp.MustCompile(`^.{12}$`))), + }, + }, + }, + }) +} +``` + +`data` is a `dynamic` attribute, so whatever [type](/terraform/language/expressions/types) you pass in will be directly reflected in the managed resource `data` attribute. In the config above, we reference an object (`secret_data`) from the ephemeral resource instance, so the resulting type of `echo.test_krb.data` is also an `object`. + +You can also reference the entire ephemeral resource instance for assertions, rather than specific attributes: + +```go +func TestExampleCloudSecret(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // .. test case setup + + Steps: []resource.TestStep{ + { + Config: ` + ephemeral "examplecloud_secret" "krb" { + name = "example_kerberos_user" + } + + provider "echo" { + data = ephemeral.examplecloud_secret.krb + } + + resource "echo" "test_krb" {} + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("echo.test_krb", tfjsonpath.New("data").AtMapKey("name"), knownvalue.StringExact("example_kerberos_user")), + }, + }, + }, + }) +} +``` + +### Caveats with `echo` provider + +Since data produced by an ephemeral resource is allowed to change between plan/apply operations, the `echo` resource has special handling to allow this data to be used in the `terraform-plugin-testing` Go module without producing confusing error messages: + +* During plan, if the `echo` resource is being created, the `data` attribute will always be marked as unknown. +* During plan, if the `echo` resource already exists and is not being destroyed, prior state will always be fully preserved regardless of changes to the provider configuration. This essentially means an instance of the `echo` resource is immutable. +* During refresh, the prior state of the `echo` resource is always returned, regardless of changes to the provider configuration. + +Due to this special handling, if multiple test steps are required for testing data, provider developers should create new instances of `echo` for each new test step, for example: + +```go +func TestExampleCloudSecret(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // .. test case setup + + Steps: []resource.TestStep{ + { + Config: ` + ephemeral "examplecloud_secret" "krb" { + name = "user_one" + } + + provider "echo" { + data = ephemeral.examplecloud_secret.krb + } + + # First test object -> 1 + resource "echo" "test_krb_one" {} + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("echo.test_krb_one", tfjsonpath.New("data").AtMapKey("name"), knownvalue.StringExact("user_one")), + }, + }, + { + Config: ` + ephemeral "examplecloud_secret" "krb" { + name = "user_two" + } + + provider "echo" { + data = ephemeral.examplecloud_secret.krb + } + + # New test object -> 2 + resource "echo" "test_krb_two" {} + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("echo.test_krb_two", tfjsonpath.New("data").AtMapKey("name"), knownvalue.StringExact("user_two")), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/import-mode.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/import-mode.mdx new file mode 100644 index 0000000000..a3622b1722 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/import-mode.mdx @@ -0,0 +1,94 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Import mode' +description: |- + _Import_ mode is used for testing resource functionality to import existing + infrastructure into a Terraform statefile, using real Terraform import logic. +--- + +# Acceptance Tests: Import mode + +_Import_ is the workflow that brings existing infrastructure into Terraform, +without altering the infrastructure itself. For reference information about the +_Import_ workflow: [Terraform +CLI](https://developer.hashicorp.com/terraform/cli/import), +[Framework](https://developer.hashicorp.com/terraform/plugin/framework/resources/import), +[SDKv2](https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/import). + +In provider acceptance tests, _Import_ mode is used for testing resource +functionality to import existing infrastructure into a Terraform statefile, +using real Terraform import functionality. + +At its core, _Import_ mode runs the _Import_ workflow and checks: does a +resource added by _Import_ match a resource added by _Lifecycle_ methods (i.e. +_Create_)? + +In common testing terminology, _Import_ mode uses a resource added to Terraform +by _Create_ as the _expected result_. + +_Import_ mode uses a resource added to Terraform by _Import_ as the _actual +result_. + +_Import_ mode runs a deep comparison of the two data structures. The test step +passes only if the two data structures match. + +## Examples + +### Testing the `terraform import` workflow + +```go +func TestImportCommand(t *testing.T) { + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: providerFactories, + Steps: []r.TestStep{ + { + Config: `resource "examplecloud_thing" "test" {}`, + }, + { + ImportState: true, + ResourceName: "examplecloud_thing.test", + ImportStateVerify: true, + }, + }, + }) +} +``` + +### Testing the plannable import workflow + +```go +func TestImportBlockWithID(t *testing.T) { + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: providerFactories, + Steps: []r.TestStep{ + { + Config: `resource "examplecloud_thing" "test" {}`, + }, + { + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ResourceName: "examplecloud_thing.test", + }, + }, + }) +} +``` + +#### Testing the plannable import workflow using a managed resource identity + +```go +func TestImportBlockWithResourceIdentity(t *testing.T) { + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: providerFactories, + Steps: []r.TestStep{ + { + Config: `resource "examplecloud_thing" "test" {}`, + }, + { + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ResourceName: "examplecloud_thing.test", + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/index.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/index.mdx new file mode 100644 index 0000000000..9cea93edb8 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/index.mdx @@ -0,0 +1,168 @@ +--- +page_title: Plugin Development - Acceptance Testing +description: |- + Terraform includes a framework for constructing acceptance tests that + imitate applying one or more configuration files. +--- + +# Acceptance Tests + +Acceptance tests for Terraform providers are a feature of the +[`terraform-plugin-testing`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing) +framework. The testing framework uses standard Go features such as the `go +test` command. It runs a local Terraform binary to perform real plan, apply, +refresh, and destroy operations, and enables developers to make assertions +about what happens during those actions. + +When testing the happy path, developers can make assertions about the data +stored in state after successfully provisioning a resource. When testing +unhappy paths, developers are able to make assertions about errors returned by +Terraform, non-empty plans, and the actual cloud resources created by +Terraform. + +~> **Note**: Acceptance tests can create and destroy real infrastructure +resources; as a result, they can incur expenses and consume quotas. The testing +framework provides a way to check that all resources created by tests are +destroyed. Refer to the [requirements and +recommendations](#requirements-and-recommendations) section. + +## Test files + +Terraform follows many of the Go programming language conventions with regards +to testing. Tests are placed in a file that matches the file under test, with +an added `_test.go` suffix. Here's an example file structure: + +``` +terraform-plugin-example/ +├── provider.go +├── provider_test.go +├── example/ +│ ├── resource_example_compute.go +│ ├── resource_example_compute_test.go +``` + +To create an acceptance test in the example `resource_example_compute_test.go` +file, add a test function with name that begins with `TestAcc`. + +``` +import "testing" + +func TestAccExampleComputeResource(*testing.T) { +} +``` + +## Requirements and recommendations + +Acceptance tests have the following requirements: + +- **[Go](https://go.dev/)**: The most recent stable version. +- **Terraform CLI**: Version 0.12.26 or later. +- **Provider Access**: Network or system access to the provider and any + resources being tested. +- **Provider Credentials**: Authorized credentials to the provider and any + resources being tested. +- **`TF_ACC` environment variable**: Set to any value. Prevents developers from + incurring unintended charges when running other Go tests. + +We also recommend the following when running acceptance tests: + +- **A separate Account**: Use a separate provider account or namespace for + acceptance testing. This prevents Terraform from unexpectedly modifying or +destroying infrastructure due to code or testing issues. +- **Previous Terraform CLI Installation**: Install Terraform CLI either into + the operating system `PATH` or use the `TF_ACC_TERRAFORM_PATH` environment +variable prior to running acceptance tests. Otherwise, the testing framework +will download and install the latest Terraform CLI version into a temporary +directory for every test invocation. Refer to the [Terraform CLI Installation +Behaviors](#terraform-cli-installation-behaviors) section for details. + +Each provider may have additional requirements and setup recommendations. Refer +to the provider's codebase for more details. + +## Running Acceptance Tests + +Use the [`go test`](https://pkg.go.dev/cmd/go/internal/test) command to run +acceptance tests. You can run the acceptance tests on any environment capable +of running `go test`, such as a local workstation [command +line](#command-line-workflow) or a [continuous integration +runner](/terraform/plugin/testing/acceptance-tests/continuous-integration), +such as GitHub Actions. + +### Command Line Workflow + +This example will execute all available acceptance tests in a provider +codebase: + +```shell +TF_ACC=1 go test -v ./... +``` + +#### Makefiles + +test to make sure the gnu domain is skipped + +For convenience, provider codebases can place common tasks in a +[Makefile](https://www.gnu.org/software/make/manual/make.html#Makefiles). + +This example defines a `testacc` target, which sets `TF_ACC` and the verbose +(`-v`) flag. + +```make +testacc: + TF_ACC=1 go test -v ./... +``` + +To run acceptance tests: + +```shell +make testacc +``` + +-> **Note**: Refer to the [Environment +Variables](/terraform/plugin/testing/acceptance-tests/environment-variables) +section for more details about behaviors and valid configurations. + +### Terraform CLI Installation Behaviors + +The testing framework implements the following Terraform CLI discovery and +installation behaviors: + +- If the `TF_ACC_TERRAFORM_PATH` environment variable is set, the framework + will use that Terraform CLI binary if it exists and is executable. If the +framework cannot find the binary or it is not executable, the framework returns +an error unless the `TF_ACC_TERRAFORM_VERSION` environment variable is also +set. +- If the `TF_ACC_TERRAFORM_VERSION` environment variable is set, the framework + will install and use that Terraform CLI version. +- If both the `TF_ACC_TERRAFORM_PATH` and `TF_ACC_TERRAFORM_VERSION` + environment variables are unset, the framework will search for the Terraform +CLI binary based on the operating system `PATH`. If the framework cannot find +the specified binary, it installs the latest available Terraform CLI binary. + +## Troubleshooting + +This section lists common errors encountered during testing. + +### Unrecognized remote plugin message + +``` +terraform failed: exit status 1 + + stderr: + + Error: Failed to instantiate provider "random" to obtain schema: +Unrecognized remote plugin message: --- FAIL: TestAccResourceID (4.28s) + + This usually means that the plugin is either invalid or simply needs to +be recompiled to support the latest protocol. +``` + +This error indicates that the provider server could not connect to Terraform +Core. Verify that the output of `terraform version` is v0.12.26 or above. + +## Next Steps + +The next step is to create _Test Cases_ using Terraform's testing +framework.build and verify real infrastructure. + +Proceed to [_Test Cases_](/terraform/plugin/testing/acceptance-tests/testcase). diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/bool.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/bool.mdx new file mode 100644 index 0000000000..51c08f5ed8 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/bool.mdx @@ -0,0 +1,77 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Bool Value Checks for use with Plan and State Checks. +--- + +# Bool Known Value Checks + +The known value checks that are available for bool values are: + +* [Bool](/terraform/plugin/testing/acceptance-tests/known-value-checks/bool#bool-check) +* [BoolFunc](/terraform/plugin/testing/acceptance-tests/known-value-checks/bool#boolfunc-check) + +## `Bool` Check + +The [Bool](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Bool) check tests that a resource attribute, or output value has an exactly matching bool value. + +Example usage of [Bool](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Bool) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Bool(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed boolean attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Bool(true), + ), + }, + }, + }, + }, + }) +} +``` + +## `BoolFunc` Check + +The [BoolFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#BoolFunc) check allows defining a custom function to validate whether the bool value of a resource attribute or output satisfies specific conditions. + +Example usage of [BoolFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#BoolFunc) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_BoolFunc(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a boolean attribute named "configurable_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("configurable_attribute"), + knownvalue.BoolFunc(func(v bool) error { + if !v { + return fmt.Errorf("expected true, got %t", v) + } + return nil + }), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/custom.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/custom.mdx new file mode 100644 index 0000000000..ef92d4e806 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/custom.mdx @@ -0,0 +1,76 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Custom Value Checks for use with Plan Checks. +--- + +# Custom Known Value Checks + +Custom known value checks can be created by implementing the [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) interface. + +```go +type Check interface { + CheckValue(value any) error + String() string +} +``` + +For example, a `StringContains` implementation could look as follows: + +```go +var _ knownvalue.Check = stringContains{} + +type stringContains struct { + value string +} + +func (v stringContains) CheckValue(other any) error { + otherVal, ok := other.(string) + + if !ok { + return fmt.Errorf("expected string value for StringContains check, got: %T", other) + } + + if !strings.Contains(otherVal, v.value) { + return fmt.Errorf("expected string %q to contain %q for StringContains check", otherVal, v.value) + } + + return nil +} + +func (v stringContains) String() string { + return v.value +} + +func StringContains(value string) stringContains { + return stringContains{ + value: value, + } +} +``` + +## `CheckValue` Method Implementation + +The `other` parameter passed to the `CheckValue` method is one of the following types: + +* bool +* map[string]any +* []any +* string + +-> **Note:** Numerical values will be of type `json.Number`, with an underlying type of `string`. + +Refer to the following built-in known value checks for implementations that handle the different types that can be passed to the `CheckValue` method in the `other` parameter: + +* [Bool](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Bool) +* [Float32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float32Exact) +* [Float64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float64Exact) +* [Int32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int32Exact) +* [Int64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int64Exact) +* [ListExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListExact) +* [MapExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapExact) +* [NumberExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NumberExact) +* [ObjectExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectExact) +* [SetExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetExact) +* [StringExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringExact) +* [TupleExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#TupleExact) diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/float32.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/float32.mdx new file mode 100644 index 0000000000..613e69a217 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/float32.mdx @@ -0,0 +1,77 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Float32 Value Checks for use with Plan and State Checks. +--- + +# Float32 Known Value Checks + +The known value checks that are available for float32 values are: + +* [Float32Exact](/terraform/plugin/testing/acceptance-tests/known-value-checks/float32#float32exact-check) +* [Float32Func](/terraform/plugin/testing/acceptance-tests/known-value-checks/float32#float32func-check) + +## `Float32Exact` Check + +The [Float32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float32Exact) check tests that a resource attribute, or output value has an exactly matching float32 value. + +Example usage of [Float32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float32Exact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Float32(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed float32 attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Float32Exact(1.23), + ), + }, + }, + }, + }, + }) +} +``` + +## `Float32Func` Check + +The [Float32Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float32Func) check allows defining a custom function to validate whether the float32 value of a resource attribute or output satisfies specific conditions. + +Example usage of [Float32Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float32Func) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_Float32Func(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a float32 attribute named "configurable_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("configurable_attribute"), + knownvalue.Float32Func(func(v float32) error { + if v > 1.0 && v < 5.0 { + return fmt.Errorf("value must be between 1.0 and 5.0") + } + return nil + }), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/float64.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/float64.mdx new file mode 100644 index 0000000000..04294ebb9f --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/float64.mdx @@ -0,0 +1,77 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Float64 Value Checks for use with Plan and State Checks. +--- + +# Float64 Known Value Checks + +The known value checks that are available for float64 values are: + +* [Float64Exact](/terraform/plugin/testing/acceptance-tests/known-value-checks/float64#float64exact-check) +* [Float64Func](/terraform/plugin/testing/acceptance-tests/known-value-checks/float64#float64func-check) + +## `Float64Exact` Check + +The [Float64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float64Exact) check tests that a resource attribute, or output value has an exactly matching float64 value. + +Example usage of [Float64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float64Exact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Float64(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed float64 attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Float64Exact(1.23), + ), + }, + }, + }, + }, + }) +} +``` + +## `Float64Func` Check + +The [Float64Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float64Func) check allows defining a custom function to validate whether the float64 value of a resource attribute or output satisfies specific conditions. + +Example usage of [Float64Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float64Func) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_Float64Func(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a float64 attribute named "configurable_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("configurable_attribute"), + knownvalue.Float64Func(func(v float64) error { + if v > 1.0 && v < 5.0 { + return fmt.Errorf("value must be between 1.0 and 5.0") + } + return nil + }), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx new file mode 100644 index 0000000000..569f6b5b6b --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx @@ -0,0 +1,56 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + How to use known values in the testing module. + Known values define an expected type, and value for a resource attribute, or output value in a Terraform plan or state for use in Plan Checks or State Checks. +--- + +# Known Value Checks + +Known Value Checks are for use in conjunction with [Plan Checks](/terraform/plugin/testing/acceptance-tests/plan-checks), and [State Checks](/terraform/plugin/testing/acceptance-tests/state-checks) which leverage the [terraform-json](https://pkg.go.dev/github.com/hashicorp/terraform-json) representation of a Terraform plan. + +## Usage + +Example uses in the testing module include: + +- **Plan Checks**: The [`ExpectKnownValue()`](/terraform/plugin/testing/acceptance-tests/plan-checks/resource#expectknownvalue-plan-check), [`ExpectKnownOutputValue()`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalue-plan-check) and [`ExpectKnownOutputValueAtPath()`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalueatpath-plan-check) [built-in plan checks](/terraform/plugin/testing/acceptance-tests/plan-checks) use known value checks for asserting whether a specific resource attribute, or output value has a particular type, and value. +- **State Checks**: The [`ExpectKnownValue()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#expectknownvalue-state-check), [`ExpectKnownOutputValue()`](/terraform/plugin/testing/acceptance-tests/state-checks/output#expectknownoutputvalue-state-check) and [`ExpectKnownOutputValueAtPath()`](/terraform/plugin/testing/acceptance-tests/state-checks/output#expectknownoutputvalueatpath-state-check) [built-in state checks](/terraform/plugin/testing/acceptance-tests/state-checks) use known value checks for asserting whether a specific resource attribute, or output value has a particular type, and value. + +## Using a Known Value Check + +The known value check types are implemented within the `terraform-plugin-testing` module in the [`knownvalue` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue). Known value checks are instantiated by calling the relevant constructor function. + +```go +knownvalue.Bool(true) +``` + +For known value checks that represent collections, or objects, nesting of known value checks can be used to define a "composite" known value check for use in asserting against a resource attribute, or output value that contains other values. + +```go +knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("value1"), + knownvalue.StringExact("value2"), +}) +``` + +## Known Value Check Types + +The following table shows the correspondence between [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types, and attributes. + +| Known Value Check Type | Framework Attribute Type | SDKv2 Attribute Type | +|------------------------------------------------------------------------------------------------------|---------------------------|----------------------| +| [Bool Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/bool) | `schema.BoolAttribute` | `schema.TypeBool` | +| [Float32 Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/float32) | `schema.Float32Attribute` | `schema.TypeFloat` | +| [Float64 Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/float64) | `schema.Float64Attribute` | `schema.TypeFloat` | +| [Int32 Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/int32) | `schema.Int32Attribute` | `schema.TypeInt` | +| [Int64 Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/int64) | `schema.Int64Attribute` | `schema.TypeInt` | +| [List Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/list) | `schema.ListAttribute` | `schema.TypeList` | +| [Map Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/map) | `schema.MapAttribute` | `schema.TypeMap` | +| [NotNull Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/not-null) | All | All | +| [Null Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/null) | All | All | +| [Number Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/number) | `schema.NumberAttribute` | N/A | +| [Object Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/object) | `schema.ObjectAttribute` | N/A | +| [Set Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/set) | `schema.SetAttribute` | `schema.TypeSet` | +| [String Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/string) | `schema.StringAttribute` | `schema.TypeString` | +| [Tuple Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/tuple) | `schema.DynamicAttribute` | N/A | + diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/int32.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/int32.mdx new file mode 100644 index 0000000000..254b28aed6 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/int32.mdx @@ -0,0 +1,77 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Int32 Value Checks for use with Plan and State Checks. +--- + +# Int32 Known Value Checks + +The known value checks that are available for int32 values are: + +* [Int32Exact](/terraform/plugin/testing/acceptance-tests/known-value-checks/int32#int32exact-check) +* [Int32Func](/terraform/plugin/testing/acceptance-tests/known-value-checks/int32#int32func-check) + +## `Int32Exact` Check + +The [Int32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int32Exact) check tests that a resource attribute, or output value has an exactly matching int32 value. + +Example usage of [Int32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int32Exact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Int32(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed int32 attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Int32Exact(123), + ), + }, + }, + }, + }, + }) +} +``` + +## `Int32Func` Check + +The [Int32Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int32Func) check allows defining a custom function to validate whether the int32 value of a resource attribute or output satisfies specific conditions. + +Example usage of [Int32Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int32Func) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_Int32Func(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing an int32 attribute named "configurable_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("configurable_attribute"), + knownvalue.Int32Func(func(v int32) error { + if v > 1 && v < 12 { + return fmt.Errorf("value must be between 1 and 12") + } + return nil + }), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/int64.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/int64.mdx new file mode 100644 index 0000000000..cb43441318 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/int64.mdx @@ -0,0 +1,77 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Int64 Value Checks for use with Plan and State Checks. +--- + +# Int64 Known Value Checks + +The known value checks that are available for int64 values are: + +* [Int64Exact](/terraform/plugin/testing/acceptance-tests/known-value-checks/int64#int64exact-check) +* [Int64Func](/terraform/plugin/testing/acceptance-tests/known-value-checks/int64#int64func-check) + +## `Int64Exact` Check + +The [Int64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int64Exact) check tests that a resource attribute, or output value has an exactly matching int64 value. + +Example usage of [Int64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int64Exact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Int64(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed int64 attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Int64Exact(123), + ), + }, + }, + }, + }, + }) +} +``` + +## `Int64Func` Check + +The [Int64Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int64Func) check allows defining a custom function to validate whether the int64 value of a resource attribute or output satisfies specific conditions. + +Example usage of [Int64Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int64Func) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_Int64Func(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing an int64 attribute named "configurable_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("configurable_attribute"), + knownvalue.Int64Func(func(v int64) error { + if v > 1 && v < 12 { + return fmt.Errorf("value must be between 1 and 12") + } + return nil + }), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/list.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/list.mdx new file mode 100644 index 0000000000..92963b91e4 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/list.mdx @@ -0,0 +1,111 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + List Value Checks for use with Plan Checks. +--- + +# List Known Value Checks + +The known value checks that are available for list values are: + +* [ListExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/list#listexact-check) +* [ListPartial](/terraform/plugin/testing/acceptance-tests/known-value-checks/list#listpartial-check) +* [ListSizeExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/list#listsizeexact-check) + +## `ListExact` Check + +The [ListExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListExact) check tests that a resource attribute, or output value has an order-dependent, matching collection of element values. + +Example usage of [ListExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_List(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed list attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("value1"), + knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `ListPartial` Check + +The [ListPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListPartial) check tests that a resource attribute, or output value has matching element values for the specified collection indices. + +Example usage of [ListPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListPartial) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. In this example, only the first element within the list, the element defined at index `0`, is checked. + +```go +func TestExpectKnownValue_CheckPlan_ListPartial(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed list attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.ListPartial(map[int]knownvalue.Check{ + 0: knownvalue.StringExact("value1"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `ListSizeExact` Check + +The [ListSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListSizeExact) check tests that a resource attribute, or output value contains the specified number of elements. + +Example usage of [ListSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListSizeExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_ListElements(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed list attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.ListSizeExact(2), + ), + }, + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/map.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/map.mdx new file mode 100644 index 0000000000..9685249c4b --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/map.mdx @@ -0,0 +1,113 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Map Value Checks for use with Plan Checks. +--- + +# Map Known Value Checks + +The known value checks that are available for map values are: + +* [MapExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/map#mapexact-check) +* [MapPartial](/terraform/plugin/testing/acceptance-tests/known-value-checks/map#mappartial-check) +* [MapSizeExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/map#mapsizeexact-check) + +## `MapExact` Check + +The [MapExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapExact) check tests that a resource attribute, or output value has a key-specified, matching collection of element values. + +Example usage of [MapExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Map(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed map attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.MapExact(map[string]knownvalue.Check{ + "key1": knownvalue.StringExact("value1"), + "key2": knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `MapPartial` Check + +The [MapPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapPartial) check tests that a resource attribute, or output value has matching element values for the specified keys. + +Example usage of [MapPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapPartial) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +In this example, only the element associated with `key1` within the map is checked. + +```go +func TestExpectKnownValue_CheckPlan_MapPartial(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed map attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.MapPartial(map[string]knownvalue.Check{ + "key1": knownvalue.StringExact("value1"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `MapSizeExact` Check + +The [MapSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapSizeExact) check tests that a resource attribute, or output value contains the specified number of elements. + +Example usage of [MapSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapSizeExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_MapElements(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed map attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.MapSizeExact(2), + ), + }, + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/not-null.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/not-null.mdx new file mode 100644 index 0000000000..b6e39030f3 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/not-null.mdx @@ -0,0 +1,40 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + NotNull Value Checks for use with Plan Checks or State Checks. +--- + +# NotNull Known Value Checks + +The known value checks that are available for values that are not null are: + +* [NotNull](/terraform/plugin/testing/acceptance-tests/known-value-checks/null#notnull-check) + +## `NotNull` Check + +The [NotNull](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NotNull) check tests that a resource attribute, or output value is not null (i.e., any known value). + +Example usage of [NotNull](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NotNull) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_AttributeValueNull(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + // Provider definition omitted. + Steps: []r.TestStep{ + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.NotNull(), + ), + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/null.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/null.mdx new file mode 100644 index 0000000000..8cd4bee006 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/null.mdx @@ -0,0 +1,40 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Null Value Checks for use with Plan Checks or State Checks. +--- + +# Null Known Value Checks + +The known value checks that are available for null values are: + +* [Null](/terraform/plugin/testing/acceptance-tests/known-value-checks/null#null-check) + +## `Null` Check + +The [Null](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Null) check tests that a resource attribute, or output value has an exactly matching null value. + +Example usage of [Null](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Null) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_AttributeValueNull(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + // Provider definition omitted. + Steps: []r.TestStep{ + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Null(), + ), + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/number.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/number.mdx new file mode 100644 index 0000000000..220cd8f5d6 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/number.mdx @@ -0,0 +1,83 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Number Value Checks for use with Plan and State Checks. +--- + +# Number Known Value Checks + +The known value checks that are available for number values are: + +* [NumberExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/number#numberexact-check) +* [NumberFunc](/terraform/plugin/testing/acceptance-tests/known-value-checks/number#numberfunc-check) + +## `NumberExact` Check + +The [NumberExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NumberExact) check tests that a resource attribute, or output value has an exactly matching number value. + +Example usage of [NumberExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NumberExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Number(t *testing.T) { + t.Parallel() + + num, _, err := big.ParseFloat("1.797693134862315797693134862315797693134862315", 10, 512, big.ToNearestEven) + + if err != nil { + t.Errorf("%s", err) + } + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed number attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.NumberExact(num), + ), + }, + }, + }, + }, + }) +} +``` + +## `NumberFunc` Check + +The [NumberFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NumberFunc) check allows defining a custom function to validate whether the number value of a resource attribute or output satisfies specific conditions. + +Example usage of [NumberFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NumberFunc) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_NumberFunc(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a number attribute named "configurable_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("configurable_attribute"), + knownvalue.NumberFunc(func(v *big.Float) error { + if err := testConfigurableAttribute(v); err != nil { + return fmt.Errorf("attribute validation failed: %w", err) + } + return nil + }), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/object.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/object.mdx new file mode 100644 index 0000000000..68569dcdee --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/object.mdx @@ -0,0 +1,81 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Object Value Checks for use with Plan Checks. +--- + +# Object Known Value Checks + +The known value checks that are available for object values are: + +* [ObjectExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/object#objectexact-check) +* [ObjectPartial](/terraform/plugin/testing/acceptance-tests/known-value-checks/object#objectpartial-check) + +## `ObjectExact` Check + +The [ObjectExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectExact) check tests that a resource attribute, or output value has a matching collection of attribute name, and attribute values. + +Example usage of [ObjectExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Object(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed object attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "attr1": knownvalue.StringExact("value1"), + "attr2": knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `ObjectPartial` Check + +The [ObjectPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectPartial) check tests that a resource attribute, or output value has matching attribute values for the specified attribute names. + +Example usage of [ObjectPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectPartial) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +In this example, only the attribute value associated with the attribute name `attr1` within the object is checked. + +```go +func TestExpectKnownValue_CheckPlan_ObjectPartial(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed object attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.ObjectPartial(map[string]knownvalue.Check{ + "attr1": knownvalue.StringExact("value1"), + }), + ), + }, + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/set.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/set.mdx new file mode 100644 index 0000000000..48d9069269 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/set.mdx @@ -0,0 +1,111 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Set Value Checks for use with Plan Checks. +--- + +# Set Known Value Checks + +The known value checks that are available for set values are: + +* [SetExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/set#setexact-check) +* [SetPartial](/terraform/plugin/testing/acceptance-tests/known-value-checks/set#setpartial-check) +* [SetSizeExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/set#setsizeexact-check) + +## `SetExact` Check + +The [SetExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetExact) check tests that a resource attribute, or output value has an order-independent, matching collection of element values. + +Example usage of [SetExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Set(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed set attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.SetExact([]knownvalue.Check{ + knownvalue.StringExact("value2"), + knownvalue.StringExact("value1"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `SetPartial` Check + +The [SetPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetPartial) check tests that a resource attribute, or output value contains matching element values. + +Example usage of [SetPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetPartial) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. In this example, only the one element within the set is checked. + +```go +func TestExpectKnownValue_CheckPlan_SetPartial(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed set attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.SetPartial([]knownvalue.Check{ + knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `SetSizeExact` Check + +The [SetSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetSizeExact) check tests that a resource attribute, or output value contains the specified number of elements. + +Example usage of [SetSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetSizeExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_SetElements(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed set attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.SetSizeExact(2), + ), + }, + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/string.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/string.mdx new file mode 100644 index 0000000000..2cbc7fe944 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/string.mdx @@ -0,0 +1,107 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + String Value Checks for use with Plan and State Checks. +--- + +# String Known Value Checks + +The known value checks that are available for string values are: + +* [StringExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/string#stringexact-check) +* [StringRegexp](/terraform/plugin/testing/acceptance-tests/known-value-checks/string#stringregexp-check) +* [StringFunc](/terraform/plugin/testing/acceptance-tests/known-value-checks/string#stringfunc-check) + +## `StringExact` Check + +The [StringExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringExact) check tests that a resource attribute, or output value has an exactly matching string value. + +Example usage of [StringExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_StringExact(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed string attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.StringExact("str")), + }, + }, + }, + }, + }) +} +``` + +## `StringRegexp` Check + +The [StringRegexp](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringRegexp) check tests that a resource attribute, or output value has a string value which matches the supplied regular expression. + +Example usage of [StringRegexp](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringRegexp) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_StringRegexp(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed string attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.StringRegexp(regexp.MustCompile("str"))), + }, + }, + }, + }, + }) +} +``` + +## `StringFunc` Check + +The [StringFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringFunc) check allows defining a custom function to validate whether the string value of a resource attribute or output satisfies specific conditions. + +Example usage of [StringFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringFunc) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_StringFunc(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a string attribute named "configurable_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("configurable_attribute"), + knownvalue.StringFunc(func(v string) error { + if !strings.HasPrefix(v, "str") { + return fmt.Errorf("value must start with 'str'") + } + return nil + }), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/tuple.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/tuple.mdx new file mode 100644 index 0000000000..95129a23a9 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/known-value-checks/tuple.mdx @@ -0,0 +1,123 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Tuple Value Checks for use with Plan Checks. +--- + +# Tuple Known Value Checks + + + +Provider developers will only encounter tuples when testing [dynamic data values](/terraform/plugin/framework/handling-data/dynamic-data). + + + +The known value checks that are available for tuple values are: + +* [TupleExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/tuple#tupleexact-check) +* [TuplePartial](/terraform/plugin/testing/acceptance-tests/known-value-checks/tuple#tuplepartial-check) +* [TupleSizeExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/tuple#tuplesizeexact-check) + +## `TupleExact` Check + +The [TupleExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#TupleExact) check tests that a resource attribute, or output value has an order-dependent, matching collection of element values. + +Example usage of [TupleExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#TupleExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Tuple(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a required dynamic attribute named "example_attribute" + Config: `resource "test_resource" "one" { + example_attribute = [true, "hello world"] + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("example_attribute"), + knownvalue.TupleExact([]knownvalue.Check{ + knownvalue.Bool(true), + knownvalue.StringExact("hello world"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `TuplePartial` Check + +The [TuplePartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#TuplePartial) check tests that a resource attribute, or output value has matching element values for the specified collection indices. + +Example usage of [TuplePartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#TuplePartial) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. In this example, only the second element within the tuple, the element defined at index `1`, is checked. + +```go +func TestExpectKnownValue_CheckPlan_TuplePartial(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a required dynamic attribute named "example_attribute" + Config: `resource "test_resource" "one" { + example_attribute = [true, "hello world"] + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("example_attribute"), + knownvalue.TuplePartial(map[int]knownvalue.Check{ + 1: knownvalue.StringExact("hello world"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `TupleSizeExact` Check + +The [TupleSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#TupleSizeExact) check tests that a resource attribute, or output value contains the specified number of elements. + +Example usage of [TupleSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#TupleSizeExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_TupleElements(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a required dynamic attribute named "example_attribute" + Config: `resource "test_resource" "one" { + example_attribute = [true, "hello world"] + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("example_attribute"), + knownvalue.TupleSizeExact(2), + ), + }, + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/plan-checks/custom.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/plan-checks/custom.mdx new file mode 100644 index 0000000000..5e3672a515 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/plan-checks/custom.mdx @@ -0,0 +1,87 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Plan Checks' +description: >- + Plan Checks are test assertions that can inspect a plan at different phases in a TestStep. Custom Plan Checks can be implemented. +--- + +# Custom Plan Checks + +The package [`plancheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck) also provides the [`PlanCheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#PlanCheck) interface, which can be implemented for a custom plan check. + +The [`plancheck.CheckPlanRequest`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#CheckPlanRequest) contains the current plan file, parsed by the [terraform-json package](https://pkg.go.dev/github.com/hashicorp/terraform-json#Plan). + +Here is an example implementation of a plan check that asserts that every resource change is a no-op, aka, an empty plan: +```go +package example_test + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +var _ plancheck.PlanCheck = expectEmptyPlan{} + +type expectEmptyPlan struct{} + +func (e expectEmptyPlan) CheckPlan(ctx context.Context, req plancheck.CheckPlanRequest, resp *plancheck.CheckPlanResponse) { + var result error + + for _, rc := range req.Plan.ResourceChanges { + if !rc.Change.Actions.NoOp() { + result = errors.Join(result, fmt.Errorf("expected empty plan, but %s has planned action(s): %v", rc.Address, rc.Change.Actions)) + } + } + + resp.Error = result +} + +func ExpectEmptyPlan() plancheck.PlanCheck { + return expectEmptyPlan{} +} +``` + +And example usage: +```go +package example_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func Test_CustomPlanCheck_ExpectEmptyPlan(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + Source: "registry.terraform.io/hashicorp/random", + }, + }, + Steps: []resource.TestStep{ + { + Config: `resource "random_string" "one" { + length = 16 + }`, + }, + { + Config: `resource "random_string" "one" { + length = 16 + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + ExpectEmptyPlan(), + }, + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/plan-checks/index.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/plan-checks/index.mdx new file mode 100644 index 0000000000..9c1960b547 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/plan-checks/index.mdx @@ -0,0 +1,128 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Plan Checks' +description: >- + Plan Checks are test assertions that can inspect a plan at different phases in a TestStep. The testing module + provides built-in Plan Checks for common use-cases, and custom Plan Checks can also be implemented. +--- + +# Plan Checks + +During the **Lifecycle (config)** and **Refresh** [modes](/terraform/plugin/testing/acceptance-tests/teststep#test-modes) of a `TestStep`, the testing framework will run `terraform plan` before and after certain operations. For example, the **Lifecycle (config)** mode will run a plan before the `terraform apply` phase, as well as a plan before and after the `terraform refresh` phase. + +These `terraform plan` operations results in a [plan file](/terraform/cli/commands/plan#out-filename) and can be represented by this [JSON format](/terraform/internals/json-format#plan-representation). + +A **plan check** is a test assertion that inspects the plan file at a specific phase during the current testing mode. Multiple plan checks can be run at each defined phase, all assertion errors returned are aggregated, reported as a test failure, and all test cleanup logic is executed. + +- Available plan phases for **Lifecycle (config)** mode are defined in the [`TestStep.ConfigPlanChecks`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep) struct +- Available plan phases for **Refresh** mode are defined in the [`TestStep.RefreshPlanChecks`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep) struct +- **Import** mode currently does not run any plan operations, and therefore does not support plan checks. + +Refer to: + +- [General Plan Checks](#general-plan-checks) for built-in general purpose plan checks. +- [Resource Plan Checks](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) for built-in managed resource and data source plan checks. +- [Output Plan Checks](/terraform/plugin/testing/acceptance-tests/plan-checks/output) for built-in output-related plan checks. +- [Custom Plan Checks](/terraform/plugin/testing/acceptance-tests/plan-checks/custom) for defining bespoke plan checks. + +## General Plan Checks + +The `terraform-plugin-testing` module provides a package [`plancheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck) with built-in general plan checks for common use-cases: + +| Check | Description | +|--------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| +| [`ExpectEmptyPlan`](/terraform/plugin/testing/acceptance-tests/plan-checks#expectemptyplan-plan-check) | Asserts the entire plan has no operations for apply. | +| [`ExpectNonEmptyPlan`](/terraform/plugin/testing/acceptance-tests/plan-checks#expectnonemptyplan-plan-check) | Asserts the entire plan contains at least one operation for apply. | + +## `ExpectEmptyPlan` Plan Check + +One of the built-in plan checks, [`plancheck.ExpectEmptyPlan`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectEmptyPlan), is useful for determining a plan is a no-op prior to, for instance, the `terraform apply` phase. + +Given the following example with the [random provider](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string), we have written a test that asserts that there are no planned changes: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func Test_Random_EmptyPlan(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + Source: "registry.terraform.io/hashicorp/random", + }, + }, + Steps: []resource.TestStep{ + { + Config: `resource "random_string" "one" { + length = 16 + }`, + }, + { + Config: `resource "random_string" "one" { + length = 16 + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, + }) +} +``` + +## `ExpectNonEmptyPlan` Plan Check + +One of the built-in plan checks, [`plancheck.ExpectNonEmptyPlan`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectNonEmptyPlan), is useful for determining whether a plan contains changes prior to, for instance, the `terraform apply` phase. + +The following example, which uses the built-in [terraform_data resource](https://developer.hashicorp.com/terraform/language/resources/terraform-data), asserts that there are planned changes: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_ExpectNonEmptyPlan_ResourceChanges(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_4_0), + }, + ExternalProviders: map[string]resource.ExternalProvider{ + "terraform": {Source: "terraform.io/builtin/terraform"}, + }, + Steps: []resource.TestStep{ + { + Config: `resource "terraform_data" "one" { + triggers_replace = ["original"] + }`, + }, + { + Config: `resource "terraform_data" "one" { + triggers_replace = ["new"] + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectNonEmptyPlan(), + }, + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/plan-checks/output.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/plan-checks/output.mdx new file mode 100644 index 0000000000..3f0b97bf72 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/plan-checks/output.mdx @@ -0,0 +1,309 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Plan Checks' +description: >- + Plan Checks are test assertions that can inspect a plan at different phases in a TestStep. The testing module + provides built-in Output Value Plan Checks for common use-cases. +--- + +# Output Plan Checks + +The `terraform-plugin-testing` module provides a package [`plancheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck) with built-in output value plan checks for common use-cases: + +| Check | Description | +|-------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------| +| [`ExpectKnownOutputValue`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalue-plan-check) | Asserts the output at the specified address has the specified type, and value. | +| [`ExpectKnownOutputValueAtPath`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalueatpath-plan-check) | Asserts the output at the specified address, and path has the specified type, and value. | +| [`ExpectNullOutputValue`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectnulloutputvalue-plan-check) | Asserts the output at the specified address has a null value. | +| [`ExpectNullOutputValueAtPath`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectnulloutputvalueatpath-plan-check) | Asserts the output at the specified address, and path has a null value. | +| [`ExpectUnknownOutputValue`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectunknownoutputvalue-plan-check) | Asserts the output at the specified address has an unknown value. | +| [`ExpectUnknownOutputValueAtPath`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalueatpath-plan-check) | Asserts the output at the specified address, and path has an unknown value. | + +## `ExpectKnownOutputValue` Plan Check + +The [`plancheck.ExpectKnownOutputValue(address, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectKnownOutputValue) plan check verifies that a specific output value has a known type, and value. + +Refer to [Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks) for details, and examples of the available [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types that can be used with the `ExpectKnownOutputValue` plan check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestExpectKnownOutputValue_CheckPlan_Bool(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_8_0), + }, + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: ` + output "test" { + value = provider::example::bool(true) + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownOutputValue( + "test", + knownvalue.Bool(true), + ), + }, + }, + }, + }, + }) +} +``` + +## `ExpectKnownOutputValueAtPath` Plan Check + +The [`plancheck.ExpectKnownOutputValueAtPath(address, path, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectKnownOutputValueAtPath) plan check verifies that a specific output value at a defined path has a known type, and value. + +~> **Note**: Prior to Terraform v1.3.0 a planned output is marked as fully unknown if any attribute is unknown. + +Refer to [Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks) for details, and examples of the available [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types that can be used with the `ExpectKnownOutputValueAtPath` plan check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func TestExpectKnownOutputValue_CheckPlan_Bool(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed boolean attribute named "computed_attribute" + Config: `resource "test_resource" "one" {} + + // Generally, it is not necessary to use an output to test a resource attribute, + // the resource attribute should be tested directly instead. This is only shown as + // an example. + // + // ConfigPlanChecks: resource.ConfigPlanChecks{ + // PreApply: []plancheck.PlanCheck{ + // plancheck.ExpectKnownValue( + // "test_resource.one", + // tfjsonpath.New("computed_attribute"), + // knownvalue.Bool(true), + // ), + // }, + // }, + // + // This is only shown as an example. + output test { + value = test_resource.one + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownOutputValueAtPath( + "test", + tfjsonpath.New("computed_attribute"), + knownvalue.Bool(true), + ), + }, + }, + }, + }, + }) +} +``` + +## `ExpectNullOutputValue` Plan Check + +~> **Note**: `ExpectNullOutputValue` is deprecated. Use [`ExpectKnownOutputValue`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalue-plan-check) with [`knownvalue.Null()`](/terraform/plugin/testing/acceptance-tests/known-value-checks/null) instead. + +The built-in [`plancheck.ExpectNullOutputValue(address)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectNullOutputValue) plan check determines whether an output at the specified address has a null value. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func Test_ExpectNullOutputValue_StringAttribute_NullConfig(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: `resource "test_resource" "test" { + string_attribute = null + } + + output "string_attribute" { + value = test_resource.test.string_attribute + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectNullOutputValue("string_attribute"), + }, + }, + }, + }, + }) +} +``` + +## `ExpectNullOutputValueAtPath` Plan Check + +~> **Note**: `ExpectNullOutputValueAtPath` is deprecated. Use [`ExpectKnownOutputValueAtPath`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalueatpath-plan-check) with [`knownvalue.Null()`](/terraform/plugin/testing/acceptance-tests/known-value-checks/null) instead. + +The built-in [`plancheck.ExpectNullOutputValueAtPath(address, path)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectNullOutputValueAtPath) plan check determines whether an output at the specified address, and path has a null value. | + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_ExpectNullOutputValueAtPath_StringAttribute_NullConfig(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Prior to Terraform v1.3.0 a planned output is marked as fully unknown + // if any attribute is unknown. The id attribute within the test provider + // is unknown. + // Reference: https://github.com/hashicorp/terraform/blob/v1.3/CHANGELOG.md#130-september-21-2022 + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_3_0), + }, + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: `resource "test_resource" "test" { + string_attribute = null + } + + output "resource" { + value = test_resource.test + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectNullOutputValueAtPath("resource", tfjsonpath.New("string_attribute")), + }, + }, + }, + }, + }) +} +``` + +## `ExpectUnknownOutputValue` Plan Check + +One of the built-in plan checks, [`plancheck.ExpectUnknownOutputValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectUnknownOutputValue), determines whether an output value is unknown, for example, prior to the `terraform apply` phase. + +The following uses the [time_offset](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/offset) resource from the [time provider](https://registry.terraform.io/providers/hashicorp/time/latest), to illustrate usage of the [`plancheck.ExpectUnknownOutputValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectUnknownOutputValue), and verifies that `day` is unknown. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func Test_Time_Unknown(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "time": { + Source: "registry.terraform.io/hashicorp/time", + }, + }, + Steps: []resource.TestStep{ + { + Config: `resource "time_offset" "one" { + offset_days = 1 + } + + output day { + value = time_offset.one.day + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownOutputValue("day"), + }, + }, + }, + }, + }) +} +``` + +## `ExpectUnknownOutputValueAtPath` Plan Check + +Output values can contain objects or collections as well as primitive (e.g., string) values. Output value plan checks provide two forms for the plan checks, for example `ExpectUnknownOutputValue()`, and `ExpectUnknownOutputValueAtPath()`. The `Expect<...>OutputValueAtPath()` form is used to access a value contained within an object or collection, as illustrated in the following example. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func Test_Time_Unknown(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "time": { + Source: "registry.terraform.io/hashicorp/time", + }, + }, + Steps: []resource.TestStep{ + { + Config: `resource "time_offset" "one" { + offset_days = 1 + } + + output time_offset_one { + value = time_offset.one + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownOutputValueAtPath("time_offset_one", tfjsonpath.New("day")), + }, + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/plan-checks/resource.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/plan-checks/resource.mdx new file mode 100644 index 0000000000..b96ae68bb9 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/plan-checks/resource.mdx @@ -0,0 +1,286 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Plan Checks' +description: >- + Plan Checks are test assertions that can inspect a plan at different phases in a TestStep. The testing module + provides built-in Managed Resource and Data Source Plan Checks for common use-cases. +--- + +# Resource Plan Checks + +The `terraform-plugin-testing` module provides a package [`plancheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck) with built-in managed resource, and data source plan checks for common use-cases: + +| Check | Description | +|---------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| +| [`ExpectKnownValue`](/terraform/plugin/testing/acceptance-tests/plan-checks/resource#expectknownvalue-plan-check) | Asserts the specified attribute at the given managed resource, or data source, has the specified type, and value. | +| [`ExpectResourceAction`](/terraform/plugin/testing/acceptance-tests/plan-checks/resource#expectresourceaction-plan-check) | Asserts the given managed resource, or data source, has the specified operation for apply. | +| [`ExpectSensitiveValue`](/terraform/plugin/testing/acceptance-tests/plan-checks/resource#expectsensitivevalue-plan-check) | Asserts the specified attribute at the given managed resource, or data source, has a sensitive value. | +| [`ExpectUnknownValue`](/terraform/plugin/testing/acceptance-tests/plan-checks/resource#expectunknownvalue-plan-check) | Asserts the specified attribute at the given managed resource, or data source, has an unknown value. | + +## `ExpectKnownValue` Plan Check + +The [`plancheck.ExpectKnownValue(address, path, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectKnownValue) plan check provides a basis for asserting that a specific resource attribute has a known type, and value. + +Refer to [Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks) for details, and examples of the available [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types that can be used with the `ExpectKnownValue` plan check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestExpectKnownValue_CheckPlan_String(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed string attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.StringExact("str")), + }, + }, + }, + }, + }) +} +``` + +## `ExpectResourceAction` Plan Check + +One of the built-in plan checks, [`plancheck.ExpectResourceAction`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectResourceAction), is useful for determining the exact action type a resource will under-go during, say, the `terraform apply` phase. + +Given the following example with the [random provider](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string), we have written a test that asserts that `random_string.one` will be destroyed and re-created when the `length` attribute is changed: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func Test_Random_ForcesRecreate(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + Source: "registry.terraform.io/hashicorp/random", + }, + }, + Steps: []resource.TestStep{ + { + Config: `resource "random_string" "one" { + length = 16 + }`, + }, + { + Config: `resource "random_string" "one" { + length = 15 + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("random_string.one", plancheck.ResourceActionDestroyBeforeCreate), + }, + }, + }, + }, + }) +} +``` + +Another example with the [time provider](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/offset) asserts that `time_offset.one` will be updated in-place when the `offset_days` attribute is changed: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func Test_Time_UpdateInPlace(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "time": { + Source: "registry.terraform.io/hashicorp/time", + }, + }, + Steps: []resource.TestStep{ + { + Config: `resource "time_offset" "one" { + offset_days = 1 + }`, + }, + { + Config: `resource "time_offset" "one" { + offset_days = 2 + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("time_offset.one", plancheck.ResourceActionUpdate), + }, + }, + }, + }, + }) +} +``` + +Multiple plan checks can be combined if you want to assert multiple resource actions: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func Test_Time_UpdateInPlace_and_NoOp(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "time": { + Source: "registry.terraform.io/hashicorp/time", + }, + }, + Steps: []resource.TestStep{ + { + Config: `resource "time_offset" "one" { + offset_days = 1 + } + resource "time_offset" "two" { + offset_days = 1 + }`, + }, + { + Config: `resource "time_offset" "one" { + offset_days = 2 + } + resource "time_offset" "two" { + offset_days = 1 + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("time_offset.one", plancheck.ResourceActionUpdate), + plancheck.ExpectResourceAction("time_offset.two", plancheck.ResourceActionNoop), + }, + }, + }, + }, + }) +} +``` + +## `ExpectSensitiveValue` Plan Check + +The built-in [`plancheck.ExpectSensitiveValue(address, path)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectSensitiveValue) plan check is used to determine whether the specified attribute at the given managed resource, or data source, has a sensitive value. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_ExpectSensitiveValue_SensitiveStringAttribute(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_0_0), // Change.AfterSensitive + }, + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: ` + resource "test_resource" "one" { + sensitive_string_attribute = "test" + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectSensitiveValue("test_resource.one", + tfjsonpath.New("sensitive_string_attribute")), + }, + }, + }, + }, + }) +} +``` + +## `ExpectUnknownValue` Plan Check + +The built-in [`plancheck.ExpectUnknownValue(address, path)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectUnknownValue) plan check is used to determine whether the specified attribute at the given managed resource, or data source, has an unknown value. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func Test_ExpectUnknownValue_StringAttribute(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "time": { + Source: "registry.terraform.io/hashicorp/time", + }, + }, + // Provider definition for `test` omitted. + Steps: []resource.TestStep{ + { + Config: `resource "time_static" "one" {} + + resource "test_resource" "two" { + string_attribute = time_static.one.rfc3339 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("test_resource.two", tfjsonpath.New("string_attribute")), + }, + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/query-checks/custom.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/query-checks/custom.mdx new file mode 100644 index 0000000000..8db67d3cdb --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/query-checks/custom.mdx @@ -0,0 +1,95 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Query Checks' +description: >- + Query Checks are test assertions that can inspect the query results during a TestStep. Custom Query Checks can be implemented. +--- + +# Custom Query Checks + +The package [`querycheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/querycheck) also provides the [`QueryResultCheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/querycheck#QueryResultCheck) interface, which can be implemented for a custom query check. + +The [`querycheck.CheckQueryRequest`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/querycheck#CheckQueryResultRequest) contains the current query results, parsed by the [terraform-json package](https://pkg.go.dev/github.com/hashicorp/terraform-json#ListResourceFoundData). + +Here is an example implementation of a query check that asserts that a specific query result resource object attribute has a known type and value: + +```go +package example_test + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ querycheck.QueryResultCheck = expectKnownValue{} +var _ querycheck.QueryResultCheckWithFilters = expectKnownValue{} + +type expectKnownValue struct { + listResourceAddress string + filter queryfilter.QueryFilter + attributePath tfjsonpath.Path + knownValueCheck knownvalue.Check +} + +func (e expectResourceKnownValues) QueryFilters(_ context.Context) []queryfilter.QueryFilter { + if e.filter == nil { + return []queryfilter.QueryFilter{} + } + + return []queryfilter.QueryFilter{ + e.filter, + } +} + +func (e expectKnownValue) CheckQuery(_ context.Context, req querycheck.CheckQueryRequest, resp *querycheck.CheckQueryResponse) { + listRes := make([]tfjson.ListResourceFoundData, 0) + for _, res := range req.Query { + if e.listResourceAddress == strings.TrimPrefix(res.Address, "list.") { + listRes = append(listRes, res) + } + } + + if len(listRes) == 0 { + resp.Error = fmt.Errorf("%s - no query results found after filtering", e.listResourceAddress) + return + } + + if len(listRes) > 1 { + resp.Error = fmt.Errorf("%s - more than 1 query result found after filtering", e.listResourceAddress) + return + } + + res := listRes[0] + + if res.ResourceObject == nil { + resp.Error = fmt.Errorf("%s - no resource object was returned, ensure `include_resource` has been set to `true` in the list resource config`", e.listResourceAddress) + return + } + + attribute, err := tfjsonpath.Traverse(res.ResourceObject, e.attributePath) + if err != nil { + resp.Error = err + return + } + + if err := e.KnownValue.CheckValue(attribute); err != nil { + resp.Error = fmt.Errorf("error checking value for attribute at path: %s for resource with identity %s, err: %s", e.attributePath, e.filter, err) + return + } +} + +func ExpectKnownValues(listResourceAddress string, filter queryfilter.QueryFilter, attributePath tfjsonpath.Path, knownValueCheck knownvalue.Check) QueryResultCheck { + return expectKnownValues{ + listResourceAddress: listResourceAddress, + filter: filter, + attributePath: attributePath, + knownValueCheck: knownValueCheck, + } +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/query-checks/index.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/query-checks/index.mdx new file mode 100644 index 0000000000..01a1658fe5 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/query-checks/index.mdx @@ -0,0 +1,18 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Query Checks' +description: >- + Query Checks are test assertions that can inspect the query results during a Query TestStep. The testing module + provides built-in Query Checks for common use-cases, and custom Query Checks can also be implemented. +--- + +# Query Checks +During the **Query** [mode](/terraform/plugin/testing/acceptance-tests/teststep#test-modes) of a `TestStep`, the configuration supplied is used as the content of a `.tfquery.hcl` query file and the testing framework will run `terraform query`. + +The execution of `terraform query` results in a set of query results. + +A **query check** is a test assertion that inspects the query results. Multiple query checks can be run, all assertion errors returned are aggregated, reported as a test failure, and all test cleanup logic is executed. + +Refer to: + +- [Query Result Query Checks](/terraform/plugin/testing/acceptance-tests/query-checks/query-results) for built-in query checks. +- [Custom Query Checks](/terraform/plugin/testing/acceptance-tests/query-checks/custom) for defining bespoke query checks. \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/query-checks/query-result.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/query-checks/query-result.mdx new file mode 100644 index 0000000000..cd7672a677 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/query-checks/query-result.mdx @@ -0,0 +1,338 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Query Result Checks' +description: >- + Query Checks are test assertions that can inspect the query results during a TestStep. The testing module + provides built-in Query Checks for common use-cases. +--- + +# Query Result Checks + +The `terraform-plugin-testing` module provides a package [`querycheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/querycheck) with built-in query checks for common use-cases: + +Check | Description | +|---------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [`ExpectIdentity`](/terraform/plugin/testing/acceptance-tests/query-checks/query-result#expectidentity-query-check) | Asserts that a specified resource identity object is present in the query result. | +| [`ExpectLengthAtLeast`](/terraform/plugin/testing/acceptance-tests/query-checks/query-result#expectlengthatleast-query-check) | Asserts that the number of query results returned is at least a given value. | +| [`ExpectLengthExact`](/terraform/plugin/testing/acceptance-tests/query-checks/query-result#expectlengthexact-query-check) | Asserts that the number of query results returned is equal to the given value. | +| [`ExpectNoIdentity`](/terraform/plugin/testing/acceptance-tests/state-checks/query-checks/query-result#expectnoidentity-query-check) | Asserts that a specified resource identity object is absent from the query results. | +| [`ExpectResourceDisplayName`](/terraform/plugin/testing/acceptance-tests/query-checks/query-result#expectresourcedisplayname-query-check) | Asserts that a particular query result has the specified display name. | +| [`ExpectResourceKnownValues`](/terraform/plugin/testing/acceptance-tests/query-checks/query-result#expectresourceknownvalues-query-check) | Asserts that a specific query result's resource attribute has a specified value. | + +# Query Filter + +Several built-in query checks can only be applied to a specific query result which is identified by a query filter. The `terraform-plugin-testing` module provides a package [`queryfilter`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter) with built-in query filters for common use-cases: + +- Filtering by Display Name: [`queryfilter.ByDisplayName(displayName)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter#ByDisplayName) is used to filter query results by matching the display name of the resource. +- Filtering by Resource Identity: [`queryfilter.ByResourceIdentity(identity)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter#ByResourceIdentity) is used to filter query results by matching a resource identity object. + +## `ExpectIdentity` Query Check + +The [`querycheck.ExpectIdentity(resourceAddress, identity)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/querycheck#ExpectIdentity) query check is used to assert that the query results returned by a list resource contain a resource with the specified identity. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/querycheck" +) + +func TestExpectIdentity_QueryCheck(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example config creating three resources for querying + Config: ` + resource "test_resource" "one" {} + resource "test_resource" "two" {} + resource "test_resource" "three" {} + `, + }, + { + // Config for the .tfquery.hcl query file + Config: ` + list "test_resource" "test" { + provider = examplecloud + + config {} + } + `, + Query: true, + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectIdentity( + "test_resource.test", + map[string]knownvalue.Check{"attr_1": "foo", "attr_1": "bar"}, + ), + }, + }, + }, + }) +} +``` + +## `ExpectLengthAtLeast` Query Check + +The [`querycheck.ExpectLengthAtLeast(resourceAddress, length)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/querycheck#ExpectLengthAtLeast) query check is used to assert that a query returns a minimum number of results. + +This is intended to be used when the exact number of results cannot be guaranteed due to the testing environment. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/querycheck" +) + +func TestExpectIdentity_QueryCheck(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example config creating three resources for querying + Config: ` + resource "test_resource" "one" {} + resource "test_resource" "two" {} + resource "test_resource" "three" {} + `, + }, + { + // Config for the .tfquery.hcl query file + Config: ` + list "test_resource" "test" { + provider = examplecloud + + config {} + } + `, + Query: true, + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLengthAtLeast("test_resource.test", 3), + }, + }, + }, + }) +} +``` + +## `ExpectLengthExact` Query Check + +The [`querycheck.ExpectLengthExact(resourceAddress, length)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/querycheck#ExpectLength) query check is used to assert that a query returns a specific number of results. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/querycheck" +) + +func TestExpectIdentity_QueryCheck(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example config creating three resources for querying + Config: ` + resource "test_resource" "one" {} + resource "test_resource" "two" {} + resource "test_resource" "three" {} + `, + }, + { + // Config for the .tfquery.hcl query file + Config: ` + list "test_resource" "test" { + provider = examplecloud + + config {} + } + `, + Query: true, + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLength("test_resource.test", 3), + }, + }, + }, + }) +} +``` + +## `ExpectNoIdentity` Query Check + +The [`querycheck.ExpectNoIdentity(resourceAddress, identity)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/querycheck#ExpectNoIdentity) query check is used to assert that the query results returned by a list resource do not contain a resource with the specified identity. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/querycheck" +) + +func TestExpectIdentity_QueryCheck(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example config creating three resources for querying + Config: ` + resource "test_resource" "one" {} + resource "test_resource" "two" {} + resource "test_resource" "three" {} + `, + }, + { + // Config for the .tfquery.hcl query file + Config: ` + list "test_resource" "test" { + provider = examplecloud + + config {} + } + `, + Query: true, + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectNoIdentity( + "test_resource.test", + map[string]knownvalue.Check{"attr_1": "foo", "attr_1": "bar"} + ), + }, + }, + }, + }) +} +``` + +## `ExpectResourceDisplayName` Query Check + +The [`querycheck.ExpectResourceDisplayName(resourceAddress, filter, displayName)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/querycheck#ExpectResourceDisplayName) query check is used to assert that a particular query result which is returned by a query filter has a given display name. + +This check requires a [query filter](/terraform/plugin/testing/acceptance-tests/query-checks/query-result#query-filter) to identify the query result on which the assertion should be performed. If the query filter returns zero or multiple results, the check will fail. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/querycheck" +) + +func TestExpectIdentity_QueryCheck(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example config creating three resources for querying + Config: ` + resource "test_resource" "one" {} + resource "test_resource" "two" {} + resource "test_resource" "three" {} + `, + }, + { + // Config for the .tfquery.hcl query file + Config: ` + list "test_resource" "test" { + provider = examplecloud + + config {} + } + `, + Query: true, + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectResourceDisplayName( + "test_resource.test", + queryfilter.ByResourceIdentity(map[string]knownvalue.Check{"attr_1": knownvalue.StringExact("foo"), "attr_2": knownvalue.StringExact("bar")}), + knownvalue.StringExact("foobar"), + ), + }, + }, + }, + }) +} +``` + +## `ExpectResourceKnownValues` Query Check + +The [`querycheck.ExpectResourceKnownValues(resourceAddress, filter, knownValueChecks)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/querycheck#ExpectResourceKnownValues) query check is used to assert that a particular query result's resource object has the given values. + +This check requires a [query filter](/terraform/plugin/testing/acceptance-tests/query-checks/query-result#query-filter) to identify the query result on which the assertion should be performed. If the query filter returns zero or multiple results, the check will fail. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/querycheck" +) + +func TestExpectIdentity_QueryCheck(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example config creating three resources for querying + Config: ` + resource "test_resource" "one" {} + resource "test_resource" "two" {} + resource "test_resource" "three" {} + `, + }, + { + // Config for the .tfquery.hcl query file + Config: ` + list "test_resource" "test" { + provider = examplecloud + include_resource = true + + config {} + } + `, + Query: true, + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectResourceKnownValues( + "test_resource.test", + queryfilter.ByResourceIdentity(map[string]knownvalue.Check{"attr_1": knownvalue.StringExact("foo"), "attr_2": knownvalue.StringExact("bar")}), + []querycheck.KnownValueCheck{ + { + tfjsonpath.New("attr_3"), + knownvalue.StringExact("foobar"), + }, + { + tfjsonpath.New("attr_4"), + knownvalue.StringExact("baz"), + }, + }, + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/query-mode.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/query-mode.mdx new file mode 100644 index 0000000000..4eebc5899c --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/query-mode.mdx @@ -0,0 +1,66 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Query mode' +description: |- + _Query_ mode is used for testing list resources using Terraform query. +--- + +# Acceptance Tests: Query mode + +Terraform _query_ is used to search for resources within a given scope and is part of +the workflow that enables practitioners to bulk import existing infrastructure into +Terraform. For reference information about the _query_ workflow: +[Terraform CLI](https://developer.hashicorp.com/terraform/cli/commands/query/import) +[Framework](https://developer.hashicorp.com/terraform/plugin/framework/list-resources) +[SDKv2](https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/list) + +_Query_ mode is used for testing list resources by executing a real Terraform query, using the supplied +config as the contents of a `.tfquery.hcl` query file. + +Query checks can be performed on the results of the query to validate various things about the results such as the +number of results returned, the presence of a specific result, or the values of resource attributes of a result. + +## Examples + +### Testing a list resource with `terraform query` + +The following example shows an acceptance test that validates the presence of a specific query result by specifying the expected resource identity object. + +```go +func TestAccThing_query(t *testing.T) { + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: providerFactories, + Steps: []r.TestStep{ + { + Config: ` + resource "examplecloud_thing" "test1" {} + resource "examplecloud_thing" "test2" {} + resource "examplecloud_thing" "test3" {} + `, + }, + { + Config: ` + list "examplecloud_thing" "test" { + provider = examplecloud + + config {} + } + `, + Query: true, + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLength("examplecloud_thing.test", 1), + querycheck.ExpectResourceDisplayName("examplecloud_thing.test", + queryfilter.ByResourceIdentity(map[string]knownvalue.Check{ + "attr_1": knownvalue.StringExact("foo"), + "attr_2": knownvalue.StringExact("bar"), + }), knownvalue.StringExact("foobarthing")) + } + }, + }, + }) +} +``` + + + + + diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/state-checks/custom.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/state-checks/custom.mdx new file mode 100644 index 0000000000..46cd3877ee --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/state-checks/custom.mdx @@ -0,0 +1,122 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: State Checks' +description: >- + State Checks are test assertions that can inspect state during a TestStep. Custom State Checks can be implemented. +--- + +# Custom State Checks + +The package [`statecheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck) also provides the [`StateCheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#StateCheck) interface, which can be implemented for a custom state check. + +The [`statecheck.CheckStateRequest`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#CheckStateRequest) contains the current state file, parsed by the [terraform-json package](https://pkg.go.dev/github.com/hashicorp/terraform-json#State). + +Here is an example implementation of a state check that asserts that a specific resource attribute has a known type and value: + +```go +package example_test + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ statecheck.StateCheck = expectKnownValue{} + +type expectKnownValue struct { + resourceAddress string + attributePath tfjsonpath.Path + knownValue knownvalue.Check +} + +func (e expectKnownValue) CheckState(ctx context.Context, req statecheck.CheckStateRequest, resp *statecheck.CheckStateResponse) { + var resource *tfjson.StateResource + + if req.State == nil { + resp.Error = fmt.Errorf("state is nil") + } + + if req.State.Values == nil { + resp.Error = fmt.Errorf("state does not contain any state values") + } + + if req.State.Values.RootModule == nil { + resp.Error = fmt.Errorf("state does not contain a root module") + } + + for _, r := range req.State.Values.RootModule.Resources { + if e.resourceAddress == r.Address { + resource = r + + break + } + } + + if resource == nil { + resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddress) + + return + } + + result, err := tfjsonpath.Traverse(resource.AttributeValues, e.attributePath) + + if err != nil { + resp.Error = err + + return + } + + if err := e.knownValue.CheckValue(result); err != nil { + resp.Error = err + } +} + +func ExpectKnownValue(resourceAddress string, attributePath tfjsonpath.Path, knownValue knownvalue.Check) statecheck.StateCheck { + return expectKnownValue{ + resourceAddress: resourceAddress, + attributePath: attributePath, + knownValue: knownValue, + } +} +``` + +And example usage: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestExpectKnownValue_CheckState_Bool(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed boolean attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Bool(true), + ), + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/state-checks/index.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/state-checks/index.mdx new file mode 100644 index 0000000000..36304e3880 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/state-checks/index.mdx @@ -0,0 +1,20 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: State Checks' +description: >- + State Checks are test assertions that can inspect state during a TestStep. The testing module + provides built-in State Checks for common use-cases, and custom State Checks can also be implemented. +--- + +# State Checks + +During the **Lifecycle (config)** [mode](/terraform/plugin/testing/acceptance-tests/teststep#test-modes) of a `TestStep`, the testing framework will run `terraform apply`. + +The execution of `terraform apply` results in a [state file](/terraform/language/state), and can be represented by this [JSON format](/terraform/internals/json-format#state-representation). + +A **state check** is a test assertion that inspects the state file. Multiple state checks can be run, all assertion errors returned are aggregated, reported as a test failure, and all test cleanup logic is executed. + +Refer to: + +- [Resource State Checks](/terraform/plugin/testing/acceptance-tests/state-checks/resource) for built-in managed resource and data source state checks. +- [Output State Checks](/terraform/plugin/testing/acceptance-tests/state-checks/output) for built-in output-related state checks. +- [Custom State Checks](/terraform/plugin/testing/acceptance-tests/state-checks/custom) for defining bespoke state checks. diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/state-checks/output.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/state-checks/output.mdx new file mode 100644 index 0000000000..434e7486b9 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/state-checks/output.mdx @@ -0,0 +1,119 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: State Checks' +description: >- + State Checks are test assertions that can inspect state during a TestStep. The testing module + provides built-in Output Value State Checks for common use-cases. +--- + +# Output State Checks + +The `terraform-plugin-testing` module provides a package [`statecheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck) with built-in output value state checks for common use-cases: + +| Check | Description | +|-------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------| +| [`ExpectKnownOutputValue`](/terraform/plugin/testing/acceptance-tests/state-checks/output#expectknownoutputvalue-state-check) | Asserts the output at the specified address has the specified type, and value. | +| [`ExpectKnownOutputValueAtPath`](/terraform/plugin/testing/acceptance-tests/state-checks/output#expectknownoutputvalueatpath-state-check) | Asserts the output at the specified address, and path has the specified type, and value. | + +## `ExpectKnownOutputValue` State Check + +The [`statecheck.ExpectKnownOutputValue(address, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectKnownOutputValue) state check verifies that a specific output value has a known type, and value. + +Refer to [Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks) for details, and examples of the available [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types that can be used with the `ExpectKnownOutputValue` state check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestExpectKnownOutputValue_CheckState_Bool(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_8_0), + }, + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: ` + output "test" { + value = provider::example::bool(true) + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValue( + "test", + knownvalue.Bool(true), + ), + }, + }, + }, + }) +} +``` + +## `ExpectKnownOutputValueAtPath` State Check + +The [`statecheck.ExpectKnownOutputValueAtPath(address, path, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectKnownOutputValueAtPath) state check verifies that a specific output value at a defined path has a known type, and value. + +Refer to [Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks) for details, and examples of the available [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types that can be used with the `ExpectKnownOutputValueAtPath` state check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestExpectKnownOutputValueAtPath_CheckState_Bool(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed boolean attribute named "computed_attribute" + Config: `resource "test_resource" "one" {} + + // Generally, it is not necessary to use an output to test a resource attribute, + // the resource attribute should be tested directly instead. This is only shown as + // an example. + // Generally, it is not necessary to use an output to test a resource attribute, + // the resource attribute should be tested directly instead, by inspecting the + // value of the resource attribute. For instance: + // + // ConfigStateChecks: []statecheck.StateCheck{ + // statecheck.ExpectKnownValue( + // "test_resource.one", + // tfjsonpath.New("computed_attribute"), + // knownvalue.Bool(true), + // ), + // }, + // + // This is only shown as an example. + output test_resource_one_output { + value = test_resource.one + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("computed_attribute"), + knownvalue.Bool(true), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/state-checks/resource.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/state-checks/resource.mdx new file mode 100644 index 0000000000..d11db8f63f --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/state-checks/resource.mdx @@ -0,0 +1,310 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: State Checks' +description: >- + State Checks are test assertions that can inspect state during a TestStep. The testing module + provides built-in Managed Resource and Data Source State Checks for common use-cases. +--- + +# Resource State Checks + +The `terraform-plugin-testing` module provides a package [`statecheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck) with built-in managed resource, and data source state checks for common use-cases: + +| Check | Description | +|---------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [`CompareValue`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevalue-state-check) | Compares sequential values of the specified attribute at the given managed resource, or data source, using the supplied [value comparer](/terraform/plugin/testing/acceptance-tests/value-comparers). | +| [`CompareValueCollection`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluecollection-state-check) | Compares each item in the specified collection (e.g., list, set) attribute, with the second specified attribute at the given managed resources, or data sources, using the supplied [value comparer](/terraform/plugin/testing/acceptance-tests/value-comparers). | +| [`CompareValuePairs`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluecollection-state-check) | Compares the specified attributes at the given managed resources, or data sources, using the supplied [value comparer](/terraform/plugin/testing/acceptance-tests/value-comparers). | +| [`ExpectKnownValue`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#expectknownvalue-state-check) | Asserts the specified attribute at the given managed resource, or data source, has the specified type, and value. | +| [`ExpectSensitiveValue`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#expectsensitivevalue-state-check) | Asserts the specified attribute at the given managed resource, or data source, has a sensitive value. | + +## `CompareValue` State Check + +The intended usage of [`statecheck.CompareValue(comparer)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#CompareValue) state check is to retrieve a specific resource attribute value from state during sequential test steps, and to compare these values using the supplied value comparer. + +Refer to [Value Comparers](/terraform/plugin/testing/acceptance-tests/value-comparers) for details, and examples of the available [compare.ValueComparer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValueComparer) types that can be used with the `CompareValue` state check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValue_CheckState_ValuesSame(t *testing.T) { + t.Parallel() + + compareValuesSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + compareValuesSame.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + compareValuesSame.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + }, + }) +} +``` + +## `CompareValueCollection` State Check + +The [`statecheck.CompareValueCollection(resourceAddressOne, collectionPath, resourceAddressTwo, attributePath, comparer)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#CompareValueCollection) state check retrieves a specific collection (e.g., list, set) resource attribute, and a second resource attribute from state, and compares each of the items in the collection with the second attribute using the supplied value comparer. + +Refer to [Value Comparers](/terraform/plugin/testing/acceptance-tests/value-comparers) for details, and examples of the available [compare.ValueComparer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValueComparer) types that can be used with the `CompareValueCollection` state check. + +The following example illustrates how a `CompareValue` state check can be used to determine whether an attribute value appears in a collection attribute. Note that this is for illustrative purposes only, `CompareValue` should only be used for checking computed values. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValueCollection_CheckState_ValuesSame(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // The following is for illustrative purposes. State checking + // should only be used for computed attributes + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + list_attribute = [ + "str2", + "str", + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("list_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} +``` + +The following example illustrates how a `CompareValue` state check can be used to determine whether an object attribute value appears in a collection (e.g., list) attribute containing objects. Note that this is for illustrative purposes only, `CompareValue` should only be used for checking computed values. + + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValueCollection_CheckState_ValuesSame(t *testing.T) { + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // The following is for illustrative purposes. State checking + // should only be used for computed attributes + Config: `resource "test_resource" "one" { + list_nested_attribute = [ + { + a = false + b = "two" + }, + { + a = true + b = "four" + } + ] + single_nested_attribute = { + a = true + b = "four" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.one", + []tfjsonpath.Path{ + tfjsonpath.New("list_nested_attribute"), + tfjsonpath.New("b"), + }, + "test_resource.one", + tfjsonpath.New("single_nested_attribute").AtMapKey("b"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} +``` + +## `CompareValuePairs` State Check + +The [`statecheck.CompareValuePairs(resourceAddressOne, attributePathOne, resourceAddressTwo, attributePathTwo, comparer)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#CompareValuePairs) state check provides a basis for retrieving a pair of attribute values, and comparing them using the supplied value comparer. + +Refer to [Value Comparers](/terraform/plugin/testing/acceptance-tests/value-comparers) for details, and examples of the available [compare.ValueComparer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValueComparer) types that can be used with the `CompareValuePairs` state check. + +```go +package statecheck_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValuePairs_CheckState_ValuesSame(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {} + + resource "test_resource" "two" {} + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + "test_resource.two", + tfjsonpath.New("computed_attribute"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} +``` + +## `ExpectKnownValue` State Check + +The [`statecheck.ExpectKnownValue(address, path, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectKnownValue) state check provides a basis for asserting that a specific resource attribute has a known type, and value. + +Refer to [Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks) for details, and examples of the available [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types that can be used with the `ExpectKnownValue` state check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestExpectKnownValue_CheckState_Bool(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed boolean attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Bool(true), + ), + }, + }, + }, + }) +} +``` + +## `ExpectSensitiveValue` State Check + +The [`statecheck.ExpectSensitiveValue(address, path)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectSensitiveValue) state check provides a basis for asserting that a specific resource attribute is marked as sensitive. + +-> **Note:** In this example, a [TerraformVersionCheck](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#TerraformVersionCheck) is being used to prevent execution of this test prior to Terraform version `1.4.6` (refer to the release notes for Terraform [v1.4.6](https://github.com/hashicorp/terraform/releases/tag/v1.4.6)). + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_ExpectSensitiveValue_SensitiveStringAttribute(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_4_6), // StateResource.SensitiveValues + }, + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: ` + resource "test_resource" "one" { + sensitive_string_attribute = "test" + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectSensitiveValue("test_resource.one", + tfjsonpath.New("sensitive_string_attribute")), + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/sweepers.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/sweepers.mdx new file mode 100644 index 0000000000..2fff9834f8 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/sweepers.mdx @@ -0,0 +1,121 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Sweepers' +description: >- + Acceptance tests provision and verify real infrastructure with Terraform's + testing framework. Sweepers clean up leftover infrastructure. +--- + +# Sweepers + +Acceptance tests in Terraform provision and verify real infrastructure using [Terraform's testing framework](/terraform/plugin/testing/acceptance-tests). Ideally all infrastructure created is then destroyed within the lifecycle of a test, however the reality is that there are several situations that can arise where resources created during a test are “leaked”. Leaked test resources are resources created by Terraform during a test, but Terraform either failed to destroy them as part of the test, or the test falsely reported all resources were destroyed after completing the test. Common causes are intermittent errors or failures in vendor APIs, or developer error in the resource code or test. + +To address the possibility of leaked resources, Terraform provides a mechanism called sweepers to cleanup leftover infrastructure. We will add a file to our folder structure that will invoke the sweeper helper. + +``` +terraform-plugin-example/ +├── provider.go +├── provider_test.go +├── example/ +│ ├── example_sweeper_test.go +│ ├── resource_example_compute.go +│ ├── resource_example_compute_test.go +``` + +**`example_sweeper_test.go`** + +```go +package example + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestMain(m *testing.M) { + resource.TestMain(m) +} + +// sharedClientForRegion returns a common provider client configured for the specified region +func sharedClientForRegion(region string) (any, error) { + ... + return client, nil +} +``` + +`resource.TestMain` is responsible for parsing the special test flags and invoking the sweepers. Sweepers should be added within the acceptance test file of a resource. + +**`resource_example_compute_test.go`** + +```go +package example + +import ( + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func init() { + resource.AddTestSweepers("example_compute", &resource.Sweeper{ + Name: "example_compute", + F: func (region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("Error getting client: %s", err) + } + conn := client.(*ExampleClient) + + instances, err := conn.DescribeComputeInstances() + if err != nil { + return fmt.Errorf("Error getting instances: %s", err) + } + for _, instance := range instances { + if strings.HasPrefix(instance.Name, "test-acc") { + err := conn.DestroyInstance(instance.ID) + + if err != nil { + log.Printf("Error destroying %s during sweep: %s", instance.Name, err) + } + } + } + return nil + }, + }) +} +``` + +This example demonstrates adding a sweeper, it is important to note that the string passed to `resource.AddTestSweepers` is added to a map, this name must therefore be unique. Also note there needs to be a way of identifying resources created by Terraform during acceptance tests, a common practice is to prefix all resource names created during acceptance tests with `"test-acc"` or something similar. + +For more complex leaks, sweepers can also specify a list of sweepers that need to be run prior to the one being defined. + +**`resource_example_compute_disk_test.go`** + +```go +package example + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func init() { + resource.AddTestSweepers("example_compute_disk", &resource.Sweeper{ + Name: "example_compute_disk", + Dependencies: []string{"example_compute"} + ... + }) +} +``` + +The sweepers can be invoked with the common make target `sweep`: + +``` +$ make sweep +WARNING: This will destroy infrastructure. Use only in development accounts. +go test ... +... +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/testcase.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/testcase.mdx new file mode 100644 index 0000000000..0ecdcce202 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/testcase.mdx @@ -0,0 +1,352 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: TestCase' +description: |- + Acceptance tests are expressed in terms of Test Cases. Each Test Case + creates a set of resources then verifies the new infrastructure. +--- + +# Acceptance Tests: TestCases + +Acceptance tests are expressed in terms of **Test Cases**, each using one or +more Terraform configurations designed to create a set of resources under test, +and then verify the actual infrastructure created. Terraform's `resource` +package offers a method `Test()`, accepting two parameters and acting as the +entry point to Terraform's acceptance test framework. The first parameter is the +standard [\*testing.T struct from Golang's Testing package][3], and the second is +[TestCase][1], a Go struct that developers use to setup the acceptance tests. + +Here's an example acceptance test. Here the Provider is named `Example`, and the +Resource under test is `Widget`. The parts of this test are explained below the +example. + +```go +package example + +// example.Widget represents a concrete Go type that represents an API resource +func TestAccExampleWidget_basic(t *testing.T) { + var widgetBefore, widgetAfter example.Widget + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccExampleResource(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widgetBefore), + }, + }, + { + Config: testAccExampleResource_removedPolicy(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widgetAfter), + }, + }, + }, + }) +} +``` + +## Creating Acceptance Tests Functions + +Terraform acceptance tests are declared with the naming pattern `TestAccXxx` +and with the standard Go test function signature of `func TestAccXxx(*testing.T)`. +Using the above test as an example: + +```go +// File: example/widget_test.go +package example + +func TestAccExampleWidget_basic(t *testing.T) { + // ... +} +``` + +Inside this function we invoke `resource.Test()` with the `*testing.T` input and +a new testcase object: + +```go +// File: example/widget_test.go +package example + +func TestAccExampleWidget_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + // ... + }) +} +``` + +The majority of acceptance tests will only invoke `resource.Test()` and exit. If +at any point this method encounters an error, either in executing the provided +Terraform configurations or subsequent developer defined checks, `Test()` will +invoke the `t.Error` method of Go's standard testing framework and the test will +fail. A failed test will not halt or otherwise interrupt any other tests +currently running. + +## TestCase Reference API + +`TestCase` offers several fields for developers to add to customize and validate +each test, defined below. The source for `TestCase` can be viewed [here on +godoc.org](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase) + +### IsUnitTest + +**Type:** [bool](https://pkg.go.dev/builtin#bool) + +**Default:** `false` + +**Required:** no + +**IsUnitTest** allows a test to run regardless of the TF_ACC environment +variable. This should be used with care - only for fast tests on local resources +(e.g. remote state with a local backend) but can be used to increase confidence +in correct operation of Terraform without waiting for a full acceptance test +run. + +### PreCheck + +**Type:** `function` + +**Default:** `nil` + +**Required:** no + +**PreCheck** if non-nil, will be called before any test steps are executed. It +is commonly used to verify that required values exist for testing, such as +environment variables containing test keys that are used to configure the +Provider or Resource under test. + +**Example usage:** + +```go +// File: example/widget_test.go +package example + +func TestAccExampleWidget_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + // ... + }) +} + + +// testAccPreCheck validates the necessary test API keys exist +// in the testing environment +func testAccPreCheck(t *testing.T) { + if v := os.Getenv("EXAMPLE_KEY"); v == "" { + t.Fatal("EXAMPLE_KEY must be set for acceptance tests") + } + if v := os.Getenv("EXAMPLE_SECRET"); v == "" { + t.Fatal("EXAMPLE_SECRET must be set for acceptance tests") + } +} +``` + +### TerraformVersionChecks + +**Type:** [`[]tfversion.TerraformVersionCheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#TerraformVersionCheck) + +**Default:** `nil` + +**Required:** no + +**TerraformVersionChecks** if non-nil, will be called after any defined PreChecks +but before any test steps are executed. The [Terraform Version Checks](/terraform/plugin/testing/acceptance-tests/tfversion-checks) +are generic checks that check logic against the Terraform CLI version and can +immediately pass or fail a test before any test steps are executed. + +The [`tfversion`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion) package provides built-in checks for common scenarios. + +**Example usage:** + +```go +// File: example/widget_test.go +package example + +func TestAccExampleWidget_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_1_0), // built-in check from tfversion package + }, + // ... + }) +} + +``` + +### Providers + +**Type:** [`map[string]*schema.Provider`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema#Provider) + +**Required:** Yes + +**Providers** is a map of `*schema.Provider` values with `string` +keys, representing the Providers that will be under test. Only the Providers +included in this map will be loaded during the test, so any Provider included in +a configuration file for testing must be represented in this map or the test +will fail during initialization. + +This map is most commonly constructed once in a common `init()` method of the +Provider's main test file, and includes an object of the current Provider type. + +**Example usage:** (note the different files `widget_test.go` and `provider_test.go`) + +```go +// File: example/widget_test.go +package example + +func TestAccExampleWidget_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_1_0), + }, + Providers: testAccProviders, + // ... + }) +} + +// File: example/provider_test.go +package example + +var testAccProviders map[string]*schema.Provider +var testAccProvider *schema.Provider + +func init() { + testAccProvider = Provider() + testAccProviders = map[string]*schema.Provider{ + "example": testAccProvider, + } +} +``` + +### CheckDestroy + +**Type:** [TestCheckFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckFunc) + +**Default:** `nil` + +**Required:** no + +**CheckDestroy** is called after all test steps have been run and Terraform +has run `destroy` on the remaining state. This allows developers to ensure any +resource created is truly destroyed. This method receives the last known +Terraform state as input, and commonly uses infrastructure SDKs to query APIs +directly to verify the expected objects are no longer found, and should return +an error if any resources remain. + +**Example usage:** + +```go +// File: example/widget_test.go +package example + +func TestAccExampleWidget_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_1_0), + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + // ... + }) +} + +// testAccCheckExampleResourceDestroy verifies the Widget +// has been destroyed +func testAccCheckExampleResourceDestroy(s *terraform.State) error { + // retrieve the connection established in Provider configuration + conn := testAccProvider.Meta().(*ExampleClient) + + // loop through the resources in state, verifying each widget + // is destroyed + for _, rs := range s.RootModule().Resources { + if rs.Type != "example_widget" { + continue + } + + // Retrieve our widget by referencing it's state ID for API lookup + request := &example.DescribeWidgets{ + IDs: []string{rs.Primary.ID}, + } + + response, err := conn.DescribeWidgets(request) + if err == nil { + if len(response.Widgets) > 0 && *response.Widgets[0].ID == rs.Primary.ID { + return fmt.Errorf("Widget (%s) still exists.", rs.Primary.ID) + } + + return nil + } + + // If the error is equivalent to 404 not found, the widget is destroyed. + // Otherwise return the error + if !strings.Contains(err.Error(), "Widget not found") { + return err + } + } + + return nil +} +``` + +### Steps + +**Type:** [`[]TestStep`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep) + +**Required:** yes + +**TestStep** is a single apply sequence of a test, done within the context of a +state. Multiple `TestStep`s can be sequenced in a Test to allow testing +potentially complex update logic and usage. Basic tests typically contain one to +two steps, to verify the resource can be created and subsequently updated, +depending on the properties of the resource. In general, simply create/destroy +tests will only need one step. + +`TestStep`s are covered in detail in [the next section, `TestSteps`](/terraform/plugin/testing/acceptance-tests/teststep). + +**Example usage:** + +```go +// File: example/widget_test.go +package example + +func TestAccExampleWidget_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_1_0), + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccExampleResource(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widgetBefore), + }, + }, + { + Config: testAccExampleResource_removedPolicy(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widgetAfter), + }, + }, + }, + }) +} +``` + +## Next Steps + +Proceed to define [`TestSteps`][2]. + +[1]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase + +[2]: /terraform/plugin/testing/acceptance-tests/teststep + +[3]: https://pkg.go.dev/testing#T diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/teststep.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/teststep.mdx new file mode 100644 index 0000000000..4e7c6893b7 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/teststep.mdx @@ -0,0 +1,420 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: TestStep' +description: |- + TestSteps represent the application of an actual Terraform configuration + file to a given state. +--- + +# Acceptance Tests: TestSteps + +`TestStep`s represent the application of an actual Terraform configuration file +to a given state. Each step requires a configuration as input and provides +developers several means of validating the behavior of the specific resource +under test. + +## Test Modes + +Terraform's test framework facilitates four distinct modes of acceptance tests, +_Lifecycle (config)_, _Import_, _Refresh_ and _Query_. + +### Lifecycle (config) mode + +_Lifecycle (config)_ mode is the most common mode. It tests plugins by +providing one or more configuration files with the same logic as would be used +when running `terraform apply`. Configuration is supplied by specifying +[TestStep.Config](/terraform/plugin/testing/acceptance-tests/configuration#teststep-config), +[TestStep.ConfigDirectory](/terraform/plugin/testing/acceptance-tests/configuration#teststep-configdirectory), or +[TestStep.ConfigFile](/terraform/plugin/testing/acceptance-tests/configuration#teststep-configfile). +Variables for use with configuration are defined by specifying +[TestStep.ConfigVariables](/terraform/plugin/testing/acceptance-tests/configuration#teststep-configvariables). + +-> **Note:** To define a _Lifecycle (config)_ mode test step, use one of the +`Config`, `ConfigFile`, or `ConfigDirectory` fields. + +```go +steps := []TestStep{ + { + Config: `resource "random_string" { length = 12 }`, + }, +} +``` + +### Import mode + +[_Import_ mode](/terraform/plugin/testing/acceptance-tests/import-mode) +exercises provider logic for importing existing infrastructure resources into a +Terraform statefile, using real Terraform import functionality. + +-> **Note:** To define an _Import_ mode test step, set the `ImportState` field to `true`. + +The recommended use of _Import_ mode is to run it after a _Lifecycle (config)_ +mode test step. The testing framework uses the configuration and state of the +_Lifecycle (config)_ mode test step as known good values for any `_Import_` +mode test steps that follow it. As a result, _Import_ mode blocks tend to be +concise and idiomatic. + +```go +steps := []TestStep{ + { + Config: `resource "random_string" "puzzle" { length = 12 }`, + }, + { + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ResourceName: `random_string.puzzle`, + }, +} +``` + + +### Refresh mode + +_Refresh_ mode runs `terraform refresh` to refresh the test case's Terraform state. + +-> **Note:** To define a _Refresh_ mode test step, set the `RefreshState` field to `true`. + +```go +steps := []TestStep{ + { + Config: `resource "random_string" "puzzle" { length = 12 }`, + }, + { + RefreshState: true, + }, +} +``` + +### Query mode + +_Query_ mode runs `terraform query` to test list resources. The configuration supplied +for a _Query_ mode test step are used as the contents of a `.tfquery.hcl` query file. + +-> **Note:** To define a _Query_ mode test step, set the `Query` field to `true`. + +It is recommended to use _Query_ mode after a _Lifecycle (config)_ mode test step that +creates instances of the resources to be listed. + +```go +steps := []TestStep{ + { + Config: ` + resource "random_string" "puzzle" { length = 12 } + resource "random_string" "jigsaw" { length = 1000 } + `, + }, + { + Config: ` + list "random_string" "test" { + provider = examplecloud + + config {} + } + `, + Query: true, + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLengthExact("random_string.test", 2), + } + }, +} +``` + +## Steps + +`Steps` is a field within +[TestCase](/terraform/plugin/testing/acceptance-tests/testcase), the struct used +to construct acceptance tests. Each step represents a full `terraform apply` of +a given configuration language, followed by zero or more checks (defined later) +to verify the application. Each `Step` is applied in order, and require its own +configuration and optional check functions. + +Below is a code example of a lifecycle test that provides two `TestStep` structs: + +```go +package example + +// example.Widget represents a concrete Go type that represents an API resource +func TestAccExampleWidget_basic(t *testing.T) { + var widgetBefore, widgetAfter example.Widget + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccExampleResource(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widgetBefore), + }, + }, + { + Config: testAccExampleResource_removedPolicy(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widgetAfter), + }, + }, + }, + }) +} +``` + +In the above example each `TestCase` invokes a function to retrieve it's desired +configuration, based on a randomized name provided, however an in-line string or +constant string would work as well, so long as they contain valid Terraform +configuration for the plugin or resource under test. This pattern of first +applying and checking a basic configuration, followed by applying a modified +configuration with updated or additional checks is a common pattern used to test +update functionality. + +## Plan Checks + +Before and after the configuration for a `TestStep` is applied, Terraform's +testing framework provides developers an opportunity to make test assertions +against `terraform plan` results via the plan file. This is provided via [Plan +Checks](/terraform/plugin/testing/acceptance-tests/plan-checks), which provide +both built-in plan checks and an interface to implement custom plan checks. + +## State Checks + +After the configuration for a `TestStep` is applied, Terraform's testing +framework provides developers an opportunity to check the results by providing +one or more [state check +implementations](/terraform/plugin/testing/acceptance-tests/state-checks). +While possible to only supply a single state check, it is recommended you use +multiple state checks to validate specific information about the results of the +`terraform apply` ran in each `TestStep`. + +Refer to the [State Checks](/terraform/plugin/testing/acceptance-tests/state-checks) +section for more information about the built-in state checks for resources, +data sources, output values, and how to write custom state checks. + +## Query Checks + +After the configuration for a _Query_ mode `TestStep` is run, Terraform's +testing framework provides developers an opportunity to check the query results +by providing one or more [query check +implementations](/terraform/plugin/testing/acceptance-tests/query-checks). + +Refer to the [Query Checks](terraform/plugin/testing/acceptance-tests/query-checks) +section for more information about the built-in query checks for list resources +and how to write custom query checks. + +### Legacy Check function + + + +Use the new `ConfigStateChecks` attribute and [State Check implementations](/terraform/plugin/testing/acceptance-tests/state-checks) +instead of the `Check` function. + + + +The `Check` function is used to check results of a Terraform operation. The `Check` +attribute of `TestStep` is singular, so in order to include multiple checks +developers should use either `ComposeTestCheckFunc` or +`ComposeAggregateTestCheckFunc` (defined below) to group multiple check +functions, defined below: + +#### ComposeTestCheckFunc + +ComposeTestCheckFunc lets you compose multiple TestCheckFunc functions into a +single check. As a user testing their provider, this lets you decompose your +checks into smaller pieces more easily, with individual methods for checking +specific attributes. Each check is ran in the order provided, and on failure the +entire `TestCase` is stopped, and Terraform attempts to destroy any resources +created. + +Example: + +```go +Steps: []resource.TestStep{ + { + Config: testAccExampleResource(rName), + Check: resource.ComposeTestCheckFunc( + // if testAccCheckExampleResourceExists fails to find the resource, + // the parent TestStep and TestCase fail + testAccCheckExampleResourceExists("example_widget.foo", &widgetBefore), + resource.TestCheckResourceAttr("example_widget.foo", "size", "expected size"), + ), + }, +}, +``` + +#### ComposeAggregateTestCheckFunc + +ComposeAggregateTestCheckFunc lets you compose multiple TestCheckFunc functions +into a single check. It's purpose and usage is identical to +ComposeTestCheckFunc, however each check is ran in order even if a previous +check failed, collecting the errors returned from any checks and returning a +single aggregate error. The entire `TestCase` is still stopped, and Terraform +attempts to destroy any resources created. + +Example: + +```go +Steps: []resource.TestStep{ + { + Config: testAccExampleResource(rName), + Check: resource.ComposeAggregateTestCheckFunc( + // if testAccCheckExampleResourceExists fails to find the resource, + // the following TestCheckResourceAttr is still run, with any errors aggregated + testAccCheckExampleResourceExists("example_widget.foo", &widgetBefore), + resource.TestCheckResourceAttr("example_widget.foo", "active", "true"), + ), + }, +}, +``` + +#### Built-in check functions + +Terraform has several TestCheckFunc functions built in for developers to use for +common checks, such as verifying the status and value of a specific attribute in +the resulting state. Developers are encouraged to use as many as reasonable to +verify the behavior of the plugin/resource, and should combine them with the +above mentioned `ComposeTestCheckFunc` or `ComposeAggregateTestCheckFunc` +functions. + +Most builtin functions accept `name`, `key`, and/or `value` fields, derived from +the typical Terraform configuration stanzas: + +```hcl +resource "example_widget" "foo" { + active = true +} +``` + +Here the `name` represents the resource name in state (`example_widget.foo`), +the `key` represents the attribute to check (`active`), and `value` represents +the desired value to check against (`true`). In this case, an equality check +would be: + +```go +resource.TestCheckResourceAttr("example_widget.foo", "active", "true"), +``` + +The full list of functions can be seen in the [`helper/resource` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource). Names for these begin with `TestCheck...` and `TestMatch...`. The most common checks for non-`TypeSet` attributes are below. + +| Function | Purpose | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | +| [`TestCheckResourceAttr(name string, key string, value string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckResourceAttr) | Value equality checks | +| [`TestMatchResourceAttr(name string, key string, regex *regexp.Regexp)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestMatchResourceAttr) | | +| Value regular expression checks | | +| [`TestCheckResourceAttrPair(nameFirst string, keyFirst string, nameSecond string, keySecond string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckResourceAttrPair) | Value equality across two attributes (usually in different resources) | +| [`TestCheckResourceAttrSet(name string, key string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckResourceAttrSet) | Passes if any value was set | +| [`TestCheckNoResourceAttr(name string, key string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckNoResourceAttr) | Passes if no value was set | + +For `TypeSet` attributes, there are some additional functions that accept a `*` placeholder in attribute keys for indexing into the set. + +| Function | Purpose | +| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| [`TestCheckTypeSetElemAttr(name string, key string, value string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckTypeSetElemAttr) | Value is contained in set | +| [`TestCheckTypeSetElemAttrPair(nameFirst string, keyFirst string, nameSecond string, keySecond string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckTypeSetElemAttrPair) | Value is contained in set from another attribute (usually in different resources) | +| [`TestCheckTypeSetElemNestedAttrs(name string, key string, values map[string]string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckTypeSetElemNestedAttrs) | Map of values is contained in set (usually checking multiple attributes of a block) | + +All of these functions also accept the below syntax in attribute keys to enable additional behaviors. + +| Syntax | Purpose | Example | +| ----------- | --------------------------------- | ------------------------------------------------------------------------------- | +| `.{NUMBER}` | List index | `TestCheckResourceAttr("example_widget.foo", "some_block.0", "first value")` | +| `.{KEY}` | Map key | `TestCheckResourceAttr("example_widget.foo", "some_map.some_key", "map value")` | +| `.#` | Number of elements in list or set | `TestCheckResourceAttr("example_widget.foo", "some_list.#", "2")` | +| `.%` | Number of keys in map | `TestCheckResourceAttr("example_widget.foo", "some_map.%", "2")` | + +### Custom check functions + +The `Check` field of `TestStep` accepts any function of type +[TestCheckFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckFunc). +Developers are free to write their own `check` functions to create customized +validation functions for their plugin. Any function that matches the +`TestCheckFunc` function signature of `func(*terraform.State) error` can be used +individually, or with other `TestCheckFunc` functions with one of the above +Aggregate functions. + +It's common to write custom `TestCheckFunc` functions to validate resources were +created correctly by using SDKs directly to verify identity and properties of +resources. These functions can retrieve information by SDKs and provide the +results to other `TestCheckFunc` methods. The below example uses +`ComposeTestCheckFunc` to group a set of `TestCheckFunc` functions together. The +first function `testAccCheckExampleWidgetExists` uses the `Example` service SDK +directly, and queries it for the ID of the widget we have in state. Once found, +the result is stored into the `widget` struct declared at the beginning of the +test function. The next check function `testAccCheckExampleWidgetAttributes` +receives the updated `widget` and checks its attributes. The final check +`TestCheckResourceAttr` verifies that the same value is stored in state. + +```go +func TestAccExampleWidget_basic(t *testing.T) { + var widget example.WidgetDescription + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleWidgetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccExampleWidgetConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckExampleWidgetExists("example_widget.bar", &widget), + testAccCheckExampleWidgetAttributes(&widget), + resource.TestCheckResourceAttr("example_widget.bar", "active", "true"), + ), + }, + }, + }) +} + +// testAccCheckExampleWidgetAttributes verifies attributes are set correctly by +// Terraform +func testAccCheckExampleWidgetAttributes(widget *example.WidgetDescription) resource.TestCheckFunc { + return func(s *terraform.State) error { + if *widget.active != true { + return fmt.Errorf("widget is not active") + } + + return nil + } +} + +// testAccCheckExampleWidgetExists uses the Example SDK directly to retrieve +// the Widget description, and stores it in the provided +// *example.WidgetDescription +func testAccCheckExampleWidgetExists(resourceName string, widget *example.WidgetDescription) resource.TestCheckFunc { + return func(s *terraform.State) error { + // retrieve the resource by name from state + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("Widget ID is not set") + } + + // retrieve the client from the test provider + client := testAccProvider.Meta().(*ExampleClient) + + response, err := client.DescribeWidgets(&example.DescribeWidgetsInput{ + WidgetIDs: []string{rs.Primary.ID}, + }) + + if err != nil { + return err + } + + // we expect only a single widget by this ID. If we find zero, or many, + // then we consider this an error + if len(response.WidgetDescriptions) != 1 || + *response.WidgetDescriptions[0].WidgetID != rs.Primary.ID { + return fmt.Errorf("Widget not found") + } + + // store the resulting widget in the *example.WidgetDescription pointer + *widget = *response.WidgetDescriptions[0] + return nil + } +} +``` + +## Sweepers + +Acceptance Testing is an essential approach to validating the implementation of a Terraform Provider. Using actual APIs to provision resources for testing can leave behind real infrastructure that costs money between tests. The reasons for these leaks can vary, regardless Terraform provides a mechanism known as [Sweepers](/terraform/plugin/testing/acceptance-tests/sweepers) to help keep the testing account clean. diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/tfjson-paths.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/tfjson-paths.mdx new file mode 100644 index 0000000000..a40e90f52b --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/tfjson-paths.mdx @@ -0,0 +1,692 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Terraform JSON Paths' +description: >- + How to implement attribute paths in the testing module. + Attribute paths represent the location of an attribute within Terraform JSON data. +--- + +# Terraform JSON Paths + +An exact location within Terraform JSON data is referred to as a Terraform JSON or tfjson path. + +## Usage + +Example uses in the testing module include: + +- The `ExpectUnknownValue()` and `ExpectSensitiveValue()` [built-in plan checks](/terraform/plugin/testing/acceptance-tests/plan-checks#built-in-plan-checks) for specifying an attribute to make the check assertion against. + +## Concepts + +Terraform JSON Paths are designed around the underlying Go types corresponding to the Terraform JSON implementation of a schema and schema-based data. The [terraform-json](https://pkg.go.dev/github.com/hashicorp/terraform-json) library serves as the de-facto documentation for Terraform JSON data. Paths are always absolute and start from the root, or top level, of a JSON object. + +Given the tree structure of JSON objects, descriptions of paths and their steps borrow certain hierarchy terminology such as parent and child. A parent path describes a path without one or more of the final steps of a given path, or put differently, a partial path closer to the root of the object. A child path describes a path with one or more additional steps beyond a given path, or put differently, a path containing the given path but further from the root of the object. + +## Building Paths + +The `terraform-plugin-testing` module implementation for tfjson paths is in the [`tfjsonpath` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfjsonpath), with the [`tfjsonpath.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfjsonpath#Path) being the main provider developer interaction point. Call the [`tfjsonpath.New()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfjsonpath#New) with a property name at the root of the object to begin a path. + +Given the following JSON object + +```json +{ + "first_name": "John", + "last_name": "Doe", + "age": 18, + "street_address": "123 Terraform Dr.", + "phone_numbers": [ + { "mobile": "111-111-1111" }, + { "home": "222-222-2222" } + ] +} +``` + +The call to `tfjsonpath.New()` which matches the location of `first_name` string value is: + +```go +tfjsonpath.New("first_name") +``` + +Once a `tfjsonpath.Path` is started, it supports a builder pattern, which allows for chaining method calls to construct a full path. + +The path which matches the location of the string value `"222-222-222"` is: + +```go +tfjsonpath.New("phone_numbers").AtSliceIndex(1).AtMapKey("home") +``` + +The most common usage of `tfjsonpath.Path` is to specify an attribute within Terraform JSON data. When used in this way, the root of the JSON object is the same as the root of a schema. +The follow sections show how to build attribute paths for [primitive attributes](#building-attribute-paths), [aggregate attributes](#building-aggregate-type-attribute-paths), [nested attributes](#building-nested-attribute-paths), and [blocks](#building-block-paths). + +### Building Attribute Paths + +The following table shows the different [`tfjsonpath.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfjsonpath#Path) methods associated with building paths for attribute implementations. Attribute types that cannot be traversed further are shown with N/A (not applicable). + +| Framework Attribute Type | SDKv2 Attribute Type | Child Path Method | +|---------------------------|----------------------|-------------------| +| `schema.BoolAttribute` | `schema.TypeBool` | N/A | +| `schema.Float32Attribute` | `schema.TypeFloat` | N/A | +| `schema.Float64Attribute` | `schema.TypeFloat` | N/A | +| `schema.Int32Attribute` | `schema.TypeInt` | N/A | +| `schema.Int64Attribute` | `schema.TypeInt` | N/A | +| `schema.ListAttribute` | `schema.TypeList` | `AtSliceIndex()` | +| `schema.MapAttribute` | `schema.TypeMap` | `AtMapKey()` | +| `schema.NumberAttribute` | N/A | N/A | +| `schema.ObjectAttribute` | N/A | `AtMapKey()` | +| `schema.SetAttribute` | `schema.TypeSet` | `AtSliceIndex()` | +| `schema.StringAttribute` | `schema.TypeString` | N/A | + + +Given this example schema with a root attribute named `example_root_attribute`: + +```go +//Terraform Plugin Framework +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_root_attribute": schema.StringAttribute{ + Required: true, + }, + }, +} + +//Terraform Plugin SDKv2 +Schema: map[string]*schema.Schema{ + "example_root_attribute": { + Type: schema.TypeString, + Required: true, + }, +}, +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "example_root_attribute": "example-value" +} +``` + +The call to `tfjsonpath.New()` which matches the location of `example_root_attribute` string value is: + +```go +tfjsonpath.New("example_root_attribute") +``` + +For blocks, the beginning of a path is similarly defined. + +Given this example schema with a root block named `example_root_block`: + +```go +//Terraform Plugin Framework +schema.Schema{ + Blocks: map[string]schema.Block{ + "example_root_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{/* ... */}, + }, + }, +} + +//Terraform Plugin SDKv2 +Schema: map[string]*schema.Schema{ + "example_root_block": { + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{/* ... */}, + }, + }, +}, +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "example_root_block": [ + {} + ] +} +``` + +The call to `tfjsonpath.New()` which matches the location of `example_root_block` slice value is: + +```go +tfjsonpath.New("example_root_block") +``` + +### Building Aggregate Type Attribute Paths + +Given following schema example: + +```go +//Terraform Plugin Framework +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_map_attribute": schema.MapAttribute{ + ElementType: types.StringType, + Required: true, + }, + "root_list_attribute": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + }, + "root_set_attribute": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + }, + }, +} + +//Terraform Plugin SDKv2 +Schema: map[string]*schema.Schema{ + "root_map_attribute": { + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Required: true, + }, + "root_list_attribute": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Required: true, + }, + "root_set_attribute": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Required: true, + }, +}, +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "root_map_attribute": { + "example-key": "map-value" + }, + "root_list_attribute": [ + "list-value1", + "list-value2" + ], + "root_set_attribute": [ + "set-value1", + "set-value2" + ] +} +``` + +The path which matches the string value associated with the map key `example-key` of the `root_map_attribute` attribute is: + +```go +tfjsonpath.New("root_map_attribute").AtMapKey("example-key") +``` + +The path which matches the string value `list-value1` in the `root_list_attribute` attribute is: + +```go +tfjsonpath.New("root_list_attribute").AtSliceIndex(0) +``` + +The path which matches the string value `set-value2` in the `root_set_attribute` attribute is: + +```go +tfjsonpath.New("root_set_attribute").AtSliceIndex(1) +``` + +Note that because Sets are unordered in Terraform, the ordering of Set elements in the Terraform JSON data is not guaranteed to be the same as the ordering in the configuration. + +### Building Nested Attribute Paths + +The following table shows the different [`tfjsonpath.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfjsonpath#Path) methods associated with building paths for nested attributes. + +| Nested Attribute Type | Child Path Method(s) | +|--------------------------------|-----------------------------| +| `schema.ListNestedAttribute` | `AtSliceIndex().AtMapKey()` | +| `schema.MapNestedAttribute` | `AtMapKey().AtMapKey()` | +| `schema.SetNestedAttribute` | `AtSliceIndex().AtMapKey()` | +| `schema.SingleNestedAttribute` | `AtMapKey()` | + +Nested attributes eventually follow the same path rules as attributes at child paths, which follow the methods shown in the [Building Attribute Paths section](#building-attribute-paths). + +#### Building List Nested Attributes Paths + +An attribute that implements `schema.ListNestedAttribute` conceptually is a slice containing a map with attribute names as keys. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_list_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "root_list_attribute": [ + { + "nested_string_attribute": "value" + } + ] +} +``` + +The path which matches the slice associated with the `root_list_attribute` attribute is: + +```go +tfjsonpath.New("root_list_attribute") +``` + +The path which matches the first map in the slice associated with the `root_list_attribute` attribute is: + +```go +tfjsonpath.New("root_list_attribute").AtSliceIndex(0) +``` + +The path which matches the `nested_string_attribute` map key in the first map in the slice associated with `root_list_attribute` attribute is: + +```go +tfjsonpath.New("root_list_attribute").AtSliceIndex(0).AtMapKey("nested_string_attribute") +``` + +#### Building Map Nested Attributes Paths + +An attribute that implements `schema.MapNestedAttribute` conceptually is a map containing values of maps with attribute names as keys. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_map_attribute": schema.MapNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "root_map_attribute": { + "example-key" : { + "nested_string_attribute": "value" + } + } +} +``` + +The path which matches the map associated with the `root_map_attribute` attribute is: + +```go +tfjsonpath.New("root_map_attribute") +``` + +The path which matches the `"example-key"` object in the map associated with the `root_map_attribute` attribute is: + +```go +tfjsonpath.New("root_map_attribute").AtMapKey("example-key") +``` + +The path which matches the `nested_string_attribute` string value in a `"example-key"` object in the map associated with `root_map_attribute` attribute is: + +```go +tfjsonpath.New("root_map_attribute").AtMapKey("example-key").AtMapKey("nested_string_attribute") +``` + +#### Building Set Nested Attributes Paths + +An attribute that implements `schema.SetNestedAttribute` conceptually is a slice containing maps with attribute names as keys. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_set_attribute": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "root_set_attribute": [ + { + "nested_string_attribute": "value" + } + ] +} +``` + +The path which matches the set associated with the `root_set_attribute` attribute is: + +```go +tfjsonpath.New("root_set_attribute") +``` + +The path which matches the first map in the slice associated with the `root_set_attribute` attribute is: + +```go +tfjsonpath.New("root_set_attribute").AtSliceIndex(0) +``` + +Note that because Sets are unordered in Terraform, the ordering of Set elements in the Terraform JSON data is not guaranteed to be the same as the ordering in the configuration. + +The path which matches the `nested_string_attribute` map key in the first map in the slice associated with `root_set_attribute` attribute is: + +```go +tfjsonpath.New("root_set_attribute").AtSliceIndex(0).AtMapKey("nested_string_attribute") +``` + +#### Building Single Nested Attributes Paths + +An attribute that implements `schema.SingleNestedAttribute` conceptually is a map with attribute names as keys. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_grouped_attributes": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + Required: true, + }, + }, +} +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "root_grouped_attributes": { + "nested_string_attribute": "value" + } +} +``` + +The path which matches the map associated with the `root_grouped_attributes` attribute is: + +```go +tfjsonpath.New("root_grouped_attributes") +``` + +The path which matches the `nested_string_attribute` string value in the map associated with the `root_grouped_attributes` attribute is: + +```go +tfjsonpath.New("root_grouped_attributes").AtMapKey("nested_string_attribute") +``` + +### Building Block Paths + +The following table shows the different [`tfjsonpath.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfjsonpath#Path) methods associated with building paths for blocks. + +| Block Type | Child Path Method(s) | +|---------------------|-----------------------------| +| `ListNestedBlock` | `AtSliceIndex().AtMapKey()` | +| `SetNestedBlock` | `AtSliceIndex().AtMapKey()` | +| `SingleNestedBlock` | `AtMapKey()` | + +Blocks can implement nested blocks. Paths can continue to be built using the associated method with each level of the block type. + +Blocks eventually follow the same path rules as attributes at child paths, which follow the methods shown in the [Building Attribute Paths section](#building-attribute-paths). Blocks cannot contain nested attributes. + +#### Building List Block Paths + +A `ListNestedBlock` conceptually is a slice containing maps with attribute or block names as keys. + +Given following schema example: + +```go +//Terraform Plugin Framework +schema.Schema{ + Blocks: map[string]schema.Block{ + "root_list_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "nested_list_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nested_block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, +} + +//Terraform Plugin SDKv2 +Schema: map[string]*schema.Schema{ + "root_list_block": { + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "block_string_attribute": { + Type: schema.TypeString, + Required: true, + }, + "nested_list_block": { + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "nested_block_string_attribute": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, +}, +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "root_list_block": [ + { + "block_string_attribute": "value1", + "nested_list_block": [ + {"nested_block_string_attribute": "value2"} + ] + } + ] +} +``` + +The path which matches the slice associated with the `root_list_block` block is: + +```go +tfjsonpath.New("root_list_block") +``` + +The path which matches the first map in the slice associated with the `root_list_block` block is: + +```go +tfjsonpath.New("root_list_block").AtSliceIndex(0) +``` + +The path which matches the `block_string_attribute` string value in the first map in the slice associated with `root_list_block` block is: + +```go +tfjsonpath.New("root_list_block").AtSliceIndex(0).AtMapKey("block_string_attribute") +``` + +The path which matches the `nested_list_block` slice in the first object in the slice associated with `root_list_block` block is: + +```go +tfjsonpath.New("root_list_block").AtSliceIndex(0).AtMapKey("nested_list_block") +``` + +The path which matches the `nested_block_string_attribute` string value in the first map in the slice associated with the `nested_list_block` slice in the first map in the slice associated with `root_list_block` block is: + +```go +tfjsonpath.New("root_list_block").AtSliceIndex(0).AtMapKey("nested_list_block").AtSliceIndex(0).AtMapKey("nested_block_string_attribute") +``` + +#### Building Set Block Paths + +A `SetNestedBlock` conceptually is a slice containing maps with attribute or block names as keys. + +Given following schema example: + +```go +//Terraform Plugin Framework +schema.Schema{ + Blocks: map[string]schema.Block{ + "root_set_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "block_string_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, +} + +//Terraform Plugin SDKv2 +Schema: map[string]*schema.Schema{ + "root_set_block": { + Type: schema.TypeSet, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "block_string_attribute": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, +}, +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "root_set_block": [ + { + "block_string_attribute": "value1" + } + ] +} +``` + +The path which matches the slice associated with the `root_set_block` block is: + +```go +tfjsonpath.New("root_set_block") +``` + + +The path which matches the first map in the slice associated with the `root_set_block` block is: + +```go +tfjsonpath.New("root_set_block").AtSliceIndex(0) +``` + +Note that because sets are unordered in Terraform, the ordering of set elements in the Terraform JSON data is not guaranteed to be the same as the ordering in the configuration. + +The path which matches the `block_string_attribute` string value in the first map in the slice associated with `root_set_block` block is: + +```go +tfjsonpath.New("root_set_block").AtSliceIndex(0).AtMapKey("block_string_attribute") +```` + +#### Building Single Block Paths + +A `SingleNestedBlock` conceptually is a map with attribute or block names as keys. + +Given following schema example: + +```go +//Terraform Plugin Framework +schema.Schema{ + Blocks: map[string]schema.Block{ + "root_single_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "nested_single_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "nested_block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + }, +} +``` + +The path which matches the map associated with the `root_single_block` block is: + +```go +tfjsonpath.New("root_single_block") +``` + +The path which matches the `block_string_attribute` string value in the map associated with `root_single_block` block is: + +```go +tfjsonpath.New("root_single_block").AtMapKey("block_string_attribute") +``` + +The path which matches the `nested_single_block` map in the map associated with `root_single_block` block is: + +```go +tfjsonpath.New("root_single_block").AtMapKey("nested_single_block") +``` + +The path which matches the `nested_block_string_attribute` string value in the map associated with the `nested_single_block` in the map associated with `root_single_block` block is: + +```go +tfjsonpath.New("root_single_block").AtMapKey("nested_single_block").AtMapKey("nested_block_string_attribute") +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/tfversion-checks.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/tfversion-checks.mdx new file mode 100644 index 0000000000..3c8efd4f03 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/tfversion-checks.mdx @@ -0,0 +1,267 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Terraform Version Checks' +description: >- + Terraform Version Checks are generic checks defined at the TestCase level that check logic against the Terraform CLI version. The testing module + provides built-in Version Checks for common use-cases, but custom Version Checks can also be implemented. +--- + +# Terraform Version Checks + +**Terraform Version Checks** are generic checks defined at the TestCase level that check logic against the Terraform CLI version. The checks are executed at the beginning of the TestCase before any TestStep is executed. + +The Terraform CLI version is determined by the binary selected by the [`TF_ACC_TERRAFORM_PATH`](/terraform/plugin/testing/acceptance-tests#environment-variables) environment variable value, installed by the [`TF_ACC_TERRAFORM_VERSION`](/terraform/plugin/testing/acceptance-tests#environment-variables) value, or already existing based on the `PATH` environment variable. + +A **version check** will either return an error and fail the associated test, return a skip message and pass the associated test immediately by skipping, or it will return nothing and allow the associated test to run. + +Terraform CLI prerelease versions include a `-alphaYYYYMMDD`, `-beta#`, or `rc#` (release candidate) suffix for a minor version with `0` patch version. For example, `1.8.0-rc1`. Prereleases of Terraform are considered semantically equivalent to the associated minor version since prereleases are when any new features are introduced. + +## Built-in Version Checks and Variables + +The `terraform-plugin-testing` module provides a package [`tfversion`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion) with built-in version checks for common use-cases. There are three types of version checks: Skip Checks, Require Checks, and Collection Checks. + + + +Built-in version checks handle prereleases of a minor version as semantically equivalent to given minor versions. For example, if the test includes `tfversion.SkipBelow(tfversion.Version1_8_0)` and the running Terraform CLI version is `1.8.0-rc1`, the test will run, not skip. This is intended to enable prerelease testing of new features. + + + +### Version Variables + +The built-in checks in the `tfversion` package typically require the use of the [`github.com/hashicorp/go-version`](https://pkg.go.dev/github.com/hashicorp/go-version) module [`version.Version`](https://pkg.go.dev/github.com/hashicorp/go-version#Version) type. To simplify provider testing implementations, the `tfversion` package provides [built-in variables](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#pkg-variables) for common use case versions, such as each released minor and major Terraform version. These follow the pattern of `Version{MAJOR}_{MINOR}_{PATCH}` with the major, minor, and patch version numbers, such as `Version1_2_0`. + +### Skip Version Checks + +Skip Version Checks will pass the associated test by skipping and provide a skip message if the detected Terraform CLI version satisfies the specified check criteria. + +| Check | Description | +|---------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| +| [`tfversion.SkipAbove(maximumVersion)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#SkipAbove) | Skips the test if the Terraform CLI version is exclusively above the given maximum. | +| [`tfversion.SkipBelow(minimumVersion)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#SkipBelow) | Skips the test if the Terraform CLI version is exclusively below the given minimum. | +| [`tfversion.SkipBetween(minimumVersion, maximumVersion)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#SkipBetween) | Skips the test if the Terraform CLI version is between the given minimum (inclusive) and maximum (exclusive). | +| [`tfversion.SkipIf(version)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#SkipIf) | Skips the test if the Terraform CLI version matches the given version. | + +#### Example using `tfversion.SkipBetween` + +The built-in [`tfversion.SkipBetween`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#SkipBetween) version check is useful for skipping all patch versions associated with a minor version. + +In the following example, we have written a test that skips all Terraform CLI patch versions associated with 0.14.0: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_Skip_TF14(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": func() (tfprotov6.ProviderServer, error) { + return nil, nil + }, + }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBetween(tfversion.Version0_14_0, tfversion.Version0_15_0), + }, + Steps: []resource.TestStep{ + { + Config: `//example test config`, + }, + }, + }) +} +``` + +### Require Version Checks + +Require Version Checks will raise an error and fail the associated test if the detected Terraform CLI version does not satisfy the specified check requirements. + +| Check | Description | +|---------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| +| [`tfversion.RequireAbove(minimumVersion)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#RequireAbove) | Fails the test if the Terraform CLI version is exclusively below the given minimum. | +| [`tfversion.RequireBelow(maximumVersion)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#RequireBelow) | Fails the test if the Terraform CLI version is inclusively above the given maximum. | +| [`tfversion.RequireBetween(minimumVersion, maximumVersion)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#RequireBetween) | Fails the test if the Terraform CLI version is outside the given minimum (exclusive) and maximum (inclusive). | +| [`tfversion.RequireNot(version)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#RequireNot) | Fails the test if the Terraform CLI version matches the given version. | + + +#### Example using `tfversion.RequireAbove` + +The built-in [`tfversion.RequireAbove`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#RequireAbove) version check is useful for required tests that may use features only available in newer versions of the Terraform ClI. + +In the following example, the test Terraform configuration uses the `nullable` argument for an input variable, a feature that is only available in Terraform CLI versions `1.3.0` and above. The version check will fail the test with a specific error if the detected version is below `1.3.0`. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_Require_TF1_3(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": func() (tfprotov6.ProviderServer, error) { + return nil, nil + }, + }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_3_0), + }, + Steps: []resource.TestStep{ + { + Config: `variable "a" { + nullable = true + default = "hello" + }`, + }, + }, + }) +} +``` + +### Collection Version Checks + +Collection Version Checks operate on multiple version checks and can be used to create more complex checks. + +[`tfversion.Any(TerraformVersionChecks...)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#Any) will run the associated test by returning a nil error and empty skip message +if any of the given version sub-checks return a nil error and empty skip message. If none of the sub-checks return a nil error and empty skip message, then the check will return all sub-check errors and fail the associated test. +Otherwise, if none of the sub-checks return a non-nil error, the check will pass the associated test by skipping and return all sub-check skip messages. + +[`tfversion.All(TerraformVersionChecks...)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#All) will either fail or skip the associated test if any of the given sub-checks return a non-nil error or non-empty skip message. The check will return the +first non-nil error or non-empty skip message from the given sub-checks in the order that they are given. Otherwise, if all sub-checks return a nil error and empty skip message, then the check will return a nil error and empty skip message and run the associated test. This check should only be +used in conjunction with `tfversion.Any()` as the behavior provided by this check is applied to the `TerraformVersionChecks` field by default. + +#### Example using `tfversion.Any` + +In the following example, the test will only run if either the Terraform CLI version is above `1.2.0` or if it's below `1.0.0` but not version `0.15.0`, otherwise an error will be returned. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_Any(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": func() (tfprotov6.ProviderServer, error) { //nolint:unparam // required signature + return nil, nil + }, + }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.Any( + tfversion.All( + tfversion.RequireNot(tfversion.Version0_15_0), + tfversion.RequireBelow(tfversion.Version1_0_0), + ), + tfversion.RequireAbove(tfversion.Version1_2_0), + ), + }, + Steps: []resource.TestStep{ + { + Config: `//example test config`, + }, + }, + }) +} +``` + + +## Custom Version Checks + +The package [`tfversion`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion) also provides the [`TerraformVersionCheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#TerraformVersionCheck) interface, which can be implemented for a custom version check. + +The [`tfversion.CheckTerraformVersionRequest`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#CheckTerraformVersionRequest) has a `TerraformVersion` field of type [`*version.Version`](https://pkg.go.dev/github.com/hashicorp/go-version#Version) which contains the version of the Terraform CLI binary running the test. + +The [`tfversion.CheckTerraformVersionResponse`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#CheckTerraformVersionResponse) has an `Error` field and a `Skip` field. The behavior of the version check depends on which field is populated. Populating the `Error` field will fail the associated test with the given error. +Populating the `Skip` field will pass the associated test by skipping the test with the given skip message. Only one of these fields should be populated. + +Here is an example implementation of a version check returns an error if the detected Terraform CLI version matches the given version: + +```go +package example_test + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-version" +) + +// Ensure implementation satisfies the tfversion.TerraformVersionCheck interface. +var _ tfversion.TerraformVersionCheck = requireNotCheck{} + +// RequireNot will fail the test if the given version matches. +func RequireNot(v *version.Version) tfversion.TerraformVersionCheck { + return requireNotCheck{ + version: v, + } +} + +type requireNotCheck struct { + version *version.Version +} + +func (s requireNotCheck) CheckTerraformVersion(ctx context.Context, req tfversion.CheckTerraformVersionRequest, resp *tfversion.CheckTerraformVersionResponse) { + if req.TerraformVersion.Equal(s.version) { + resp.Error = fmt.Errorf("unexpected Terraform CLI version: %s", s.version) + } +} +``` + +And example usage: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_RequireNot(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": func() (tfprotov6.ProviderServer, error) { + return nil, nil + }, + }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireNot(tfversion.Version0_13_0), + }, + Steps: []resource.TestStep{ + { + Config: `//example test config`, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/value-comparers/index.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/value-comparers/index.mdx new file mode 100644 index 0000000000..3ef5e19d6a --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/acceptance-tests/value-comparers/index.mdx @@ -0,0 +1,183 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Value Comparers' +description: >- + How to use value comparers in the testing module. + Value comparers define a comparison for a resource attribute, or output value for use in State Checks. +--- + +# Value Comparers + + + +Value Comparers are for use in conjunction with [State Checks](/terraform/plugin/testing/acceptance-tests/state-checks), which leverage the [terraform-json](https://pkg.go.dev/github.com/hashicorp/terraform-json) representation of Terraform state. + + + +Value comparers can be used to assert a resource or data source attribute value across multiple [Test Steps](/terraform/plugin/testing/acceptance-tests/teststep), like asserting that a randomly generated resource attribute doesn't change after multiple apply steps. This is done by creating the value comparer, typically before the test case is defined, using the relevant constructor function: +```go +func TestExample(t *testing.T) { + // Create the value comparer so we can add state values to it during the test steps + compareValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + // .. test steps omitted + }, + }) +} +``` + +Once the value comparer is created, state values can be added in `TestStep.ConfigStateChecks`: +```go +func TestExample(t *testing.T) { + // Create the value comparer so we can add state values to it during the test steps + compareValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + // Add the current state value of "computed_attribute" to the value comparer. + // Since there are no other state values at this point, no assertion is made. + compareValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + // Add the current state value of "computed_attribute" to the value comparer. + // Since there is an existing state value in the value comparer at this point, + // if the two values are equal, the test will produce an error. + compareValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + }, + }) +} +``` + +The value comparer implementation (defined by the [`ValueComparer` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValueComparer)) determines what assertion occurs when a state value is added. The built-in value comparers are: +- [`CompareValue()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevalue-state-check) +- [`CompareValueCollection()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluecollection-state-check) +- [`CompareValuePairs()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluepairs-state-check) + +## Values Differ + +The [ValuesDiffer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValuesDiffer) value comparer verifies that each value in the sequence of values supplied to the `CompareValues()` method differs from the preceding value. + +Example usage of [ValuesDiffer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValuesDiffer) in a [CompareValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevalue-state-check) state check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValue_CheckState_ValuesDiffer(t *testing.T) { + t.Parallel() + + compareValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + // Add the current state value of "computed_attribute" to the value comparer. + // Since there are no other state values at this point, no assertion is made. + compareValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + { + // Add the current state value of "computed_attribute" to the value comparer. + // Since there is an existing state value in the value comparer at this point, + // if the two values are equal, the test will produce an error. + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + compareValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + }, + }) +} +``` + +## Values Same + +The [ValuesSame](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValuesSame) value comparer verifies that each value in the sequence of values supplied to the `CompareValues()` method is the same as the preceding value. + +Example usage of [ValuesSame](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValuesSame) in a [CompareValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevalue-state-check) state check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValue_CheckState_ValuesSame(t *testing.T) { + t.Parallel() + + compareValuesSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + // Add the current state value of "computed_attribute" to the value comparer. + // Since there are no other state values at this point, no assertion is made. + compareValuesSame.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + // Add the current state value of "computed_attribute" to the value comparer. + // Since there is an existing state value in the value comparer at this point, + // if the two values are not equal, the test will produce an error. + compareValuesSame.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/index.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/index.mdx new file mode 100644 index 0000000000..2ff339fb32 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/index.mdx @@ -0,0 +1,55 @@ +--- +page_title: Plugin Development - Testing +description: |- + Write acceptance tests and unit tests with the terraform-plugin-testing Go + module. +--- + +# Plugin Testing + +The testing framework for Terraform providers,`terraform-plugin-testing`, +uses standard Go features such as the `go test` command and the standard +library `testing` package. In addition to this documentation, refer to the +[generated Go +documentation](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing). + +This section introduces strategies for testing provider functionality with +acceptance tests and unit tests. + +## Acceptance Tests + +Acceptance tests for Terraform providers are a feature of the +[`terraform-plugin-testing`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing) +framework. The testing framework uses standard Go features such as the `go +test` command. It runs a local Terraform binary to perform real plan, apply, +refresh, and destroy operations, and enables developers to make assertions +about what happens during those actions. + +Refer to [Acceptance Testing](/terraform/plugin/testing/acceptance-tests) to learn more. + +## Unit Tests + +Testing plugin code in small, isolated units is distinct from Acceptance Tests, +and does not require network connections. Unit tests are commonly used for +testing helper methods that expand or flatten API response data into data +structures for storage into state by Terraform. This section covers the +specifics of writing Unit Tests for Terraform Plugin code. + +For a given plugin, Unit Tests can be run from the root of the project by using +a common make task: + +```shell +$ make test +``` + +Refer to [Unit Testing](/terraform/plugin/testing/unit-testing) to learn more. + +## Testing Patterns + +Terraform developers are encouraged to write acceptance tests that create real +resource to verify the behavior of plugins, ensuring a reliable and safe way to +manage infrastructure. In [Testing +Patterns](/terraform/plugin/testing/testing-patterns) we cover some basic +acceptance tests that almost all resources should have to validate not only the +functionality of the resource, but that the resource behaves as Terraform would +expect. diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/migrating.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/migrating.mdx new file mode 100644 index 0000000000..51de606104 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/migrating.mdx @@ -0,0 +1,133 @@ +--- +page_title: 'Plugin Development: Migrating testing from SDKv2 to the testing module' +description: >- + Migrate your provider's acceptance testing dependencies from SDKv2 to the testing module. +--- + +# Overview + +This guide helps you migrate a Terraform provider's acceptance testing dependencies from SDKv2 to the plugin testing module. We recommend migrating to terraform-plugin-testing to take advantage of new features of the testing module and to avoid importing the SDKv2 for providers that are built on the plugin Framework. + +This guide provides information and examples for most common use cases, but it does not discuss every nuance of migration. You can ask additional migration questions in the [HashiCorp Discuss forum](https://discuss.hashicorp.com/c/terraform-providers/tf-plugin-sdk/43). To request additions or updates to this guide, submit issues or pull requests to the [`terraform-plugin-testing` repository](https://github.com/hashicorp/terraform-plugin-testing). + +## Migration steps + +Take the following steps when you migrate a provider's acceptance tests from SDKv2 to the testing module. + +Change all instances of the following Go import statements in `*_test.go` files: + +| Original Import | Migrated Import | +|---|---| +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest` | `github.com/hashicorp/terraform-plugin-testing/helper/acctest` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource` | `github.com/hashicorp/terraform-plugin-testing/helper/resource` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/terraform` | `github.com/hashicorp/terraform-plugin-testing/terraform` | + +If the provider implements terraform-plugin-sdk based state migration unit testing with `github.com/hashicorp/terraform-plugin-sdk/v2/terraform.InstanceState`, this must remain with the original import since it is testing terraform-plugin-sdk functionality. + +Verify if the `TestStep` type `PlanOnly` field is enabled in any tests where the final `TestStep` is intentionally changing the provider setup to ensure schema changes (e.g. state upgrades or SDK to framework migrations) cause no plan differences. In those tests, replace `PlanOnly` with `ConfigPlanChecks` containing a `PreApply` check of `plancheck.ExpectEmptyPlan()` instead: + +```go +resource.Test(t, resource.TestCase{ + // ... + Steps: []resource.TestStep{ + { /* ... */ }, + { + // ... + // The below replacing PlanOnly: true + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, +}) +``` + +Change all instances of the following in **non-test** `*.go` files: + +| Original Reference | Migrated Reference | +|---|---| +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.NonRetryableError` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.NonRetryableError` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.NotFoundError` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.NotFoundError` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.PrefixedUniqueId` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/id.PrefixedUniqueId` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.Retry` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.Retry` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.RetryableError` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.RetryableError` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.RetryContext` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.RetryContext` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.RetryError` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.RetryError` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.RetryFunc` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.RetryFunc` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.StateChangeConf` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.StateChangeConf` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.StateRefreshFunc` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.StateRefreshFunc` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.TimeoutError` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.TimeoutError` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.UnexpectedStateError` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.UnexpectedStateError` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.UniqueId` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/id.UniqueId` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.UniqueIdPrefix` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/id.UniqueIdPrefix` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.UniqueIDSuffixLength` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/id.UniqueIDSuffixLength` | + +Get and download the latest version of terraform-plugin-testing: + +```shell +$ go get github.com/hashicorp/terraform-plugin-testing@latest +``` + +Clean up `go.mod`: + +```shell +$ go mod tidy +``` + +Verify that the tests are working as expected. + +## Troubleshooting + +### flag redefined Panic + +This panic occurs when your provider code imports both the `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource` and `github.com/hashicorp/terraform-plugin-testing/helper/resource` packages because they contain a duplicate `TestMain` function: + +```text +panic: XXX flag redefined: sweep + +goroutine 1 [running]: +flag.(*FlagSet).Var(0x14000030240, {0x10132b6d8, 0x140002219c0}, {0x10103ad88, 0x5}, {0x10105d47b, 0x29}) + /usr/local/go/src/flag/flag.go:982 +0x2a4 +flag.(*FlagSet).StringVar(...) + /usr/local/go/src/flag/flag.go:847 +flag.(*FlagSet).String(0x1400031fb98?, {0x10103ad88, 0x5}, {0x0, 0x0}, {0x10105d47b, 0x29}) + /usr/local/go/src/flag/flag.go:860 +0x98 +flag.String(...) + /usr/local/go/src/flag/flag.go:867 +github.com/hashicorp/terraform-plugin-testing/helper/resource.init() + /XXX/go/pkg/mod/github.com/hashicorp/terraform-plugin-testing@v1.1.0/helper/resource/testing.go:53 +0x44 +``` + +Remove imports of `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource` to resolve the issue. terraform-plugin-sdk version 2.26.0 introduced separate packages, [`github.com/hashicorp/terraform-plugin-sdk/v2/helper/id`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/id) and [`github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry), which contain all non-testing functionality. + +### Failed to marshal state to json + +This error can occur when your testing includes `PlanOnly: true` in final `TestStep` that is intentionally changing the provider setup to ensure schema changes (e.g. state upgrades or SDK to framework migrations) cause no plan differences: + +```text +Failed to marshal state to json: schema version 0 for examplecloud_thing.test in state does not match version 1 from the provider +# or in the case of removed attributes between provider versions: +Failed to marshal state to json: unsupported attribute +``` + +In those tests, replace `PlanOnly` with `ConfigPlanChecks` containing a `PreApply` check of `plancheck.ExpectEmptyPlan()` instead: + +```go +resource.Test(t, resource.TestCase{ + // ... + Steps: []resource.TestStep{ + { /* ... at least one prior step ... */ }, + { + // ... + // Replacing PlanOnly: true + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, +}) +``` diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/testing-patterns.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/testing-patterns.mdx new file mode 100644 index 0000000000..e431528e20 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/testing-patterns.mdx @@ -0,0 +1,392 @@ +--- +page_title: Plugin Development - Testing Patterns +description: |- + Testing Patterns covers essential acceptance test patterns to implement for + Terraform resources. +--- + +# Testing Patterns + +In [Testing Terraform Plugins][1] we introduce Terraform's Testing Framework, +providing reference for its functionality and introducing the basic parts of +writing acceptance tests. In this section we'll cover some test patterns that +are common and considered a best practice to have when developing and verifying +your Terraform plugins. At time of writing these guides are particular to +Terraform Resources, but other testing best practices may be added later. + +## Table of Contents + +- [Built-in Patterns](#built-in-patterns) +- [Basic test to verify attributes](#basic-test-to-verify-attributes) +- [Update test verify configuration changes](#update-test-verify-configuration-changes) +- [Import mode: verify that import and create produce the same state](#import-mode-verify-that-import-and-create-produce-the-same-state) +- [Expecting errors or non-empty plans](#expecting-errors-or-non-empty-plans) +- [Regression tests](#regression-tests) + +## Built-in Patterns + +Acceptance tests use [TestCases][2] to construct scenarios that can be evaluated +with Terraform's lifecycle of plan, apply, refresh, and destroy. The test +framework has some behaviors built in that provide very basic workflow assurance +tests, such as verifying configurations apply with no diff generated by the next +plan. + +Each TestCase will run any [PreCheck][3] function provided before running the +test, and then any [CheckDestroy][4] functions after the test concludes. These +functions allow developers to verify the state of the resource and test before +and after it runs. + +When a test is ran, Terraform runs plan, apply, refresh, and then final plan for +each [TestStep][5] in the TestCase. If the last plan results in a non-empty +plan, Terraform will exit with an error. This enables developers to ensure that +configurations apply cleanly. In the case of introducing regression tests or +otherwise testing specific error behavior, TestStep offers a boolean field +[ExpectNonEmptyPlan][6] as well [ExpectError][7] regex field to specify ways the +test framework can handle expected failures. If these properties are omitted and +either a non-empty plan occurs or an error encountered, Terraform will fail the +test. + +After all TestSteps have been ran, Terraform then runs destroy, and ends by +running any CheckDestroy function provided. + +[Back to top](#table-of-contents) + +## Basic test to verify attributes + +The most basic resource acceptance test should use what is likely to be a common +configuration for the resource under test, and verify that Terraform can +correctly create the resource, and that resources attributes are what Terraform +expects them to be. At a high level, the first basic test for a resource should +establish the following: + +- Terraform can plan and apply a common resource configuration without error. +- Verify the expected attributes are saved to state, and contain the values + expected. +- Verify the values in the remote API/Service for the resource match + what is stored in state. +- Verify that a subsequent terraform plan does not produce + a diff/change. + +The first and last item are provided by the test framework as described above in +**Built-in Patterns**. The middle items are implemented by composing a series of +State Check implementations as described in [Acceptance Tests: State Checks][8]. + +To verify attributes are saved to the state file correctly, use a combination of +the built-in [`statecheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck) +implementations provided by the testing framework. See [Resource State Checks][9] to see available +state checks for managed resource and data source attributes. + +Checking the values in a remote API generally consists of two parts: a function +to verify the corresponding object exists remotely, and a state check implementation to +verify the values of the object. By separating the check used to verify the +object exists into its own function, developers are free to re-use it for all +TestSteps as a means of retrieving it's values, and can provide [custom state checks][10] +functions per TestStep to verify remote values or scenarios specific to that TestStep. + +Here's an example test, with in-line comments to demonstrate the key parts of a +basic test. + +```go +package example + +// example.Widget represents a concrete Go type that represents an API resource +func TestAccExampleWidget_basic(t *testing.T) { + var widget example.Widget + + // generate a random name for each widget test run, to avoid + // collisions from multiple concurrent tests. + // the acctest package includes many helpers such as RandStringFromCharSet + // See https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/helper/acctest + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + // use a dynamic configuration with the random name from above + Config: testAccExampleResource(rName), + // compose a basic test, checking both remote and local values + ConfigStateChecks: []statecheck.StateCheck{ + // custom state check - query the API to retrieve the widget object + stateCheckExampleResourceExists("example_widget.foo", &widget), + // custom state check - verify remote values + stateCheckExampleWidgetValues(widget, rName), + // built-in state checks - verify local (state) values + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("active"), knownvalue.Bool(true)), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("name"), knownvalue.StringExact(rName)), + }, + }, + }, + }) +} + +// testAccExampleResource returns an configuration for an Example Widget with the provided name +func testAccExampleResource(name string) string { + return fmt.Sprintf(` +resource "example_widget" "foo" { + active = true + name = "%s" +}`, name) +} +``` + +This example covers all the items needed for a basic test, and will be +referenced or added to in the other test cases to come. + +[Back to top](#table-of-contents) + +## Update test verify configuration changes + +A basic test covers a simple configuration that should apply successfully and +with no follow up differences in state. To verify a resource correctly applies +updates, the second most common test found is an extension of the basic test, +that simply applies another `TestStep` with a modified version of the original +configuration. + +Below is an example test, copied and modified from the basic test. Here we +preserve the `TestStep` from the basic test, but we add an additional +`TestStep`, changing the configuration and rechecking the values, with a +different configuration function `testAccExampleResourceUpdated` and state check +implementation `stateCheckExampleWidgetValuesUpdated` for verifying the values. + +```go +func TestAccExampleWidget_update(t *testing.T) { + var widget example.Widget + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + // use a dynamic configuration with the random name from above + Config: testAccExampleResource(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widget), + stateCheckExampleWidgetValues(widget, rName), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("active"), knownvalue.Bool(true)), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("name"), knownvalue.StringExact(rName)), + }, + }, + { + // use a dynamic configuration with the random name from above + Config: testAccExampleResourceUpdated(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widget), + stateCheckExampleWidgetValuesUpdated(widget, rName), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("active"), knownvalue.Bool(false)), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("name"), knownvalue.StringExact(rName)), + }, + }, + }, + }) +} + +// testAccExampleResource returns an configuration for an Example Widget with the provided name +func testAccExampleResourceUpdated(name string) string { + return fmt.Sprintf(` +resource "example_widget" "foo" { + active = false + name = "%s" +}`, name) +} +``` + +It's common for resources to just have the above update test, as it is a +superset of the basic test. So long as the basics are covered, combining the two +tests is sufficient as opposed to having two separate tests. + +[Back to top](#table-of-contents) + +## Import mode: verify that import and create produce the same state + +To exercise a provider's import behaviors, the test author adds a test step in import mode. +In this example, `ImportState: true` indicates an import mode test step. + +```go +func TestAccExampleWidget_update(t *testing.T) { + var widget example.Widget + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + // Test Step 1: config mode + Config: testAccExampleResource(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widget), + stateCheckExampleWidgetValues(widget, rName), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("active"), knownvalue.Bool(true)), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("name"), knownvalue.StringExact(rName)), + }, + }, + { + // Test Step 2: config mode + Config: testAccExampleResourceUpdated(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widget), + stateCheckExampleWidgetValuesUpdated(widget, rName), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("active"), knownvalue.Bool(false)), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("name"), knownvalue.StringExact(rName)), + }, + }, + { + // Test Step 3: import mode + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ResourceName: "example_widget.foo", + }, + }, + }) +} +``` +In this example, "Test Step 3" is a very concise import mode test step because it does not need a completely different Terraform configuration. It simply reuses artifacts from its surrounding `TestCase`, in a "convention over configuration" style. As a detailed example: + +* After "Test Step 2" has run, the test case has two useful artifacts: (1) a Terraform configuration and (2) a statefile. These two artifacts act as inputs to "Test Step 3." +* "Test Step 3" uses the Terraform configuration input as a base configuration for verifying import behavior. The test step generates a new Terraform configuration: the base configuration plus a generated `import` block. + * This is a testing convenience; the test author can override this via their choice of `step.Config`, `step.ConfigFile`, or `step.ConfigDirectory` +* "Test Step 3" generates a plan from this Terraform configuration. It uses the statefile input as a "known good" reference for the generated plan. + * In other words, The statefile acts as a "golden file" reference. In import mode, the planned values are expected to match the state values in the "golden file" and the test will report an error if the values do not match. + +[Back to top](#table-of-contents) + +## Expecting errors or non-empty plans + +The number of acceptance tests for a given resource typically start small with +the basic and update scenarios covered. Other tests should be added to +demonstrate common expected configurations or behavior scenarios for a given +resource, such as typical updates or changes to configuration, or exercising +logic that uses polling for updates such as an autoscaling group adding or +draining instances. + +It is possible for scenarios to exist where a valid configuration (no errors +during `plan`) would result in a non-empty `plan` after successfully running +`terraform apply`. This is typically due to a valid but otherwise +misconfiguration of the resource, and is generally undesirable. Occasionally it +is useful to intentionally create this scenario in an early `TestStep` in order +to demonstrate correcting the state with proper configuration in a follow-up +`TestStep`. Normally a `TestStep` that results in a non-empty plan would fail +the test after apply, however developers can use the `ExpectNonEmptyPlan` +attribute to prevent failure and allow the `TestCase` to continue: + +```go +func TestAccExampleWidget_expectPlan(t *testing.T) { + var widget example.Widget + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + // use an incomplete configuration that we expect + // to result in a non-empty plan after apply + Config: testAccExampleResourceIncomplete(rName), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("name"), knownvalue.StringExact(rName)), + }, + ExpectNonEmptyPlan: true, + }, + { + // apply the complete configuration + Config: testAccExampleResourceComplete(rName), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("name"), knownvalue.StringExact(rName)), + }, + }, + }, + }) +} +``` + +In addition to `ExpectNonEmptyPlan`, `TestStep` also exposes an `ExpectError` +hook, allowing developers to test configuration that they expect to produce an +error, such as configuration that fails schema validators: + +```go +func TestAccExampleWidget_expectError(t *testing.T) { + var widget example.Widget + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + // use a configuration that we expect to fail a validator + // on the resource Name attribute, which only allows alphanumeric + // characters + Config: testAccExampleResourceError(rName + "*$%%^"), + // No check function is given because we expect this configuration + // to fail before any infrastructure is created + ExpectError: regexp.MustCompile("Widget names may only contain alphanumeric characters"), + }, + }, + }) +} +``` + +`ExpectError` expects a valid regular expression, and the error message must +match in order to consider the error as expected and allow the test to pass. If +the regular expression does not match, the `TestStep` fails explaining that the +configuration did not produce the error expected. + +[Back to top](#table-of-contents) + +## Regression tests + +As resources are put into use, issues can arise as bugs that need to be fixed +and released in a new version. Developers are encouraged to introduce regression +tests that demonstrate not only any bugs reported, but that code modified to +address any bug is verified as fixing the issues. These regression tests should +be named and documented appropriately to identify the issue(s) they demonstrate +fixes for. When possible the documentation for a regression test should include +a link to the original bug report. + +An ideal bug fix would include at least 2 commits to source control: + +A single commit introducing the regression test, verifying the issue(s) 1 or +more commits that modify code to fix the issue(s) + +This allows other developers to independently verify that a regression test +indeed reproduces the issue by checking out the source at that commit first, and +then advancing the revisions to evaluate the fix. + +[Back to top](#table-of-contents) + +# Conclusion + +Terraform's [Testing Framework][1] allows for powerful, iterative acceptance +tests that enable developers to fully test the behavior of Terraform plugins. By +following the above best practices, developers can ensure their plugin behaves +correctly across the most common use cases and everyday operations users will +have using their plugins, and ensure that Terraform remains a world-class tool +for safely managing infrastructure. + +[1]: /terraform/plugin/testing + +[2]: /terraform/plugin/testing/acceptance-tests/testcase + +[3]: /terraform/plugin/testing/acceptance-tests/testcase#precheck + +[4]: /terraform/plugin/testing/acceptance-tests/testcase#checkdestroy + +[5]: /terraform/plugin/testing/acceptance-tests/teststep + +[6]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep.ExpectNonEmptyPlan + +[7]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep.ExpectError + +[8]: /terraform/plugin/testing/acceptance-tests/state-checks + +[9]: /terraform/plugin/testing/acceptance-tests/state-checks/resource + +[10]: /terraform/plugin/testing/acceptance-tests/state-checks/custom diff --git a/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/unit-testing.mdx b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/unit-testing.mdx new file mode 100644 index 0000000000..c8b8690c11 --- /dev/null +++ b/content/terraform-plugin-testing/v1.14.x/docs/plugin/testing/unit-testing.mdx @@ -0,0 +1,93 @@ +--- +page_title: Plugin Development - Unit Testing +description: |- + Unit tests are commonly used for testing helper methods that expand or + flatten API responses into data structures that Terraform stores as state. +--- + +# Unit Testing + +Testing plugin code in small, isolated units is distinct from Acceptance Tests, +and does not require network connections. Unit tests are commonly used for +testing helper methods that expand or flatten API responses into data structures +for storage into state by Terraform. This section covers the specifics of +writing Unit Tests for Terraform Plugin code. + +The procedure for writing unit tests for Terraform follows the same setup and +conventions of writing any Go unit tests. We recommend naming tests to follow +the same convention as our acceptance tests, `Test_`. For more +information on Go tests, see the [official Golang docs on testing](https://pkg.go.dev/testing). + +Below is an example unit test used in flattening AWS security group rules, +demonstrating a typical `flattener` type method that's commonly used to convert +structures returned from APIs into data structures used by Terraform in saving +to state. This example is truncated for brevity, but you can see the full test in the +[aws/structure_test.go in the Terraform AWS Provider +repository on GitHub](https://github.com/hashicorp/terraform-provider-aws/blob/f22ae122d8407672bd38951f80a2813b8b9af683/aws/structure_test.go#L930-L1027) + +```go +func TestFlattenSecurityGroups(t *testing.T) { + cases := []struct { + ownerId *string + pairs []*ec2.UserIdGroupPair + expected []*GroupIdentifier + }{ + // simple, no user id included + { + ownerId: aws.String("user1234"), + pairs: []*ec2.UserIdGroupPair{ + &ec2.UserIdGroupPair{ + GroupId: aws.String("sg-12345"), + }, + }, + expected: []*GroupIdentifier{ + &GroupIdentifier{ + GroupId: aws.String("sg-12345"), + }, + }, + }, + // include the owner id, but keep it consitent with the same account. Tests + // EC2 classic situation + { + ownerId: aws.String("user1234"), + pairs: []*ec2.UserIdGroupPair{ + &ec2.UserIdGroupPair{ + GroupId: aws.String("sg-12345"), + UserId: aws.String("user1234"), + }, + }, + expected: []*GroupIdentifier{ + &GroupIdentifier{ + GroupId: aws.String("sg-12345"), + }, + }, + }, + + // include the owner id, but from a different account. This is reflects + // EC2 Classic when referring to groups by name + { + ownerId: aws.String("user1234"), + pairs: []*ec2.UserIdGroupPair{ + &ec2.UserIdGroupPair{ + GroupId: aws.String("sg-12345"), + GroupName: aws.String("somegroup"), // GroupName is only included in Classic + UserId: aws.String("user4321"), + }, + }, + expected: []*GroupIdentifier{ + &GroupIdentifier{ + GroupId: aws.String("sg-12345"), + GroupName: aws.String("user4321/somegroup"), + }, + }, + }, + } + + for _, c := range cases { + out := flattenSecurityGroups(c.pairs, c.ownerId) + if !reflect.DeepEqual(out, c.expected) { + t.Fatalf("Error matching output and expected: %#v vs %#v", out, c.expected) + } + } +} +``` diff --git a/content/terraform-plugin-testing/v1.14.x/img/.gitkeep b/content/terraform-plugin-testing/v1.14.x/img/.gitkeep new file mode 100644 index 0000000000..e69de29bb2