Terraform
Testing
During migration, you should write tests to verify that the behaviour of your provider has not been altered by the migration itself. You will also need to update your tests too.
Tip
If muxing, only migrated resources and data sources require immediate updates of testing code.
Testing Migration
During migration, we recommend writing tests to verify that switching from SDKv2 to the Framework has not affected your provider's behavior. These tests use identical configuration. The first test step applies a plan and generates state with the SDKv2 version of the provider. The second test step generates a plan with the Framework version of the provider and verifies that the plan is a no-op, indicating that migrating to the framework has not altered behaviour.
Use the ExternalProviders
field within a resource.TestStep
to specify the configuration of a specific provider to
use during each test step. You can specify a version of the provider built on SDKv2 during the first test step, and
then you can use the version of the provider built on the Framework in subsequent test step(s) to verify that Terraform
CLI does not detect any planned changes.
You must also update the provider factories to use the Framework.
Example
These examples show how you can use external providers to generate state with a previous version of the provider and then verify that there are no planned changes after migrating to the Framework.
- The first
TestStep
usesExternalProviders
to causeterraform apply
to execute with a previous version of the provider, which is built on SDKv2. - The second
TestStep
usesProtoV5ProviderFactories
so that the test uses the provider code contained within the provider repository. The second step usesConfigPlanChecks
to verify that a no-op plan is generated.
Managed Resource
In this example configuration, all managed resource attribute values are compared between the SDK and Framework implementations:
func TestResource_UpgradeFromVersion(t *testing.T) {
/* ... */
resource.Test(t, resource.TestCase{
Steps: []resource.TestStep{
{
ExternalProviders: map[string]resource.ExternalProvider{
"<provider>": {
VersionConstraint: "<sdk_version>",
Source: "hashicorp/<provider>",
},
},
Config: `resource "provider_resource" "example" {
/* ... */
}`,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("provider_resource.example", "<attribute>", "<value>"),
/* ... */
),
},
{
ProtoV5ProviderFactories: protoV5ProviderFactories(),
Config: `resource "provider_resource" "example" {
/* ... */
}`,
// ConfigPlanChecks is a terraform-plugin-testing feature.
// If acceptance testing is still using terraform-plugin-sdk/v2,
// use `PlanOnly: true` instead. When migrating to
// terraform-plugin-testing, switch to `ConfigPlanChecks` or you
// will likely experience test failures.
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectEmptyPlan(),
},
},
},
},
})
}
Data Source
Tip
Prefer individually testing all attribute values of a data source instead of this testing pattern. Testing individual attribute values will catch unexpected data handling changes after migration and prevent any expected introduction of new attributes from causing test failures when using this pattern.
Since data sources are refreshed every Terraform plan and do not use prior state during planning, additional configuration of a separate managed resource or output value is required to track value differences. The terraform_data
managed resource is one option that is implicitly available for configurations in Terraform 1.4 and later. If testing on older versions of Terraform is required, use the null_resource
managed resource with an associated ExternalProviders
step configuration instead.
In this example configuration, all data source attribute values are compared between the SDK and Framework implementations:
func TestDataSource_UpgradeFromVersion(t *testing.T) {
/* ... */
resource.Test(t, resource.TestCase{
Steps: []resource.TestStep{
{
ExternalProviders: map[string]resource.ExternalProvider{
"<provider>": {
VersionConstraint: "<sdk_version>",
Source: "hashicorp/<provider>",
},
},
Config: `data "provider_datasource" "test" {
/* ... */
}
resource "terraform_data" "test" {
input = data.provider_datasource.test
}`,
},
{
ProtoV5ProviderFactories: protoV5ProviderFactories(),
Config: `data "provider_datasource" "test" {
/* ... */
}
resource "terraform_data" "test" {
input = data.provider_datasource.test
}`,
// ConfigPlanChecks is a terraform-plugin-testing feature.
// If acceptance testing is still using terraform-plugin-sdk/v2,
// use `PlanOnly: true` instead. When migrating to
// terraform-plugin-testing, switch to `ConfigPlanChecks` or you
// will likely experience test failures.
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectEmptyPlan(),
},
},
},
},
})
}
Provider Factories
Existing tests should require minimal updates when migrating from SDKv2 to the Framework. The only critical change relates to the provider factories that create the provider during the test case. Refer to Acceptance Tests - Specify Providers in the Framework documentation for details.
We also recommend writing tests that verify that switching from SDKv2 to the Framework has not affected provider behavior. Refer to Testing Migration for details.
SDKv2
In SDKv2, you use the ProviderFactories
field on the resource.TestCase
struct to obtain schema.Provider
.
The following example shows a test written in SDKv2.
resource.UnitTest(t, resource.TestCase{
ProviderFactories: testProviders(),
Framework
In the Framework, use either the ProtoV5ProviderFactories
or ProtoV6ProviderFactories
field on the
resource.TestCase
struct to obtain the provider.Provider
, depending on the
Terraform Plugin Protocol version your provider is using.
The following example shows how you can write a test in the Framework for a Provider that uses protocol version 6.
resource.UnitTest(t, resource.TestCase{
ProtoV6ProviderFactories: protoV6ProviderFactories(),
Example
SDKv2
The following code sample shows how to define provider factories within a test case when using SDKv2.
func TestDataSource_Exmple(t *testing.T) {
/* ... */
resource.UnitTest(t, resource.TestCase{
ProviderFactories: testProviders(),
/* ... */
})
}
The following shows how to generate provider factories when using SDKv2.
func testProviders() map[string]func() (*schema.Provider, error) {
return map[string]func() (*schema.Provider, error){
"example": func() (*schema.Provider, error) { return New(), nil },
}
}
Framework
The following shows how to define provider factories within a test case when using the Framework.
func TestDataSource_Example(t *testing.T) {
/* ... */
resource.UnitTest(t, resource.TestCase{
ProtoV5ProviderFactories: protoV5ProviderFactories(),
/* ... */
})
}
The following shows how to generate provider factories when using
the Framework. The call to New
returns an instance of the provider. Refer to
Provider Definition in this guide for details.
func protoV5ProviderFactories() map[string]func() (tfprotov5.ProviderServer, error) {
return map[string]func() (tfprotov5.ProviderServer, error){
"example": providerserver.NewProtocol5WithError(New()),
}
}