provider-test-patterns
Acerca de
Esta habilidad proporciona a los desarrolladores de proveedores de Terraform patrones y orientación para escribir pruebas de aceptación utilizando el framework terraform-plugin-testing. Cubre la estructura de las pruebas, componentes clave de prueba como verificaciones de estado y verificaciones de plan, y escenarios comunes de prueba, incluyendo la verificación de importación y pruebas de recursos efímeros. Úsela al escribir, revisar o depurar pruebas de aceptación del proveedor para implementar las mejores prácticas.
Instalación rápida
Claude Code
Recomendadonpx skills add hashicorp/agent-skills -a claude-code/plugin add https://github.com/hashicorp/agent-skillsgit clone https://github.com/hashicorp/agent-skills.git ~/.claude/skills/provider-test-patternsCopia y pega este comando en Claude Code para instalar esta habilidad
Documentación
Provider Acceptance Test Patterns
Patterns for writing acceptance tests using terraform-plugin-testing with the Plugin Framework.
Source: HashiCorp Testing Patterns
References (load when needed):
references/checks.md— statecheck, plancheck, knownvalue types, tfjsonpath, comparersreferences/sweepers.md— sweeper setup, TestMain, dependenciesreferences/ephemeral.md— ephemeral resource testing, echoprovider, multi-step patterns
Test Lifecycle
The framework runs each TestStep through: plan → apply → refresh → final
plan. If the final plan shows a diff, the test fails (unless
ExpectNonEmptyPlan is set). After all steps, destroy runs followed by
CheckDestroy. This means every test automatically verifies that
configurations apply cleanly and produce no drift — no assertions needed for
that.
Test Function Structure
func TestAccExample_basic(t *testing.T) {
var widget example.Widget
rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
resourceName := "example_widget.test"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckExampleDestroy,
Steps: []resource.TestStep{
{
Config: testAccExampleConfig_basic(rName),
ConfigStateChecks: []statecheck.StateCheck{
stateCheckExampleExists(resourceName, &widget),
statecheck.ExpectKnownValue(resourceName,
tfjsonpath.New("name"), knownvalue.StringExact(rName)),
statecheck.ExpectKnownValue(resourceName,
tfjsonpath.New("id"), knownvalue.NotNull()),
},
},
},
})
}
Use resource.ParallelTest by default. Use resource.Test only when tests
share state or cannot run concurrently.
Provider Factory
// provider_test.go — Plugin Framework with Protocol 6 (use Protocol5 variant if needed)
var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){
"example": providerserver.NewProtocol6WithError(New("test")()),
}
TestCase Fields
| Field | Purpose |
|---|---|
PreCheck | func() — verify prerequisites (env vars, API access) |
ProtoV6ProviderFactories | Plugin Framework provider factories |
CheckDestroy | TestCheckFunc — verify resources destroyed after all steps |
Steps | []TestStep — sequential test operations |
TerraformVersionChecks | []tfversion.TerraformVersionCheck — gate by CLI version |
TestStep Fields
Config Mode
| Field | Purpose |
|---|---|
Config | Inline HCL string to apply |
ConfigStateChecks | []statecheck.StateCheck — modern assertions (preferred) |
ConfigPlanChecks | resource.ConfigPlanChecks{PreApply: []plancheck.PlanCheck{...}} |
ExpectError | *regexp.Regexp — expect failure matching pattern |
ExpectNonEmptyPlan | bool — expect non-empty plan after apply |
PlanOnly | bool — plan without applying |
Destroy | bool — run destroy step |
PreConfig | func() — setup before step |
Import Mode
| Field | Purpose |
|---|---|
ImportState | true to enable import mode |
ImportStateVerify | Verify imported state matches prior state |
ImportStateVerifyIgnore | []string — attributes to skip during verify |
ImportStateKind | resource.ImportBlockWithID — import block generation |
ResourceName | Resource address to import |
ImportStateId | Override the ID used for import |
Check Functions
Modern: ConfigStateChecks (preferred)
Type-safe with aggregated error reporting. Compose built-in checks with custom
statecheck.StateCheck implementations. See references/checks.md for full
knownvalue types, tfjsonpath navigation, and comparers.
ConfigStateChecks: []statecheck.StateCheck{
stateCheckExampleExists(resourceName, &widget),
statecheck.ExpectKnownValue(resourceName,
tfjsonpath.New("name"), knownvalue.StringExact("my-widget")),
statecheck.ExpectKnownValue(resourceName,
tfjsonpath.New("enabled"), knownvalue.Bool(true)),
statecheck.ExpectKnownValue(resourceName,
tfjsonpath.New("id"), knownvalue.NotNull()),
statecheck.ExpectSensitiveValue(resourceName,
tfjsonpath.New("api_key")),
},
Do not mix Check (legacy) and ConfigStateChecks in the same step.
Legacy: Check (for CheckDestroy and migration)
CheckDestroy on TestCase requires TestCheckFunc. The Check field on
TestStep also accepts TestCheckFunc but prefer ConfigStateChecks for new
tests.
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(name, "key", "expected"),
resource.TestCheckResourceAttrSet(name, "id"),
resource.TestCheckNoResourceAttr(name, "removed"),
resource.TestMatchResourceAttr(name, "url", regexp.MustCompile(`^https://`)),
resource.TestCheckResourceAttrPair(res1, "ref_id", res2, "id"),
),
ComposeAggregateTestCheckFunc reports all errors; ComposeTestCheckFunc
fails fast on the first.
Config Helpers
Use numbered format verbs — %[1]q for quoted strings, %[1]s for raw:
func testAccExampleConfig_basic(rName string) string {
return fmt.Sprintf(`
resource "example_widget" "test" {
name = %[1]q
}
`, rName)
}
func testAccExampleConfig_full(rName, description string) string {
return fmt.Sprintf(`
resource "example_widget" "test" {
name = %[1]q
description = %[2]q
enabled = true
}
`, rName, description)
}
Scenario Patterns
Basic + Update (combine in one test — updates are supersets of basic)
Steps: []resource.TestStep{
{
Config: testAccExampleConfig_basic(rName),
ConfigStateChecks: []statecheck.StateCheck{
stateCheckExampleExists(resourceName, &widget),
statecheck.ExpectKnownValue(resourceName,
tfjsonpath.New("name"), knownvalue.StringExact(rName)),
},
},
{
Config: testAccExampleConfig_full(rName, "updated"),
ConfigStateChecks: []statecheck.StateCheck{
stateCheckExampleExists(resourceName, &widget),
statecheck.ExpectKnownValue(resourceName,
tfjsonpath.New("description"), knownvalue.StringExact("updated")),
},
},
},
Import
After a config step, verify import produces identical state. Use
ImportStateKind for import block generation:
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateKind: resource.ImportBlockWithID,
},
Disappears (resource deleted externally)
{
Config: testAccExampleConfig_basic(rName),
ConfigStateChecks: []statecheck.StateCheck{
stateCheckExampleExists(resourceName, &widget),
stateCheckExampleDisappears(resourceName),
},
ExpectNonEmptyPlan: true,
},
Validation (expect error)
{
Config: testAccExampleConfig_invalidName(""),
ExpectError: regexp.MustCompile(`name must not be empty`),
},
Regression (two-commit workflow)
A proper bug fix uses at least two commits: first commit the regression test (which fails, confirming the bug), then commit the fix (test passes). This lets reviewers independently verify the test reproduces the issue by checking out the first commit, then advancing to the fix.
Name and document regression tests to identify the issue they fix. Include a link to the original bug report when possible.
// TestAccExample_regressionGH1234 verifies fix for https://github.com/org/repo/issues/1234
func TestAccExample_regressionGH1234(t *testing.T) {
rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
resourceName := "example_widget.test"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckExampleDestroy,
Steps: []resource.TestStep{
{
// Reproduce the issue: this config triggered the bug
Config: testAccExampleConfig_regressionGH1234(rName),
ConfigStateChecks: []statecheck.StateCheck{
stateCheckExampleExists(resourceName, nil),
statecheck.ExpectKnownValue(resourceName,
tfjsonpath.New("computed_field"), knownvalue.NotNull()),
},
},
},
})
}
Helper Functions
Custom StateCheck: Exists
Implement statecheck.StateCheck for API existence verification. Separate the
exists check into its own function for reuse across steps — the source
recommends this as a design principle:
type exampleExistsCheck struct {
resourceAddress string
widget *example.Widget
}
func (e exampleExistsCheck) CheckState(ctx context.Context, req statecheck.CheckStateRequest, resp *statecheck.CheckStateResponse) {
r, err := stateResourceAtAddress(req.State, e.resourceAddress)
if err != nil {
resp.Error = err
return
}
id, ok := r.AttributeValues["id"].(string)
if !ok {
resp.Error = fmt.Errorf("no id found for %s", e.resourceAddress)
return
}
conn := testAccAPIClient()
widget, err := conn.GetWidget(id)
if err != nil {
resp.Error = fmt.Errorf("%s not found via API: %w", e.resourceAddress, err)
return
}
if e.widget != nil {
*e.widget = *widget
}
}
func stateCheckExampleExists(name string, widget *example.Widget) statecheck.StateCheck {
return exampleExistsCheck{resourceAddress: name, widget: widget}
}
Custom StateCheck: Disappears
Delete a resource via API to simulate external deletion:
type exampleDisappearsCheck struct {
resourceAddress string
}
func (e exampleDisappearsCheck) CheckState(ctx context.Context, req statecheck.CheckStateRequest, resp *statecheck.CheckStateResponse) {
r, err := stateResourceAtAddress(req.State, e.resourceAddress)
if err != nil {
resp.Error = err
return
}
id := r.AttributeValues["id"].(string)
conn := testAccAPIClient()
resp.Error = conn.DeleteWidget(id)
}
func stateCheckExampleDisappears(name string) statecheck.StateCheck {
return exampleDisappearsCheck{resourceAddress: name}
}
State Resource Lookup (shared utility)
func stateResourceAtAddress(state *tfjson.State, address string) (*tfjson.StateResource, error) {
if state == nil || state.Values == nil || state.Values.RootModule == nil {
return nil, fmt.Errorf("no state available")
}
for _, r := range state.Values.RootModule.Resources {
if r.Address == address {
return r, nil
}
}
return nil, fmt.Errorf("not found in state: %s", address)
}
Destroy Check (TestCheckFunc — required by CheckDestroy)
func testAccCheckExampleDestroy(s *terraform.State) error {
conn := testAccAPIClient()
for _, rs := range s.RootModule().Resources {
if rs.Type != "example_widget" {
continue
}
_, err := conn.GetWidget(rs.Primary.ID)
if err == nil {
return fmt.Errorf("widget %s still exists", rs.Primary.ID)
}
if !isNotFoundError(err) {
return err
}
}
return nil
}
PreCheck
func testAccPreCheck(t *testing.T) {
t.Helper()
if os.Getenv("EXAMPLE_API_KEY") == "" {
t.Fatal("EXAMPLE_API_KEY must be set for acceptance tests")
}
}
Repositorio GitHub
Habilidades relacionadas
evaluating-llms-harness
PruebasEsta Skill de Claude ejecuta el benchmark lm-evaluation-harness para evaluar modelos de lenguaje en más de 60 tareas académicas estandarizadas como MMLU y GSM8K. Está diseñada para que los desarrolladores comparen la calidad de los modelos, realicen seguimiento del progreso del entrenamiento o reporten resultados académicos. La herramienta admite varios backends, incluidos modelos de HuggingFace y vLLM.
cloudflare-cron-triggers
PruebasEsta habilidad proporciona conocimiento integral para implementar Cron Triggers de Cloudflare y programar Workers mediante expresiones cron. Cubre la configuración de tareas periódicas, trabajos de mantenimiento y flujos de trabajo automatizados, manejando problemas comunes como expresiones cron inválidas y inconvenientes de zonas horarias. Los desarrolladores pueden utilizarla para configurar manejadores programados, probar activadores cron e integrar con Workflows y Green Compute.
webapp-testing
PruebasEsta habilidad de Claude proporciona un kit de herramientas basado en Playwright para probar aplicaciones web locales mediante scripts de Python. Permite verificación de frontend, depuración de interfaz de usuario, captura de pantallas y visualización de registros, mientras gestiona los ciclos de vida del servidor. Úsela para tareas de automatización de navegadores, pero ejecute los scripts directamente en lugar de leer su código fuente para evitar contaminación del contexto.
finishing-a-development-branch
PruebasEsta habilidad ayuda a los desarrolladores a completar el trabajo terminado verificando que las pruebas pasen y luego presentando opciones estructuradas de integración. Guía el flujo de trabajo para fusionar, crear PRs o limpiar ramas después de que se completa la implementación. Úsala cuando tu código esté listo y probado para finalizar sistemáticamente el proceso de desarrollo.
