component_helper

package
v0.13.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 29, 2025 License: Apache-2.0 Imports: 14 Imported by: 0

README

Atmos Component Helper

Overview

The component-helper package (The Helper) is designed to be used to test components that follow the Cloud Posse convention for Components (terraform root modules) using standard go testing and the atmos tool for creating test fixtures.

The Helper package provides the concept of a TestSuite, which is a collection of tests that are run against a common component and its dependencies.

The Helper provides several standard test phases that are run in order to setup, deploy, and teardown the component under test and its dependencies.

The main goals of The Helper are to provide a simple way to test components with as little boilerplate code as possible while also making it easy for non-Go developers to understand and use.

Example

package test

import (
  "fmt"
  "testing"

  atmos "github.com/cloudposse/test-helpers/pkg/atmos"
  helper "github.com/cloudposse/test-helpers/pkg/atmos/component-helper"
  "github.com/stretchr/testify/assert"
)

// First we define a test suite that will contain all of our tests. Here we embed the TestSuite struct from the Helper
// package so that this test suite struct will inherit all of the Helper's functionality.
type VpcTestSuite struct {
  helper.TestSuite
}

// Next we can define a set of tests that will be run for out component. These tests are defined as methods on the suite
// struct so that we can control the lifecycle of dependencies and the test suite. Any methods that start with `Test`
// are considered tests.
func (s *VpcTestSuite) TestVPC() {

  // The component name and stack name are passed to the DeployAtmosComponent()  and DestroyAtmosComponent() methods.
  // These stacks and components are defined in the `fixtures/` directory (see test phases below for more details).
  const component = "vpc"
  const stack = "test-use2-sandbox"

  // First, we need to call the DestroyAtmosComponent() method to ensure that the component is destroyed after the
  // tests are run, even if the tests fail. Although this seems backwards, it is actually a good practice to ensure
  // that the component is destroyed after the tests are run. And Go will not let you leave a function scope without
  // executing the defer statement, even if other parts of the test suite fail.
  defer s.DestroyAtmosComponent(s.T(), component, stack, nil)

  // Next, we need to call the DeployAtmosComponent() method to actually deploy the component. Under the hood, this
  // method will call the `atmos terraform deploy` command to deploy the component.
  options, _ := s.DeployAtmosComponent(s.T(), component, stack, nil)

  // Finally, as an example, we can use the atmos Output() method to get the value of the `vpc_cidr` output from the
  // component and assert that it is equal to the expected value.
  cidrBlock := atmos.Output(s.T(), options, "vpc_cidr")
  assert.Equal(s.T(), "10.1.0.0/16", cidrBlock)
}

func (s *VpcTestSuite) TestVPC2() {
 // Add other tests as needed. For example, you may deploy one VPC with only private subnets, one without a NAT gateway,
 // etc. and run various other assertions to validate the component behaves as you expect with different configuration.
}

// Cloud Posse strongly recommends that each component have a flag (variable) that can be set to enable ot disable the
// entire component (root module). This is extremely useful in a number of scenarios. As such, all of Cloud Posse's
// components have a `enabled` flag that can be set to `true` or `false`. This test verifies that running
// `atmos terraform plan` on the component results in a `no op` plan (that is, a plan that shows no changes).
func (s *VpcTestSuite) TestEnabledFlag() {
  s.VerifyEnabledFlag("vpc", "test-use2-sandbox", nil)
}


// Finally, we define a standard go test function that will run the test suite. Note that this function is not a method
// on the suite struct, but rather a standard go test function. This becomes the entry point for the test suite.
func TestRunVPCSuite(t *testing.T) {

  // Create a new instance of the test suite struct.
  suite := new(VpcTestSuite)

  // Add dependencies to the test suite.
  suite.AddDependency(t, "vpc-flow-logs", "test-use2-sandbox", nil)

  // Run the test suite
  helper.Run(t, suite)
}

Now that we have a TestSuite defined, we can use Go's standard test runner

Component Directory Structure

The Helper assumes that each test suite is used to test a component (root module) and that the component, atmos stack configuration, and tests are are located within a directory that is structured as described below, however, The Helper is configurable so that it can be used in other directory structures.

component-root/
├── src/
│   ├── main.tf
│   ├── variables.tf
│   ├── outputs.tf
│   └── other files...
└── test/
    ├── fixtures/
    │   ├── stacks/
    │   │   ├── orgs/
    │   │   │   └── other stack directories...
    │   │── atmos.yaml
    │   └── vendor.yaml
    │
    ├── component_name_test.go
    ├── component_name_variation_test.go
    └── other test files as needed...

Test Phases

As mentioned above, The Helper provides several standard test phases that are run in order to setup, deploy, and teardown the component under test and its dependencies. It is possible to skip some of these phases using command-line flags so that you can iterate locally on the tests without having to run all of the phases. This is particularly helpful when deploying dependencies, which often can take a considerable amount of time to complete.

The following test phases are run by default (and the flag to skip where applicable):

Setup (--skip-setup)

During the setup phase, The Helper will first create a temporary directory to use for running the tests. If you want to specify the path to the temporary directory, you can use the --temp-dir flag, otherwise it will be randomly generated.

