Component Testing
This documentation will guide you through our comprehensive strategy for testing Terraform components, provide step-by-step instructions and practical examples to help you validate your component configurations effectively. Whether you're setting up initial tests, adding dependencies, or verifying output assertions, you'll find the resources you need to ensure robust and reliable deployments.
Context
Our component testing strategy is a direct outcome of our migration to a dedicated GitHub Organization for components. This separation allows each component to live in its own repository, enabling independent versioning and testing. It not only improves the reliability of each component but also empowers the community to contribute via pull requests confidently. With integrated testing for every PR, we can ensure high quality and build trust in each contribution.
For more information on building and maintaining components, please refer to our Component Development Guide, which provides detailed insights into best practices, design principles, and the overall process of component development.
Prerequisites
-
Install Terraform / Tofu
-
Install Atmos
- Atmos is a tool for managing Terraform environments.
-
Install Golang
- Go is a programming language that you'll need to run the tests.
- Download and install Go from the official Go website.
- Make sure to set up your Go environment correctly by following the Getting Started with Go guide.
-
Authenticate on AWS
- Ensure you have the necessary AWS credentials configured on your machine. You can do this by setting up the AWS CLI and running
aws configure
, where you'll input your AWS Access Key, Secret Key, region, and output format. - Refer to the AWS CLI documentation for more details.
- Ensure you have the necessary AWS credentials configured on your machine. You can do this by setting up the AWS CLI and running
Test Framework
Component testing framework assumes that each component's repo structure follows the convention when all component terraform source code
would be stored in src
directory and everything related to tests will be placed in test
directory.
Tests consists of two coupled parts - atmos configuration fixtures and tests written on Go code.
Repo structure should be simular to this one:
component-root/
├── src/ # Component source directory
│ └── main.tf
└── test/ # Tests directory
├── fixtures/ # Atmos configurations
├── component_test.go # Tests
├── go.mod
└── go.sum
Atmos configuration fixtures
Atmos configuration fixtures provides minimal settings to deploy the component and it's dependencies on test account during test run.
The difference with a regular atmos configuration are:
- All components deployed on one stack
default-test
in oneus-east-2
region. - Use single aws account for all test resources. If component assumes the cross region or cross account interaction, the configuration still deploys it to the same actual aws account.
- Mock
account-map
component to skip role assuming and always use current AWS credentials provided with environment variables - Configure teraform state files storage to local directory at a path provided by test framework with environment variable
COMPONENT_HELPER_STATE_DIR
This configuration is common for all components and could be copied from template repo.
Fixtures directory structure looks like
fixtures/
├── stacks/
| ├── catalog/
| | ├── usecase/
| | | ├── basic.yaml
| | | └── disabled.yaml
| | └── account-map.yaml
│ └── orgs/default/test/
| ├── _defaults.yaml
| └── tests.yaml
├── atmos.yaml
└── vendor.yaml
For most components, avoid any changes to these files
atmos.yaml
- shared atmos config common for all test casesstacks/catalog/account-map.yaml
- Mockaccount-map
configuration makes any environment/stack/tenant to be backed with the single AWS test accountstacks/orgs/default/test/_defaults.yaml
- Configure terraform state backend to local directory and define shared variables fordefault-test
This files and directories contains custom configurations specific for a testing component:
vendor.yaml
- Vendor configuration for all component dependenciesstacks/catalog/
- Store all dependencies configuration files in the dirstacks/catalog/usecases
- Store configuration of the testing component's use casesstacks/catalog/usecases/basic.yaml
- Predefined file for basic configuration of the testing component's use casestacks/catalog/usecases/disabled.yaml
- Predefined file for thedisabled
configuration use case (when variableenabled: false
)stacks/orgs/default/test/tests.yaml
- Include all dependencies and use cases configurations to deploy them fordefault-test
stack
Tests (Golang)
Component tests are written on go lang as this general purpose language is standard defacto for cloud compute engineering Under the hood tests uses several libraries with helper functions
github.com/cloudposse/test-helpers/atmos/component-helper
- Component testing framework providesgithub.com/cloudposse/test-helpers/atmos
- Atmos APIgithub.com/cloudposse/test-helpers/aws
- Test helpers interact with AWSgithub.com/cloudposse/terratest/aws
- Test helpers provided by GruntWorkgithub.com/aws/aws-sdk-go-v2
- AWS API
You can specify any additional dependency libraries by running go get {library name}
.
Test framework extends github.com/stretchr/testify/suite
to organize test suites.
Regular test file structure follow this example:
package test
import (
"context"
"testing"
"fmt"
"strings"
helper "github.com/cloudposse/test-helpers/pkg/atmos/component-helper"
awsHelper "github.com/cloudposse/test-helpers/pkg/aws"
"github.com/cloudposse/test-helpers/pkg/atmos"
"github.com/gruntwork-io/terratest/modules/aws"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type ComponentSuite struct {
helper.TestSuite
}
// Functions Test prefix are entrypoint for `go test`
func TestRunSuite(t *testing.T) {
# Define test suite instance
suite := new(ComponentSuite)
// Add dependency to the dependencies queue
suite.AddDependency(t, "vpc", "default-test", nil)
// Run test suite
helper.Run(t, suite)
}
// Test suite methods prefixed with `Test` are tests
// Test basic usecase
func (s *ComponentSuite) TestBasic() {
const component = "example/basic"
const stack = "default-test"
const awsRegion = "us-east-2"
// Destroy test component
defer s.DestroyAtmosComponent(s.T(), component, stack, nil)
// Deploy test component
options, _ := s.DeployAtmosComponent(s.T(), component, stack, nil)
assert.NotNil(s.T(), options)
// Get test component output
id := atmos.Output(s.T(), options, "eks_cluster_id")
assert.True(s.T(), strings.HasPrefix(id, "eg-default-ue2-test-"))
// Test component drift
s.DriftTest(component, stack, nil)
}
// Test disabled use case
func (s *ComponentSuite) TestEnabledFlag() {
const component = "example/disabled"
const stack = "default-test"
// Verify no resources created when `enabled: false`
s.VerifyEnabledFlag(component, stack, nil)
}
CLI Flags Cheat Sheet
A test suite run consists of the following phases all of which can be controlled by passing flags:
Phase | Description | Skip flag |
---|---|---|
Setup | Setup test suite and deploy dependencies | --skip-setup |
Test | Deploy the component | --only-deploy-dependencies |
Teardown | Destroy all dependencies | --skip-teardown |
This is possible to enable/disable steps on each phase more precisly
Phase | Description | Skip flag |
---|---|---|
Setup | Vendor dependencies | --skip-vendor |
Setup | Deploy component dependencies | --skip-deploy-dependencies |
Test | Deploy the component | --skip-deploy-component |
Test | Perform assertions | |
Test | Destroy the deployed component (on defer) | --skip-destroy-component |
Teardown | Destroy all dependencies | --skip-destroy-dependencies |
Here is the usefull combination of flags.
Command | Description |
---|---|
go test -timeout 1h --only-deploy-dependencies --skip-destroy-dependencies | Deploy dependencies only. |
go test -timeout 1h --skip-deploy-dependencies --skip-destroy-dependencies --skip-destroy-component | Deploy testing component. Use previously deployed dependencies. Do not destroy anything. Useful when you are working on deploying use case |
go test -timeout 1h --skip-deploy-dependencies --skip-destroy-dependencies --skip-deploy-component --skip-destroy-component | Do not deploy or destroy anything. Useful when you are working on tests asserts |
go test -timeout 1h --skip-deploy-dependencies --skip-deploy-component | Destroy component and its dependencies. Useful when your tests are done to clean up all resources |
Read more about the test helpers framework
Write Tests
Writing tests for your Terraform components is essential for building trust in the component's reliability and enabling safe acceptance of community contributions. By implementing comprehensive tests, we can confidently review and merge pull requests while ensuring the component continues to function as expected.
1 Copy the test scaffold files
If you missed the test scaffold files, copy the contents from this GitHub repository into your component repository. This will provide you with the necessary structure and example tests to get started. The repo structure should looks like the following:
├── src/
│ └── main.tf
└── test/
├── fixtures/
│ ├── stacks/
│ | ├── catalog/
│ | | ├── usecase/
│ | | | ├── basic.yaml
│ | | | └── disabled.yaml
│ | | └── account-map.yaml
│ │ └── orgs/default/test/
│ | ├── _defaults.yaml
│ | └── tests.yaml
│ ├── atmos.yaml
│ └── vendor.yaml
├── component_test.go
├── go.mod
└── go.sum
2 Run Initial Tests
Navigate to the test
directory and run tests in your terminal by running
- Command
- Output
cd test
go test -v -timeout 1h --only-deploy-dependencies
➜ test git:(main) go test -v -timeout 1h --only-deploy-dependencies
=== RUN TestRunSuite
2025/03/07 14:13:34 INFO TestRunSuite: setup → started
2025/03/07 14:13:34 INFO TestRunSuite: tests will be run in temp directory path=/var/folders/1l/hcm6nfms6g58mdrpwcxklsvh0000gn/T/atmos-test-helper3047340212
2025/03/07 14:13:34 INFO TestRunSuite: terraform state for tests will be saved in state directory path=/var/folders/1l/hcm6nfms6g58mdrpwcxklsvh0000gn/T/atmos-test-helper3047340212/state
2025/03/07 14:13:34 INFO TestRunSuite: setup/bootstrap temp dir → completed
2025/03/07 14:13:34 INFO TestRunSuite: setup/copy component to temp dir → started
2025/03/07 14:13:34 INFO TestRunSuite: setup/copy component to temp dir → completed
2025/03/07 14:13:34 INFO TestRunSuite: vendor dependencies → started
TestRunSuite 2025-03-07T14:13:35+01:00 retry.go:91: atmos [vendor pull]
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Running command atmos with args [vendor pull]
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Processing vendor config file 'vendor.yaml'
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Pulling sources for the component 'account-map' from 'github.com/cloudposse/terraform-aws-components.git//modules/account-map?ref=1.520.0' into 'components/terraform/account-map'
2025/03/07 14:13:42 INFO TestRunSuite: vendor dependencies → completed
2025/03/07 14:13:42 INFO TestRunSuite: deploy dependencies → started
2025/03/07 14:13:42 INFO no dependencies to deploy
2025/03/07 14:13:42 INFO TestRunSuite: deploy dependencies → completed
2025/03/07 14:13:42 INFO TestRunSuite: setup → completed
2025/03/07 14:13:42 WARN TestRunSuite: teardown → skipped
--- PASS: TestRunSuite (8.28s)
PASS
ok test 9.142s
3 Add Dependencies
Identify any additional dependencies your component require. Skip this step if the component doesn't have any dependencies.
-
Add dependency to the vendor file
test/fixtures/vendor.yamlapiVersion: atmos/v1
kind: AtmosVendorConfig
metadata:
name: fixtures
description: Atmos vendoring manifest
spec:
sources:
- component: "account-map"
source: github.com/cloudposse/terraform-aws-components.git//modules/account-map?ref={{.Version}}
version: 1.520.0
targets:
- "components/terraform/account-map"
included_paths:
- "**/*.tf"
- "**/*.md"
- "**/*.tftmpl"
- "**/modules/**"
excluded_paths: []
# Example of a dependency from vpc component
- component: "vpc"
source: github.com/cloudposse-terraform-components/aws-vpc.git//src?ref={{.Version}}
version: v1.536.0
# Specify the path to the component directory
targets:
- "components/terraform/vpc"
included_paths:
- "**/*.tf"
- "**/*.md"
- "**/*.tftmpl"
- "**/modules/**"
excluded_paths: []
# Example of a dependency from vpc component -
Add atmos component configurations
test/fixtures/stacks/catalog/vpc.yamlcomponents:
terraform:
vpc:
metadata:
component: vpc
vars:
name: "vpc"
availability_zones:
- "b"
- "c"
public_subnets_enabled: true
max_nats: 1
# Private subnets do not need internet access
nat_gateway_enabled: false
nat_instance_enabled: false
subnet_type_tag_key: "eg.cptest.co/subnet/type"
max_subnet_count: 3
vpc_flow_logs_enabled: false
ipv4_primary_cidr_block: "172.16.0.0/16" -
Import the dependent component for
default-test
stacktest/fixtures/stacks/orgs/default/test/tests.yamlimport:
- orgs/default/test/_defaults
# Import the dependent component
- catalog/vpc -
Add the dependent component to test suite with Go code
- By default, the test suite will add a unique random value to the
attributes
terraform variable. - This is to avoid resource naming collisions with other tests that are using the same component.
- But in some cases, you may need to pass unique value to specific input for the component.
Check out the advanced example for the most common use-case with the
dns-delegated
domain name.- Basic
- Advanced
test/component_test.gopackage test
import (
"testing"
helper "github.com/cloudposse/test-helpers/pkg/atmos/component-helper"
)
type ComponentSuite struct {
helper.TestSuite
}
func (s *ComponentSuite) TestBasic() {
// Add empty test
// Suite setup would not be executed without at least one test
}
func TestRunSuite(t *testing.T) {
suite := new(ComponentSuite)
// Deploy the dependent vpc component
suite.AddDependency(t, "vpc", "default-test", nil)
helper.Run(t, suite)
}test/component_test.gopackage test
import (
"testing"
helper "github.com/cloudposse/test-helpers/pkg/atmos/component-helper"
)
type ComponentSuite struct {
helper.TestSuite
}
func TestRunSuite(t *testing.T) {
suite := new(ComponentSuite)
subdomain := strings.ToLower(random.UniqueId())
inputs := map[string]interface{}{
"zone_config": []map[string]interface{}{
{
"subdomain": subdomain,
"zone_name": "components.cptest.test-automation.app",
},
},
}
suite.AddDependency(t, "dns-delegated", "default-test", &inputs)
helper.Run(t, suite)
} - By default, the test suite will add a unique random value to the
-
Deploy dependencies
- Command
- Output
go test -v -timeout 1h --only-deploy-dependencies --skip-destroy-dependencies
=== RUN TestRunSuite
2025/03/07 14:13:34 INFO TestRunSuite: setup → started
2025/03/07 14:13:34 INFO TestRunSuite: tests will be run in temp directory path=/var/folders/1l/hcm6nfms6g58mdrpwcxklsvh0000gn/T/atmos-test-helper3047340212
2025/03/07 14:13:34 INFO TestRunSuite: terraform state for tests will be saved in state directory path=/var/folders/1l/hcm6nfms6g58mdrpwcxklsvh0000gn/T/atmos-test-helper3047340212/state
2025/03/07 14:13:34 INFO TestRunSuite: setup/bootstrap temp dir → completed
2025/03/07 14:13:34 INFO TestRunSuite: setup/copy component to temp dir → started
2025/03/07 14:13:34 INFO TestRunSuite: setup/copy component to temp dir → completed
2025/03/07 14:13:34 INFO TestRunSuite: vendor dependencies → started
TestRunSuite 2025-03-07T14:13:35+01:00 retry.go:91: atmos [vendor pull]
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Running command atmos with args [vendor pull]
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Processing vendor config file 'vendor.yaml'
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Pulling sources for the component 'account-map' from 'github.com/cloudposse/terraform-aws-components.git//modules/account-map?ref=1.520.0' into 'components/terraform/account-map'
2025/03/07 14:13:42 INFO TestRunSuite: vendor dependencies → completed
2025/03/07 17:38:24 INFO TestRunSuite: deploy dependencies → started
2025/03/07 17:38:24 INFO deploying dependency component=vpc stack=default-test
TestRunSuite 2025-03-07T17:38:24+01:00 retry.go:91: atmos [terraform apply vpc -s default-test -input=false -auto-approve -var attributes=["rydpt4"] -no-color -lock=false]
TestRunSuite 2025-03-07T17:38:24+01:00 logger.go:67: Running command atmos with args [terraform apply vpc -s default-test -input=false -auto-approve -var attributes=["rydpt4"] -no-color -lock=false]
...
2025/03/07 17:43:27 INFO TestRunSuite: deploy dependencies → completed
2025/03/07 17:43:27 INFO TestRunSuite: setup → completed
2025/03/07 17:43:27 WARN TestRunSuite: teardown → skipped
--- PASS: TestRunSuite (322.74s)
PASS
ok test 324.052s
4 Add Test Use-Cases
-
Add atmos configuration for the component use case
test/fixtures/stacks/catalog/usecase/basic.yamlcomponents:
terraform:
# You can replace example-component with your component name
example-component/basic:
metadata:
# Component name dir should be always `target`
component: target
vars:
enabled: true
# Add other inputs that are required for the use case -
Import the use case for
default-test
stacktest/fixtures/stacks/orgs/default/test/tests.yamlimport:
- orgs/default/test/_defaults
- catalog/vpc
# Import the usecase
- catalog/usecase/basic -
Write tests
test/component_test.gopackage test
import (
"testing"
helper "github.com/cloudposse/test-helpers/pkg/atmos/component-helper"
)
type ComponentSuite struct {
helper.TestSuite
}
func TestRunSuite(t *testing.T) {
suite := new(ComponentSuite)
suite.AddDependency(t, "vpc", "default-test", nil)
helper.Run(t, suite)
}
func (s *ComponentSuite) TestBasic() {
const component = "example-component/basic"
const stack = "default-test"
const awsRegion = "us-east-2"
// How to read outputs from the dependent component
// vpcOptions, err := s.GetAtmosOptions("vpc", stack, nil)
// id := atmos.Output(s.T(), vpcOptions, "id")
inputs := map[string]interface{}{
// Add other inputs that are required for the use case
}
defer s.DestroyAtmosComponent(s.T(), component, stack, &inputs)
options, _ := s.DeployAtmosComponent(s.T(), component, stack, &inputs)
assert.NotNil(s.T(), options)
} -
Deploy test component
- Command
- Output
go test -v -timeout 1h --skip-deploy-dependencies --skip-destroy-dependencies --skip-destroy-component --skip-teardown
=== RUN TestRunSuite
2025/03/07 14:13:34 INFO TestRunSuite: setup → started
2025/03/07 14:13:34 INFO TestRunSuite: tests will be run in temp directory path=/var/folders/1l/hcm6nfms6g58mdrpwcxklsvh0000gn/T/atmos-test-helper3047340212
2025/03/07 14:13:34 INFO TestRunSuite: terraform state for tests will be saved in state directory path=/var/folders/1l/hcm6nfms6g58mdrpwcxklsvh0000gn/T/atmos-test-helper3047340212/state
2025/03/07 14:13:34 INFO TestRunSuite: setup/bootstrap temp dir → completed
2025/03/07 14:13:34 INFO TestRunSuite: setup/copy component to temp dir → started
2025/03/07 14:13:34 INFO TestRunSuite: setup/copy component to temp dir → completed
2025/03/07 14:13:34 INFO TestRunSuite: vendor dependencies → started
TestRunSuite 2025-03-07T14:13:35+01:00 retry.go:91: atmos [vendor pull]
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Running command atmos with args [vendor pull]
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Processing vendor config file 'vendor.yaml'
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Pulling sources for the component 'account-map' from 'github.com/cloudposse/terraform-aws-components.git//modules/account-map?ref=1.520.0' into 'components/terraform/account-map'
2025/03/07 14:13:42 INFO TestRunSuite: vendor dependencies → completed
2025/03/07 17:38:24 INFO TestRunSuite: deploy dependencies → skipped
2025/03/07 17:43:27 INFO TestRunSuite: setup → completed
...
2025/03/07 17:43:27 WARN TestRunSuite: teardown → skipped
--- PASS: TestRunSuite (322.74s)
--- PASS: TestRunSuite/TestBasic (3.19s)
PASS
ok test 324.052s
5 Add Assertions
-
Include assertions
Within your test, include assertions to validate the expected outcomes. Use Go's testing package to assert conditions that must be true for the test to pass. This will help ensure that your component behaves as expected.
test/component_test.gopackage test
import (
"testing"
"github.com/cloudposse/test-helpers/pkg/atmos"
helper "github.com/cloudposse/test-helpers/pkg/atmos/component-helper"
"github.com/stretchr/testify/assert"
)
type ComponentSuite struct {
helper.TestSuite
}
func TestRunSuite(t *testing.T) {
suite := new(ComponentSuite)
suite.AddDependency(t, "vpc", "default-test", nil)
helper.Run(t, suite)
}
func (s *ComponentSuite) TestBasic() {
const component = "example-component/basic"
const stack = "default-test"
const awsRegion = "us-east-2"
// How to read outputs from the dependent component
// vpcOptions, err := s.GetAtmosOptions("vpc", stack, nil)
// id := atmos.Output(s.T(), vpcOptions, "id")
inputs := map[string]interface{}{
// Add other inputs that are required for the use case
}
defer s.DestroyAtmosComponent(s.T(), component, stack, &inputs)
options, _ := s.DeployAtmosComponent(s.T(), component, stack, &inputs)
assert.NotNil(s.T(), options)
// How to read string output from the component
output1 := atmos.Output(s.T(), options, "output_name_1")
assert.Equal(s.T(), "expected_value_1", output1)
// How to read list of strings output from the component
output2 := atmos.OutputList(s.T(), options, "output_name_2")
assert.Equal(s.T(), "expected_value_2", output2[0])
assert.ElementsMatch(s.T(), ["expected_value_2"], output2)
// How to read map of objects output from the component
output3 := atmos.OutputMapOfObjects(s.T(), options, "output_name_3")
assert.Equal(s.T(), "expected_value_3", output3["key"])
// How to read struct output from the component
type outputStruct struct {
keyName string `json:"key"`
}
output4 := outputStruct{}
atmos.OutputStruct(s.T(), options, "output_name_4", &output4)
assert.Equal(s.T(), "expected_value_4", output4["keyName"])
} -
Run test
- Command
- Output
go test -v -timeout 1h --skip-deploy-dependencies --skip-destroy-dependencies --skip-destroy-component --skip-teardown
=== RUN TestRunSuite
2025/03/07 14:13:34 INFO TestRunSuite: setup → started
2025/03/07 14:13:34 INFO TestRunSuite: tests will be run in temp directory path=/var/folders/1l/hcm6nfms6g58mdrpwcxklsvh0000gn/T/atmos-test-helper3047340212
2025/03/07 14:13:34 INFO TestRunSuite: terraform state for tests will be saved in state directory path=/var/folders/1l/hcm6nfms6g58mdrpwcxklsvh0000gn/T/atmos-test-helper3047340212/state
2025/03/07 14:13:34 INFO TestRunSuite: setup/bootstrap temp dir → completed
2025/03/07 14:13:34 INFO TestRunSuite: setup/copy component to temp dir → started
2025/03/07 14:13:34 INFO TestRunSuite: setup/copy component to temp dir → completed
2025/03/07 14:13:34 INFO TestRunSuite: vendor dependencies → started
TestRunSuite 2025-03-07T14:13:35+01:00 retry.go:91: atmos [vendor pull]
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Running command atmos with args [vendor pull]
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Processing vendor config file 'vendor.yaml'
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Pulling sources for the component 'account-map' from 'github.com/cloudposse/terraform-aws-components.git//modules/account-map?ref=1.520.0' into 'components/terraform/account-map'
2025/03/07 14:13:42 INFO TestRunSuite: vendor dependencies → completed
2025/03/07 17:38:24 INFO TestRunSuite: deploy dependencies → skipped
2025/03/07 17:43:27 INFO TestRunSuite: setup → completed
...
2025/03/07 17:43:27 WARN TestRunSuite: teardown → skipped
--- PASS: TestRunSuite (322.74s)
--- PASS: TestRunSuite/TestBasic (3.19s)
PASS
ok test 324.052s
6 Add Drift Detection Test
The drifting test ensures that the component is not change any resources on rerun with the same inputs.
-
Add a "drifting test" check
test/component_test.gofunc (s *ComponentSuite) TestBasic() {
const component = "example-component/basic"
const stack = "default-test"
const awsRegion = "us-east-2"
inputs := map[string]interface{}{}
defer s.DestroyAtmosComponent(s.T(), component, stack, &inputs)
options, _ := s.DeployAtmosComponent(s.T(), component, stack, &inputs)
assert.NotNil(s.T(), options)
// ...
// Just add this line to the check for drift
s.DriftTest(component, stack, &inputs)
} -
Run test
- Command
- Output
go test -v -timeout 1h --skip-deploy-dependencies --skip-destroy-dependencies --skip-destroy-component --skip-teardown
=== RUN TestRunSuite
2025/03/07 14:13:34 INFO TestRunSuite: setup → started
2025/03/07 14:13:34 INFO TestRunSuite: tests will be run in temp directory path=/var/folders/1l/hcm6nfms6g58mdrpwcxklsvh0000gn/T/atmos-test-helper3047340212
2025/03/07 14:13:34 INFO TestRunSuite: terraform state for tests will be saved in state directory path=/var/folders/1l/hcm6nfms6g58mdrpwcxklsvh0000gn/T/atmos-test-helper3047340212/state
2025/03/07 14:13:34 INFO TestRunSuite: setup/bootstrap temp dir → completed
2025/03/07 14:13:34 INFO TestRunSuite: setup/copy component to temp dir → started
2025/03/07 14:13:34 INFO TestRunSuite: setup/copy component to temp dir → completed
2025/03/07 14:13:34 INFO TestRunSuite: vendor dependencies → started
TestRunSuite 2025-03-07T14:13:35+01:00 retry.go:91: atmos [vendor pull]
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Running command atmos with args [vendor pull]
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Processing vendor config file 'vendor.yaml'
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Pulling sources for the component 'account-map' from 'github.com/cloudposse/terraform-aws-components.git//modules/account-map?ref=1.520.0' into 'components/terraform/account-map'
2025/03/07 14:13:42 INFO TestRunSuite: vendor dependencies → completed
2025/03/07 17:38:24 INFO TestRunSuite: deploy dependencies → skipped
2025/03/07 17:43:27 INFO TestRunSuite: setup → completed
...
2025/03/07 17:43:27 WARN TestRunSuite: teardown → skipped
--- PASS: TestRunSuite (322.74s)
--- PASS: TestRunSuite/TestBasic (3.19s)
PASS
ok test 324.052s
7 Test disabled
Use-case
All components should avoid creating any resources if the enabled
input is set to false
.
-
Add atmos configuration for the component use case
test/fixtures/stacks/catalog/usecase/disabled.yamlcomponents:
terraform:
# You can replace example-component with your component name
example-component/disabled:
metadata:
component: target
vars:
# Disable the component
enabled: false -
Import the use case for
default-test
stacktest/fixtures/stacks/orgs/default/test/tests.yamlimport:
- orgs/default/test/_defaults
- catalog/vpc
- catalog/usecase/basic
# Import the "disabled" usecase
- catalog/usecase/disabled -
Add a "disabled" use case test
test/component_test.go// ...
func (s *ComponentSuite) TestEnabledFlag() {
const component = "example-component/disabled"
const stack = "default-test"
s.VerifyEnabledFlag(component, stack, nil)
} -
Run test
- Command
- Output
go test -v -timeout 1h --skip-deploy-dependencies --skip-destroy-dependencies --skip-destroy-component --skip-teardown
=== RUN TestRunSuite
2025/03/07 14:13:34 INFO TestRunSuite: setup → started
2025/03/07 14:13:34 INFO TestRunSuite: tests will be run in temp directory path=/var/folders/1l/hcm6nfms6g58mdrpwcxklsvh0000gn/T/atmos-test-helper3047340212
2025/03/07 14:13:34 INFO TestRunSuite: terraform state for tests will be saved in state directory path=/var/folders/1l/hcm6nfms6g58mdrpwcxklsvh0000gn/T/atmos-test-helper3047340212/state
2025/03/07 14:13:34 INFO TestRunSuite: setup/bootstrap temp dir → completed
2025/03/07 14:13:34 INFO TestRunSuite: setup/copy component to temp dir → started
2025/03/07 14:13:34 INFO TestRunSuite: setup/copy component to temp dir → completed
2025/03/07 14:13:34 INFO TestRunSuite: vendor dependencies → started
TestRunSuite 2025-03-07T14:13:35+01:00 retry.go:91: atmos [vendor pull]
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Running command atmos with args [vendor pull]
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Processing vendor config file 'vendor.yaml'
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Pulling sources for the component 'account-map' from 'github.com/cloudposse/terraform-aws-components.git//modules/account-map?ref=1.520.0' into 'components/terraform/account-map'
2025/03/07 14:13:42 INFO TestRunSuite: vendor dependencies → completed
2025/03/07 17:38:24 INFO TestRunSuite: deploy dependencies → skipped
2025/03/07 17:43:27 INFO TestRunSuite: setup → completed
...
2025/03/07 17:43:27 WARN TestRunSuite: teardown → skipped
--- PASS: TestRunSuite (322.74s)
--- PASS: TestRunSuite/TestBasic (3.19s)
--- PASS: TestRunSuite/TestEnabledFlag (1.02s)
PASS
ok test 324.052s
8 Tear Down Resources
Tear down the test environment
- Command
- Output
go test -v -timeout 1h --skip-deploy-dependencies
=== RUN TestRunSuite
2025/03/07 14:13:34 INFO TestRunSuite: setup → started
2025/03/07 14:13:34 INFO TestRunSuite: tests will be run in temp directory path=/var/folders/1l/hcm6nfms6g58mdrpwcxklsvh0000gn/T/atmos-test-helper3047340212
2025/03/07 14:13:34 INFO TestRunSuite: terraform state for tests will be saved in state directory path=/var/folders/1l/hcm6nfms6g58mdrpwcxklsvh0000gn/T/atmos-test-helper3047340212/state
2025/03/07 14:13:34 INFO TestRunSuite: setup/bootstrap temp dir → completed
2025/03/07 14:13:34 INFO TestRunSuite: setup/copy component to temp dir → started
2025/03/07 14:13:34 INFO TestRunSuite: setup/copy component to temp dir → completed
2025/03/07 14:13:34 INFO TestRunSuite: vendor dependencies → started
TestRunSuite 2025-03-07T14:13:35+01:00 retry.go:91: atmos [vendor pull]
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Running command atmos with args [vendor pull]
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Processing vendor config file 'vendor.yaml'
TestRunSuite 2025-03-07T14:13:35+01:00 logger.go:67: Pulling sources for the component 'account-map' from 'github.com/cloudposse/terraform-aws-components.git//modules/account-map?ref=1.520.0' into 'components/terraform/account-map'
2025/03/07 14:13:42 INFO TestRunSuite: vendor dependencies → completed
2025/03/07 17:38:24 INFO TestRunSuite: deploy dependencies → completed
2025/03/07 17:43:27 INFO TestRunSuite: setup → completed
...
2025/03/07 17:43:27 WARN TestRunSuite: teardown → completed
--- PASS: TestRunSuite (322.74s)
--- PASS: TestRunSuite/TestBasic (3.19s)
--- PASS: TestRunSuite/TestEnabledFlag (1.02s)
PASS
ok test 324.052s
FAQ
Why do my tests fail when looking up remote state for components?
If you encounter an error like:
Error: Attempt to get attribute from null value
...
│ module.s3_bucket.outputs is null
...
This value is null, so it does not have any attributes.
This typically occurs when using an older version of the remote-state module. The solution is to upgrade to version 1.8.0
or higher of the cloudposse/stack-config/yaml//modules/remote-state
module. For example:
module "s3_bucket" {
source = "cloudposse/stack-config/yaml//modules/remote-state"
version = "1.8.0"
component = var.destination_bucket_component_name
context = module.this.context
}
How do I handle dependencies in my tests?
When testing components that depend on other infrastructure (like EKS clusters, VPCs, or other foundational components), you need to configure and deploy these dependencies in your test suite. This is done by adding dependencies to the stack test fixtures and deploying before running the tests. For example:
func TestRunSuite(t *testing.T) {
suite := new(ComponentSuite)
// Add dependencies
suite.AddDependency(t, "s3-bucket/cloudwatch", "default-test", nil)
helper.Run(t, suite)
}