This module makes your terratest tests easier with pre-defined stages and other helpful functions.
All functions that init or apply the module allow you to specify an errorFunc which is called when the terraform command fails.
The error and the output of the terraform command are passed to that function so that you can define if the test should succeed or fail.
To ensure correct function of this module, you need to:
- Put your tests in the directory
test. - Put variable files for your tests in
test/variablesand name them as the test is named - Put provider configuration into the
test/provider.tffile
Those functions are the easy start into testing, with more complex scenarios covered below. They are called “run functions” because their name starts with Run.
All run functions destroy the deployed infrastructure afterwards. Run functions abstract stages for you.
To only run some stages, set a SKIP_$stage environment variable to skip a certain stage, e.g. SKIP_destroy. This makes developing and debugging tests much easier.
For examples, see the examples section.
Available run functions are:
RunNoValidate: Run with default options and no extra validationRunValidate: Same asRunNoValidate, but with a validation functionRunOptionsNoValidate: Run with configured terraform options, no extra validationRunOptionsValidate: Same asRunOptionsNoValidate, but with a validation function
In a validation function, you can inspect the deployed infrastructure and have the test fail with assert.Fail if it does not match what you are expecting.
You can use the DefaultOptions function for default terraformOptions to be set automatically. Those are:
TerraformDir, set to the absolute path of the module as the tests are in a subdirectory namedtestsVarFilesis appended the with the.tfvarsfile intest/variablesthat has the same as the running test
Using DefaultOptions ensures that the module directory is copied so that tests can be run in parallel with t.Parallel()
ℹ️ You need stages if you expect e.g. the terraform plan to fail for a test and want to validate the errors that occur, see the examples below.
Available stages are:
setup: Saves theterraformOptions, runsterraform initandterraform plan. You can specify anerrorFunchere.apply: Runsterraform apply. You can specify anerrorFunchere.validate: If you want to check the state of the deployed infrastructure, you can do so in the validate stage. Pass avalidateFuncintoStageValidate.destroy: Runsterraform destroyand calls theCleanupfunction.
Cleanup: Removes the test data directory.test-dataand test provider configurationtest-provider.tf.
func TestDefaults(t *testing.T) {
// If the test needs to run on its own, e.g. because
// of quota limitations, remove this line
t.Parallel()
helpers.RunNoValidate(t)
}with this, the test will run the module with the variables in test/variables/TestDefaults.tfvars and expect all operations to be successful.
When you need to define not only constant variables, but also dynamic ones, you can use RunOptionsNoValidate as follows:
func TestWithVars(t *testing.T) {
// If the test needs to run on its own, e.g. because
// of quota limitations, remove this line
t.Parallel()
helpers.RunOptionsNoValidate(t, &terraform.Options{
Vars: map[string]interface{}{
"name": uuid.New(),
},
})
}This is needed for e.g. tests with S3 buckets to ensure every bucket is named uniquely.
If a test is expected to fail on apply, defer the destroy stage and pass an error function to the apply stage:
func TestS3BucketDefault(t *testing.T) {
t.Parallel()
terraformOptions := defaultOptions(t)
// set everything up for the terraform apply later
terraformOptions.Vars = map[string]interface{}{
"name": uuid.New(),
}
defer helpers.StageDestroy(t, terraformOptions.TerraformDir)
helpers.StageSetup(t, terraformOptions.TerraformDir, terraformOptions)
helpers.StageApply(t, terraformOptions.TerraformDir, func(err error, stdoutStderr string) {
// This error is expected
if err != nil {
if !strings.Contains(stdoutStderr, "Error: Some expected error") {
assert.Fail(t, "We expected an error, but it did not occur. Instead, another error was returned.")
}
}
})
}For a test that is expected to fail on plan, use the Cleanup function.
func TestS3BucketBackupOnVersioningOff(t *testing.T) {
t.Parallel()
terraformOptions := defaultOptions(t)
terraformOptions.NoColor = true
terraformOptions.Vars = map[string]interface{}{
"name": uuid.New(),
"backup": "on",
"versioning": "Disabled",
}
defer helpers.Cleanup(t, terraformOptions.TerraformDir)
// The terraform plan is expected to fail as versioning can't be turned off with backups turned on.
helpers.StageSetup(t, terraformOptions.TerraformDir, terraformOptions, func(err error, stdoutStderr string) {
// This error is expected, the precondition failed
if err != nil {
if !strings.Contains(stdoutStderr, "Error: Resource precondition failed") || !strings.Contains(stdoutStderr, "Versioning cannot be disabled when backups are enabled") {
assert.Fail(t, "The precondition checking versioning and backup configuration did not fail, but there is another error.")
}
} else {
// There must be an error in the precondition
assert.Fail(t, "There are no errors, but the precondition checking versioning and backup configuration must fail.")
}
})
}When cloning this repository, run make init locally. This sets up pre-commit, which takes care of the go formatting.
Versioning follows the go standard: Semantic versioning with a v prefix. Tags are currently created and pushed manually.