Skip to content

Commit

Permalink
Adding resource.TestCheckResourceAttrWith test utility (#950)
Browse files Browse the repository at this point in the history
* Adding `resource.TestCheckResourceAttrWith` test utility

* Adding a test suite for `resource.TestCheckResourceAttrWith`

* Adding changelog entry

* Adding examples

* Apply suggestions from code review

Co-authored-by: Brian Flad <bflad417@gmail.com>

* Providing `TestCheckResourceAttrWith` with a more comprehensive godoc

Co-authored-by: Brian Flad <bflad417@gmail.com>
  • Loading branch information
Ivan De Marino and bflad committed Apr 28, 2022
1 parent bb44c87 commit 5adf5f1
Show file tree
Hide file tree
Showing 4 changed files with 569 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .changelog/950.txt
@@ -0,0 +1,3 @@
```release-note:feature
helper/resource: New `TestCheckResourceAttrWith` test helper, that simplifies checking of attribute values via custom functions
```
67 changes: 65 additions & 2 deletions helper/resource/testing.go
Expand Up @@ -13,10 +13,11 @@ import (
"time"

"github.com/hashicorp/go-multierror"
testing "github.com/mitchellh/go-testing-interface"
"github.com/mitchellh/go-testing-interface"

"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/internal/addrs"
"github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging"
Expand Down Expand Up @@ -49,7 +50,7 @@ var flagSweepAllowFailures = flag.Bool("sweep-allow-failures", false, "Enable to
var flagSweepRun = flag.String("sweep-run", "", "Comma seperated list of Sweeper Tests to run")
var sweeperFuncs map[string]*Sweeper

// type SweeperFunc is a signature for a function that acts as a sweeper. It
// SweeperFunc is a signature for a function that acts as a sweeper. It
// accepts a string for the region that the sweeper is to be ran in. This
// function must be able to construct a valid client for that region.
type SweeperFunc func(r string) error
Expand Down Expand Up @@ -959,6 +960,68 @@ func testCheckResourceAttr(is *terraform.InstanceState, name string, key string,
return nil
}

// CheckResourceAttrWithFunc is the callback type used to apply a custom checking logic
// when using TestCheckResourceAttrWith and a value is found for the given name and key.
//
// When this function returns an error, TestCheckResourceAttrWith will fail the check.
type CheckResourceAttrWithFunc func(value string) error

// TestCheckResourceAttrWith ensures a value stored in state for the
// given name and key combination, is checked against a custom logic.
// State value checking is only recommended for testing Computed attributes
// and attribute defaults.
//
// For managed resources, the name parameter is combination of the resource
// type, a period (.), and the name label. The name for the below example
// configuration would be "myprovider_thing.example".
//
// resource "myprovider_thing" "example" { ... }
//
// For data sources, the name parameter is a combination of the keyword "data",
// a period (.), the data source type, a period (.), and the name label. The
// name for the below example configuration would be
// "data.myprovider_thing.example".
//
// data "myprovider_thing" "example" { ... }
//
// The key parameter is an attribute path in Terraform CLI 0.11 and earlier
// "flatmap" syntax. Keys start with the attribute name of a top-level
// attribute. Use the following special key syntax to inspect list, map, and
// set attributes:
//
// - .{NUMBER}: List value at index, e.g. .0 to inspect the first element.
// Use the TestCheckTypeSet* and TestMatchTypeSet* functions instead
// for sets.
// - .{KEY}: Map value at key, e.g. .example to inspect the example key
// value.
// - .#: Number of elements in list or set.
// - .%: Number of elements in map.
//
// The checkValueFunc parameter is a CheckResourceAttrWithFunc,
// and it's provided with the attribute value to apply a custom checking logic,
// if it was found in the state. The function must return an error for the
// check to fail, or `nil` to succeed.
func TestCheckResourceAttrWith(name, key string, checkValueFunc CheckResourceAttrWithFunc) TestCheckFunc {
return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error {
is, err := primaryInstanceState(s, name)
if err != nil {
return err
}

err = testCheckResourceAttrSet(is, name, key)
if err != nil {
return err
}

err = checkValueFunc(is.Attributes[key])
if err != nil {
return fmt.Errorf("%s: Attribute %q value: %w", name, key, err)
}

return nil
})
}

// TestCheckNoResourceAttr ensures no value exists in the state for the
// given name and key combination. The opposite of this TestCheckFunc is
// TestCheckResourceAttrSet. State value checking is only recommended for
Expand Down
63 changes: 63 additions & 0 deletions helper/resource/testing_example_test.go
@@ -1,7 +1,9 @@
package resource_test

import (
"fmt"
"regexp"
"strconv"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)
Expand Down Expand Up @@ -209,6 +211,67 @@ func ExampleTestCheckResourceAttr_typeString() {
resource.TestCheckResourceAttr("example_thing.test", "example_string_attribute", "test-value")
}

func ExampleTestCheckResourceAttrWith_typeString() {
// This function is typically implemented in a TestStep type Check field,
// wrapped with ComposeAggregateTestCheckFunc to combine results from
// multiple checks.
//
// Given the following example configuration:
//
// resource "example_thing" "test" {
// example_string_attribute = "Very long string..."
// }
//
// The following TestCheckResourceAttrWith can be written to assert against
// the expected state values.
//
// NOTE: State value checking is only necessary for Computed attributes,
// as the testing framework will automatically return test failures
// for configured attributes that mismatch the saved state, however
// this configuration and test is shown for illustrative purposes.

// Verify the attribute value string length is above 1000
resource.TestCheckResourceAttrWith("example_thing.test", "example_string_attribute", func(value string) error {
if len(value) <= 1000 {
return fmt.Errorf("should be longer than 1000 characters")
}
return nil
})
}

func ExampleTestCheckResourceAttrWith_typeInt() {
// This function is typically implemented in a TestStep type Check field,
// wrapped with ComposeAggregateTestCheckFunc to combine results from
// multiple checks.
//
// Given the following example configuration:
//
// resource "example_thing" "test" {
// example_int_attribute = 10
// }
//
// The following TestCheckResourceAttrWith can be written to assert against
// the expected state values.
//
// NOTE: State value checking is only necessary for Computed attributes,
// as the testing framework will automatically return test failures
// for configured attributes that mismatch the saved state, however
// this configuration and test is shown for illustrative purposes.

// Verify the attribute value is an integer, and it's between 5 (included) and 20 (excluded)
resource.TestCheckResourceAttrWith("example_thing.test", "example_string_attribute", func(value string) error {
valueInt, err := strconv.Atoi(value)
if err != nil {
return err
}

if valueInt < 5 && valueInt >= 20 {
return fmt.Errorf("should be between 5 and 20")
}
return nil
})
}

func ExampleTestCheckResourceAttrPair() {
// This function is typically implemented in a TestStep type Check field,
// wrapped with ComposeAggregateTestCheckFunc to combine results from
Expand Down

0 comments on commit 5adf5f1

Please sign in to comment.