Testing
ACK uses end-to-end (E2E) tests that run against real AWS APIs to verify controller behavior. These tests create actual AWS resources, verify the controller reconciles them correctly, and clean up afterward.
How Testing Works
You can run E2E tests in two ways:
- Locally — Run the controller binary directly on your machine (no need to install it in a cluster), pointing it at a Kubernetes cluster (kind or otherwise). The controller runs as a regular process, making it easy to attach a debugger or add logging.
- In CI — Prow automatically runs E2E tests on every PR using a kind cluster with real AWS credentials.
Both approaches use the same pytest-based test framework (acktest).
Test Types
Unit Tests
cd $SERVICE-controller
make test
Only needed when you've added custom logic in hooks or helper functions. Generated code doesn't need unit tests.
E2E Tests
E2E tests use pytest with the acktest framework from the test-infra repository.
Setting Up the Test Environment
cd $SERVICE-controller
# Create Python virtual environment
python3 -m venv test/e2e/.venv
source test/e2e/.venv/bin/activate
# Install dependencies
pip install -r test/e2e/requirements.txt
pip install setuptools # Required for Python 3.13+
# Set required environment variables
export AWS_ACCOUNT_ID=123456789012
export AWS_REGION=us-west-2
E2E tests create and delete real AWS resources. Always use a dedicated test account.
Running Tests
source test/e2e/.venv/bin/activate
python -m pytest test/e2e/tests/test_<resource>.py -v
Test File Structure
test/e2e/
├── __init__.py # Service constants and load helper
├── conftest.py # pytest fixtures (boto3 client)
├── requirements.txt # acktest dependency (pins test-infra commit)
├── bootstrap_resources.py # Bootstrap resource loader
├── service_bootstrap.py # Bootstrap lifecycle (create prereqs)
├── service_cleanup.py # Cleanup lifecycle (delete prereqs)
├── resources/
│ └── <resource>.yaml # YAML fixtures with $VARIABLE placeholders
└── tests/
├── __init__.py
└── test_<resource>.py # Test cases
Writing E2E Tests
Required Coverage
Every resource PR must include E2E tests that verify:
- Create — Resource is created and reaches synced state
- Update — At least one field is modified successfully
- Delete — Resource is cleaned up
- Synced condition —
ACK.ResourceSyncedisTrueafter each operation
Test Pattern
from acktest.resources import random_suffix_name
from acktest.k8s import resource as k8s
from acktest.k8s import condition
import time
CREATE_WAIT_AFTER_SECONDS = 10
def test_create_delete(self, service_client):
resource_name = random_suffix_name("ack-test", 24)
replacements = {"RESOURCE_NAME": resource_name}
resource_data = load_resource("my_resource", additional_replacements=replacements)
ref = k8s.CustomResourceReference(
CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL,
resource_name, namespace="default"
)
k8s.create_custom_resource(ref, resource_data)
cr = k8s.wait_resource_consumed_by_controller(ref)
assert cr is not None
time.sleep(CREATE_WAIT_AFTER_SECONDS)
assert k8s.wait_on_condition(
ref, condition.CONDITION_TYPE_RESOURCE_SYNCED, "True", wait_periods=5
)
# Verify in AWS
aws_resource = service_client.describe_resource(Name=resource_name)
assert aws_resource is not None
# Cleanup
_, deleted = k8s.delete_custom_resource(ref)
assert deleted
AWS API Assertions
Reviewers expect tests to verify state via both the Kubernetes CR and direct AWS API calls:
# Verify via CR
cr = k8s.get_resource(ref)
assert cr["spec"]["fieldName"] == "expected-value"
# Verify via AWS API
aws_resource = service_client.describe_resource(Name=resource_name)
assert aws_resource["FieldName"] == "expected-value"
Bootstrap Resources
Some resources depend on other AWS resources that must exist before the test runs (e.g., a VPC for a subnet group, or an IAM role for a service). Use service_bootstrap.py to create these prerequisites and service_cleanup.py to tear them down. Bootstrap resources are created once per test session and shared across tests.
CI Testing with Prow
ACK uses Prow for continuous integration. When you open a PR, Prow runs several jobs:
- unit-test — Runs
make test - verify-code-gen — Verifies generated code is up to date
- kind-e2e — Spins up a kind cluster, installs the controller, and runs E2E tests against real AWS
The kind-e2e job uses a service team IAM role to authenticate with AWS. This role is registered in SSM and scoped to the specific service's APIs. If your controller is new, you'll need to onboard it to the test infrastructure — see the test-infra onboarding guide.
Check the Prow build logs for test failures — both the test output and controller logs are available as build artifacts.
Common Issues
- boto3 too old for new API fields — Update the test-infra pin in
test/e2e/requirements.txtto a newer commit with a current boto3 - Slow-provisioning resources — Pass explicit
timeout_secondstowait_until()rather than bumping global defaults - Python 3.13+ — Install
setuptoolsseparately (pip install setuptools) - Flaky tests — Usually timing issues; add retries or increase wait times