Terraform
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. To request additions or updates to this guide, submit issues or pull requests to the terraform-plugin-testing repository.
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:
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:
$ go get github.com/hashicorp/terraform-plugin-testing@latest
Clean up go.mod:
$ 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:
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 and 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:
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:
resource.Test(t, resource.TestCase{
    // ...
    Steps: []resource.TestStep{
        { /* ... at least one prior step ... */ },
        {
            // ...
            // Replacing PlanOnly: true
            ConfigPlanChecks: resource.ConfigPlanChecks{
                PreApply: []plancheck.PlanCheck{
                    plancheck.ExpectEmptyPlan(),
                },
            },
        },
    },
})