You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This repository uses a comprehensive three-layer testing strategy to ensure reliability and correctness at every level of the infrastructure deployment pipeline.
# Run all tests
make test-all
# Run specific test types
make test-unit # BATS unit tests
make test-tofu # OpenTofu infrastructure tests
make test-integration # End-to-end integration tests# Run tests for a specific module
make test-unit MODULE=frontend
make test-tofu MODULE=frontend
make test-integration MODULE=frontend
Test Layers Overview
Our testing strategy follows a pyramid approach with three distinct layers, each serving a specific purpose:
┌─────────────────────┐
│ Integration Tests │ Slow, Few
│ End-to-end flows │
└──────────┬──────────┘
│
┌───────────────┴───────────────┐
│ OpenTofu Tests │ Medium
│ Infrastructure contracts │
└───────────────┬───────────────┘
│
┌───────────────────────────┴───────────────────────────┐
│ Unit Tests │ Fast, Many
│ Script logic & behavior │
└───────────────────────────────────────────────────────┘
Layer
Framework
Purpose
Speed
Coverage
Unit
BATS
Test bash scripts, setup logic, error handling
Fast (~seconds)
High
Infrastructure
OpenTofu
Validate Terraform/OpenTofu module contracts
Medium (~seconds)
Medium
Integration
BATS + Docker
End-to-end workflow validation with mocked services
# Show available test commands
make test# Run all test suites
make test-all
# Run individual test suites
make test-unit
make test-tofu
make test-integration
# Run tests for a specific module
make test-unit MODULE=frontend
make test-tofu MODULE=frontend
make test-integration MODULE=frontend
# Run a single test file directly
bats frontend/deployment/tests/build_context_test.bats
tofu test# from within a modules directory
Unit Tests (BATS)
Unit tests validate the bash scripts that orchestrate the deployment pipeline. They test individual setup scripts, context building, error handling, and environment configuration.
What to Test
Setup scripts: Validate environment variable handling, error cases, output format
#!/usr/bin/env bats# =============================================================================# Unit tests for provider/aws/setup script# =============================================================================# Setup - runs before each testsetup() {
TEST_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")"&& pwd)"
PROJECT_ROOT="$(cd "$TEST_DIR/../../.."&& pwd)"
SCRIPT_PATH="$PROJECT_ROOT/provider/aws/setup"# Load shared test utilitiessource"$PROJECT_ROOT/testing/assertions.sh"# Initialize required environment variablesexport AWS_REGION="us-east-1"export TOFU_PROVIDER_BUCKET="my-terraform-state"export TOFU_LOCK_TABLE="terraform-locks"
}
# Teardown - runs after each testteardown() {
unset AWS_REGION TOFU_PROVIDER_BUCKET TOFU_LOCK_TABLE
}
# =============================================================================# Tests# =============================================================================
@test "fails when AWS_REGION is not set" {
unset AWS_REGION
run source"$SCRIPT_PATH"
assert_equal "$status""1"
assert_contains "$output""AWS_REGION is not set"
}
@test "exports correct TOFU_VARIABLES structure" {
source"$SCRIPT_PATH"local region=$(echo "$TOFU_VARIABLES"| jq -r '.aws_provider.region')
assert_equal "$region""us-east-1"
}
@test "appends to existing MODULES_TO_USE" {
export MODULES_TO_USE="existing/module"source"$SCRIPT_PATH"
assert_contains "$MODULES_TO_USE""existing/module"
assert_contains "$MODULES_TO_USE""provider/aws/modules"
}
Infrastructure Tests (OpenTofu)
Infrastructure tests validate the OpenTofu/Terraform modules in isolation. They verify variable contracts, resource configurations, and module outputs without deploying real infrastructure.
What to Test
Variable validation: Required variables, type constraints, default values
Resource configuration: Correct resource attributes based on inputs
Module outputs: Expected outputs are produced with correct values
Edge cases: Empty values, special characters, boundary conditions
# =============================================================================# Unit tests for cloudfront module# =============================================================================mock_provider"aws" {}
variables {
distribution_bucket_name="my-assets-bucket"distribution_app_name="my-app-123"distribution_s3_prefix="/static"network_hosted_zone_id="Z1234567890"network_domain="example.com"network_subdomain="app"distribution_resource_tags_json={
Environment ="test"
}
}
# =============================================================================# Test: CloudFront distribution is created with correct origin# =============================================================================run"cloudfront_has_correct_s3_origin" {
command=plan
assert {
condition=aws_cloudfront_distribution.static.origin[0].domain_name!=""error_message="CloudFront distribution must have an S3 origin"
}
}
# =============================================================================# Test: Origin Access Control is configured# =============================================================================run"oac_is_configured" {
command=plan
assert {
condition=aws_cloudfront_origin_access_control.static.signing_behavior=="always"error_message="OAC should always sign requests"
}
}
# =============================================================================# Test: Custom error responses for SPA routing# =============================================================================run"spa_error_responses_configured" {
command=plan
assert {
condition=length(aws_cloudfront_distribution.static.custom_error_response) >0error_message="SPA should have custom error responses for client-side routing"
}
}
Integration Tests
Integration tests validate the complete deployment workflow end-to-end. They run in a containerized environment with mocked cloud services, testing the entire pipeline from context building through infrastructure provisioning.
What to Test
Complete workflows: Full deployment and destruction cycles
Service interactions: AWS services, nullplatform API calls
Resource creation: Verify infrastructure is created correctly
Both helper libraries include a test_help function that displays all available utilities:
# View unit test helperssource testing/assertions.sh && test_help
# View integration test helperssource testing/integration_helpers.sh && test_help
Unit Test Assertions (testing/assertions.sh)
Function
Description
assert_equal "$actual" "$expected"
Assert two values are equal
assert_contains "$haystack" "$needle"
Assert string contains substring
assert_not_empty "$value" ["$name"]
Assert value is not empty
assert_empty "$value" ["$name"]
Assert value is empty
assert_file_exists "$path"
Assert file exists
assert_directory_exists "$path"
Assert directory exists
assert_json_equal "$actual" "$expected"
Assert JSON structures are equal
Integration Test Helpers (testing/integration_helpers.sh)
AWS Commands
Function
Description
aws_local <args>
Execute AWS CLI against LocalStack
aws_moto <args>
Execute AWS CLI against Moto (CloudFront)
Workflow Execution
Function
Description
run_workflow "$path"
Run a nullplatform workflow file
Context Management
Function
Description
load_context "$path"
Load context JSON into $CONTEXT
override_context "$key" "$value"
Override a value in current context
API Mocking
Function
Description
clear_mocks
Clear all mocks, set up defaults
mock_request "$method" "$path" "$file"
Mock API request with file response
mock_request "$method" "$path" $status '$body'
Mock API request inline
assert_mock_called "$method" "$path"
Assert mock was called
AWS Assertions
Function
Description
assert_s3_bucket_exists "$bucket"
Assert S3 bucket exists
assert_s3_bucket_not_exists "$bucket"
Assert S3 bucket doesn't exist
assert_cloudfront_exists "$comment"
Assert CloudFront distribution exists
assert_cloudfront_not_exists "$comment"
Assert CloudFront distribution doesn't exist
assert_route53_record_exists "$name" "$type"
Assert Route53 record exists
assert_route53_record_not_exists "$name" "$type"
Assert Route53 record doesn't exist
assert_dynamodb_table_exists "$table"
Assert DynamoDB table exists
Writing New Tests
Unit Test Checklist
Create test file: <module>/tests/<component>/<name>_test.bats
Add setup() function that sources testing/assertions.sh
Set up required environment variables and mocks
Write tests using @test "description" { ... } syntax
Use run to capture command output and exit status
Assert with helper functions or standard bash conditionals
Infrastructure Test Checklist
Create test file: <module>/modules/<name>.tftest.hcl
Add mock_provider "aws" {} to avoid real API calls
Define variables {} block with test inputs
Write run "test_name" { ... } blocks with assertions
Use command = plan to validate without applying
Integration Test Checklist
Create test file: <module>/tests/integration/<name>_test.bats
Add setup_file() to create prerequisites in LocalStack
Add setup() to configure mocks and context per test
Add teardown_file() to clean up
Create localstack/provider_override.tf for LocalStack-compatible provider
Create mock response files in mocks/ directory
Use run_workflow to execute deployment workflows
Assert with AWS assertion helpers
Extending Test Helpers
Adding New Assertions
Add the function to the appropriate helper file:
testing/assertions.sh for unit test helpers
testing/integration_helpers.sh for integration test helpers
Follow the naming convention: assert_<condition> for assertions
Update the test_help function to document your new helper:
# Example: Adding a new assertion to assertions.sh# Add the functionassert_file_contains() {
local file="$1"local content="$2"if! grep -q "$content""$file"2>/dev/null;thenecho"Expected file '$file' to contain: $content"return 1
fi
}
# Update test_help() - add to the appropriate sectiontest_help() {
cat <<'EOF'...FILE SYSTEM ASSERTIONS---------------------- assert_file_exists "<path>" Assert a file exists. assert_file_contains "<path>" "<content>" # <-- Add documentation Assert a file contains specific content....EOF
}