This temporary directory is structured exactly like a typical atmos project directory:

root/
├── components/
│   ├── terraform/
│   │   ├── target/
│   │   ├── componentX/
│   │   └── componentY/
├── stacks/
├── atmos.yaml
└── vendor.yaml

The Helper will copy the component under test from the component's src directory to the components/terraform/target directory under the temp directory. The source directory can be overridden using the --src-dir flag.

Next, the Helper will copy the contents of the fixtures directory to the root of the temp directory. This includes the stacks directory, the atmos.yaml file, and the vendor.yaml file. If yyou want to override the path to the fixtures directory that is being copied, you can use the --fixtures-path flag.

Vendor Dependencies (--skip-vendor)

During the next phase, The Helper will switch into the temp directory and run atmos vendor pull to install any dependencies that were defined in the vendor.yaml file.

Deploy Dependencies (--skip-deploy)

Next, The Helper will switch into the temp directory and run atmos deploy for each of the stack dependencies defined within the test suite. The order of deployment is the same as the order in which the dependencies are added to the suite.

Test

The Helper will then use the go test command to run any tests that are defined in the test suite.

Destroy Dependencies (--skip-destroy-dependencies)

Once all of the tests have been run, The Helper will destroy all of the dependencies in the reverse order of which they were deployed by running the atmos destroy command for each dependency.

Teardown (--skip-teardown)

Finally, The Helper will clean up the temporary directory and any other resources that were created during the test run.

Advanced Usage

  • If you choose to use any of the --skip-* flags, the test suite will write a file to the directory where the test suite is being run. By default this file is called test_suite.yaml and it contains information needed to run the test suite again in the future, including the path to the temporary and state directories. If you need to run multiple suites at the same time, you can use the -config flag to specify a different configuration file name.

  • When you use the --skip-setup flag, the test suite will use the previously created temporary directory and state directories, but will always copy the component under test and the fixtures directory to the temporary directory to ensure that the test suite is always being run on the latest version of the component and its configuration.

  • As previously mentioned, The temporary directory is structured exactly like a typical atmos project directory. This means that you can switch to the temporary directory and run atmos commands directly to deploy and destroy the component under test and its dependencies. This can be useful for debugging issues with the component or its dependencies.

Flags reference

Flag Description Default
-config The path to the config file test_suite.yaml
-fixtures-dir The path to the fixtures directory fixtures
-skip-deploy-dependencies Skips running the deploy dependencies phase of tests false
-skip-destroy-component Skips running the destroy component phase of tests false
-skip-destroy-dependencies Skips running the destroy dependencies phase of tests false
-skip-enabled-flag-test Skips running the Enabled flag test false
-skip-setup Skips running the setup test suite phase of tests false
-skip-teardown Skips running the teardown test suite phase of tests false
-skip-vendor Skips running the vendor dependencies phase of tests false
-src-dir The path to the component source directory src
-state-dir The path to the terraform state directory {temp_dir}/state
-temp-dir The path to the temp directory {random temp dir}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Run

func Run(t *testing.T, s suite.TestingSuite)

Types

type TestSuite

type TestSuite struct {
	Config       *c.Config
	Dependencies []*dependency.Dependency
	suite.Suite
}

func NewTestSuite

func NewTestSuite() *TestSuite

func (*TestSuite) AddDependency

func (s *TestSuite) AddDependency(t *testing.T, componentName string, stackName string, additionalVars *map[string]interface{})

func (*TestSuite) BootstrapTempDir

func (s *TestSuite) BootstrapTempDir(t *testing.T, config *c.Config)

func (*TestSuite) CopyComponentToTempDir

func (s *TestSuite) CopyComponentToTempDir(t *testing.T, config *c.Config)

func (*TestSuite) DeployAtmosComponent

func (s *TestSuite) DeployAtmosComponent(t *testing.T, componentName string, stackName string, additionalVars *map[string]interface{}) (*atmos.Options, string)

func (*TestSuite) DeployDependencies

func (s *TestSuite) DeployDependencies(t *testing.T, config *c.Config)

func (*TestSuite) DestroyAtmosComponent

func (s *TestSuite) DestroyAtmosComponent(t *testing.T, componentName string, stackName string, additionalVars *map[string]interface{})

func (*TestSuite) DestroyConfigFile

func (s *TestSuite) DestroyConfigFile(t *testing.T, config *c.Config)

func (*TestSuite) DestroyDependencies

func (s *TestSuite) DestroyDependencies(t *testing.T, config *c.Config)

func (*TestSuite) DestroyTempDir

func (s *TestSuite) DestroyTempDir(t *testing.T, config *c.Config)

func (*TestSuite) SetupSuite

func (s *TestSuite) SetupSuite()

Setup runs the setup phase of the test suite.

func (*TestSuite) TearDownSuite

func (s *TestSuite) TearDownSuite()

func (*TestSuite) VendorDependencies

func (s *TestSuite) VendorDependencies(t *testing.T, config *c.Config)

func (*TestSuite) VerifyEnabledFlag

func (s *TestSuite) VerifyEnabledFlag(componentName, stackName string, additionalVars *map[string]interface{})

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL