Testing Resources
Testing Resources Overview
Resources require comprehensive testing of Create, Read, Update, Delete operations plus import, upgrades, and edge cases.
Testing Create Operation
Basic Create Test
func TestAccCacheResource_create(t *testing.T) {
rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "cachecloud_cache.test"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckCacheDestroy,
Steps: []resource.TestStep{
{
Config: testAccCacheResourceConfig_basic(rName),
Check: resource.ComposeAggregateTestCheckFunc(
// Verify basic attributes
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttr(resourceName, "engine", "redis"),
resource.TestCheckResourceAttr(resourceName, "engine_version", "7.0"),
resource.TestCheckResourceAttr(resourceName, "node_type", "cache.t3.micro"),
resource.TestCheckResourceAttr(resourceName, "num_nodes", "1"),
// Verify computed attributes are set
resource.TestCheckResourceAttrSet(resourceName, "id"),
resource.TestCheckResourceAttrSet(resourceName, "endpoint"),
resource.TestCheckResourceAttrSet(resourceName, "created_at"),
// Verify status
resource.TestCheckResourceAttr(resourceName, "status", "available"),
// Custom check
testAccCheckCacheExists(resourceName),
),
},
},
})
}
Test with All Attributes
func TestAccCacheResource_complete(t *testing.T) {
rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "cachecloud_cache.test"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckCacheDestroy,
Steps: []resource.TestStep{
{
Config: testAccCacheResourceConfig_complete(rName),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttr(resourceName, "engine", "redis"),
resource.TestCheckResourceAttr(resourceName, "engine_version", "7.0"),
resource.TestCheckResourceAttr(resourceName, "node_type", "cache.t3.micro"),
resource.TestCheckResourceAttr(resourceName, "num_nodes", "2"),
resource.TestCheckResourceAttr(resourceName, "port", "6379"),
resource.TestCheckResourceAttr(resourceName, "parameter_group_name", "default.redis7"),
resource.TestCheckResourceAttr(resourceName, "maintenance_window", "sun:05:00-sun:06:00"),
resource.TestCheckResourceAttr(resourceName, "snapshot_retention_limit", "7"),
// Tags
resource.TestCheckResourceAttr(resourceName, "tags.%", "2"),
resource.TestCheckResourceAttr(resourceName, "tags.Environment", "test"),
resource.TestCheckResourceAttr(resourceName, "tags.Team", "platform"),
),
},
},
})
}
func testAccCacheResourceConfig_complete(rName string) string {
return fmt.Sprintf(`
resource "cachecloud_cache" "test" {
name = %[1]q
engine = "redis"
engine_version = "7.0"
node_type = "cache.t3.micro"
num_nodes = 2
port = 6379
parameter_group_name = "default.redis7"
maintenance_window = "sun:05:00-sun:06:00"
snapshot_retention_limit = 7
tags = {
Environment = "test"
Team = "platform"
}
}
`, rName)
}
Testing Update Operations
In-Place Update
func TestAccCacheResource_updateNumNodes(t *testing.T) {
rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "cachecloud_cache.test"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckCacheDestroy,
Steps: []resource.TestStep{
{
Config: testAccCacheResourceConfig_numNodes(rName, 1),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "num_nodes", "1"),
),
},
{
Config: testAccCacheResourceConfig_numNodes(rName, 3),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "num_nodes", "3"),
),
},
{
Config: testAccCacheResourceConfig_numNodes(rName, 2),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "num_nodes", "2"),
),
},
},
})
}
Force Replacement
func TestAccCacheResource_updateEngine_forceNew(t *testing.T) {
rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "cachecloud_cache.test"
var cache1, cache2 client.Cache
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckCacheDestroy,
Steps: []resource.TestStep{
{
Config: testAccCacheResourceConfig_engine(rName, "redis"),
Check: resource.ComposeTestCheckFunc(
testAccCheckCacheExists(resourceName, &cache1),
resource.TestCheckResourceAttr(resourceName, "engine", "redis"),
),
},
{
Config: testAccCacheResourceConfig_engine(rName, "memcached"),
Check: resource.ComposeTestCheckFunc(
testAccCheckCacheExists(resourceName, &cache2),
resource.TestCheckResourceAttr(resourceName, "engine", "memcached"),
testAccCheckCacheRecreated(&cache1, &cache2),
),
},
},
})
}
func testAccCheckCacheRecreated(before, after *client.Cache) resource.TestCheckFunc {
return func(s *terraform.State) error {
if before.ID == after.ID {
return fmt.Errorf("Expected cache to be recreated, but ID unchanged: %s", before.ID)
}
return nil
}
}
Update Tags
func TestAccCacheResource_updateTags(t *testing.T) {
rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "cachecloud_cache.test"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckCacheDestroy,
Steps: []resource.TestStep{
{
Config: testAccCacheResourceConfig_tags(rName, map[string]string{
"Environment": "test",
}),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
resource.TestCheckResourceAttr(resourceName, "tags.Environment", "test"),
),
},
{
Config: testAccCacheResourceConfig_tags(rName, map[string]string{
"Environment": "production",
"Team": "platform",
}),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "tags.%", "2"),
resource.TestCheckResourceAttr(resourceName, "tags.Environment", "production"),
resource.TestCheckResourceAttr(resourceName, "tags.Team", "platform"),
),
},
{
Config: testAccCacheResourceConfig_basic(rName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
),
},
},
})
}
Testing Delete Operation
Standard Delete
func testAccCheckCacheDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*client.Client)
for _, rs := range s.RootModule().Resources {
if rs.Type != "cachecloud_cache" {
continue
}
_, err := conn.GetCache(context.Background(), rs.Primary.ID)
if err == nil {
return fmt.Errorf("Cache %s still exists", rs.Primary.ID)
}
// Verify it's actually not found
if !client.IsNotFoundError(err) {
return fmt.Errorf("Unexpected error checking cache deletion: %w", err)
}
}
return nil
}
Testing Import
Simple Import
func TestAccCacheResource_import(t *testing.T) {
rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "cachecloud_cache.test"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckCacheDestroy,
Steps: []resource.TestStep{
{
Config: testAccCacheResourceConfig_basic(rName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "name", rName),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
Import with Ignore Fields
func TestAccCacheResource_importWithPassword(t *testing.T) {
rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "cachecloud_cache.test"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckCacheDestroy,
Steps: []resource.TestStep{
{
Config: testAccCacheResourceConfig_withPassword(rName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttrSet(resourceName, "password"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
// Password can't be imported from API
ImportStateVerifyIgnore: []string{"password"},
},
},
})
}
Composite ID Import
func TestAccCacheResource_importCompositeID(t *testing.T) {
rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "cachecloud_cache.test"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckCacheDestroy,
Steps: []resource.TestStep{
{
Config: testAccCacheResourceConfig_basic(rName),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateIdFunc: testAccCacheImportStateIdFunc(resourceName),
},
},
})
}
func testAccCacheImportStateIdFunc(resourceName string) resource.ImportStateIdFunc {
return func(s *terraform.State) (string, error) {
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return "", fmt.Errorf("Not found: %s", resourceName)
}
// Return composite ID: region/cache-id
return fmt.Sprintf("us-east-1/%s", rs.Primary.ID), nil
}
}
Testing Computed Attributes
Verify Computed Values
func TestAccCacheResource_computedEndpoint(t *testing.T) {
rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "cachecloud_cache.test"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckCacheDestroy,
Steps: []resource.TestStep{
{
Config: testAccCacheResourceConfig_basic(rName),
Check: resource.ComposeTestCheckFunc(
// Endpoint should be computed
resource.TestCheckResourceAttrSet(resourceName, "endpoint"),
resource.TestMatchResourceAttr(resourceName, "endpoint",
regexp.MustCompile(`^[a-z0-9\-]+\.cache\.example\.com:\d+$`)),
// Status should be computed
resource.TestCheckResourceAttrSet(resourceName, "status"),
resource.TestCheckResourceAttr(resourceName, "status", "available"),
// Timestamps should be computed
resource.TestCheckResourceAttrSet(resourceName, "created_at"),
resource.TestCheckResourceAttrSet(resourceName, "updated_at"),
),
},
},
})
}
Testing Error Cases
Invalid Configuration
func TestAccCacheResource_invalidEngine(t *testing.T) {
rName := acctest.RandomWithPrefix("tf-acc-test")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccCacheResourceConfig_invalidEngine(rName),
ExpectError: regexp.MustCompile(`engine must be either 'redis' or 'memcached'`),
},
},
})
}
func TestAccCacheResource_invalidPortRange(t *testing.T) {
rName := acctest.RandomWithPrefix("tf-acc-test")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccCacheResourceConfig_port(rName, 80),
ExpectError: regexp.MustCompile(`port must be between 1024 and 65535`),
},
},
})
}
API Errors
func TestAccCacheResource_nameConflict(t *testing.T) {
rName := acctest.RandomWithPrefix("tf-acc-test")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckCacheDestroy,
Steps: []resource.TestStep{
{
Config: testAccCacheResourceConfig_duplicate(rName),
ExpectError: regexp.MustCompile(`cache with name .+ already exists`),
},
},
})
}
func testAccCacheResourceConfig_duplicate(rName string) string {
return fmt.Sprintf(`
resource "cachecloud_cache" "first" {
name = %[1]q
engine = "redis"
node_type = "cache.t3.micro"
num_nodes = 1
}
resource "cachecloud_cache" "second" {
name = %[1]q # Same name - should error
engine = "redis"
node_type = "cache.t3.micro"
num_nodes = 1
depends_on = [cachecloud_cache.first]
}
`, rName)
}
Testing with Dependencies
Resource Depends On
func TestAccCacheResource_withVPC(t *testing.T) {
rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "cachecloud_cache.test"
vpcResourceName := "cachecloud_vpc.test"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckCacheDestroy,
Steps: []resource.TestStep{
{
Config: testAccCacheResourceConfig_vpc(rName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttrPair(
resourceName, "vpc_id",
vpcResourceName, "id",
),
),
},
},
})
}
func testAccCacheResourceConfig_vpc(rName string) string {
return fmt.Sprintf(`
resource "cachecloud_vpc" "test" {
name = "%[1]s-vpc"
cidr = "10.0.0.0/16"
}
resource "cachecloud_subnet" "test" {
vpc_id = cachecloud_vpc.test.id
cidr = "10.0.1.0/24"
}
resource "cachecloud_cache" "test" {
name = %[1]q
engine = "redis"
node_type = "cache.t3.micro"
num_nodes = 1
vpc_id = cachecloud_vpc.test.id
subnet_id = cachecloud_subnet.test.id
}
`, rName)
}
Best Practices
1. Test All CRUD Operations
// ✅ Good: Complete lifecycle
func TestAccCacheResource_lifecycle(t *testing.T) {
Steps: []resource.TestStep{
{Config: createConfig, Check: createChecks}, // Create
{Config: updateConfig, Check: updateChecks}, // Update
{ImportState: true}, // Import
// Delete tested via CheckDestroy
}
}
2. Use Random Names
// ✅ Good: Unique names prevent conflicts
rName := acctest.RandomWithPrefix("tf-acc-test")
// ❌ Bad: Fixed names can conflict
name := "test-cache"
3. Verify CheckDestroy
// ✅ Good: Always include CheckDestroy
resource.Test(t, resource.TestCase{
CheckDestroy: testAccCheckCacheDestroy,
Steps: []resource.TestStep{...},
})
Key Takeaways
- ✅ Test complete CRUD lifecycle
- ✅ Verify all attributes (required, optional, computed)
- ✅ Test updates (in-place and force-new)
- ✅ Always test import functionality
- ✅ Verify computed attributes are set
- ✅ Test with resource dependencies
- ✅ Check error scenarios
- ✅ Use CheckDestroy for cleanup verification
- ✅ Generate unique resource names
- ✅ Test tags and metadata
What’s Next?
Learn how to test data sources with queries and filters.
Previous: ← Acceptance Testing Framework
Last updated: July 3, 2026