Mock AWX/AAP Testing Guide#
This guide shows you how to test AWX/Ansible Automation Platform API integrations without a real AWX server using mocked HTTP responses.
Why Mock AWX Testing?#
- No Infrastructure Required: Test without deploying AWX/Tower
- Fast Execution: Tests run in milliseconds
- Reliable: No network dependencies or authentication complexity
- Controlled Scenarios: Test edge cases, errors, and specific API responses
- CI/CD Friendly: Run in any environment without AWX infrastructure
Quick Start#
Run All Mock Tests#
# Run all AWX mock API tests
poetry run pytest tests/integration/test_awx_mock.py -v
# Run with specific test class
poetry run pytest tests/integration/test_awx_mock.py::TestAWXMockJobTemplates -v
# Run single test
poetry run pytest tests/integration/test_awx_mock.py::TestAWXMockJobs::test_get_job_status -v
Example Output#
tests/integration/test_awx_mock.py::TestAWXMockJobTemplates::test_list_job_templates PASSED
tests/integration/test_awx_mock.py::TestAWXMockJobTemplates::test_create_job_template PASSED
tests/integration/test_awx_mock.py::TestAWXMockJobTemplates::test_launch_job_template PASSED
tests/integration/test_awx_mock.py::TestAWXMockInventories::test_list_inventories PASSED
...
======================== 20 passed in 1.2s ========================
AWX API Coverage#
The mock tests cover all major AWX/AAP API endpoints:
[YES] Job Templates#
- List job templates
- Create job template
- Launch job template
- Get job template details
[YES] Inventories#
- List inventories
- Sync inventory sources
- Get inventory hosts and groups
[YES] Projects#
- List projects
- Update project (SCM sync)
- Get project details
[YES] Credentials#
- List credentials
- Create credentials
- Manage credential types
[YES] Workflows#
- List workflow templates
- Launch workflow
- Get workflow job status
[YES] Jobs & Monitoring#
- Get job status
- Get job output/stdout
- Cancel running jobs
- List job events
[YES] Authentication & Errors#
- 401 Unauthorized responses
- 403 Forbidden responses
- 404 Not Found responses
- 400 Validation errors
[YES] Organizations#
- List organizations
- Get organization details
How It Works#
Mock AWX testing uses the responses library to intercept HTTP requests:
import responses
import requests
@responses.activate
def test_awx_job_template():
# Mock AWX API response
responses.add(
responses.GET,
"https://awx.example.com/api/v2/job_templates/",
json={
"count": 1,
"results": [
{
"id": 1,
"name": "Deploy Apache",
"playbook": "apache_deploy.yml"
}
]
},
status=200
)
# Make real HTTP request (intercepted by responses)
response = requests.get(
"https://awx.example.com/api/v2/job_templates/",
headers={"Authorization": "Bearer test-token"}
)
# Verify response
assert response.status_code == 200
assert response.json()["count"] == 1
Writing Your Own AWX Mock Tests#
Basic Structure#
import responses
import requests
@responses.activate
def test_my_awx_feature():
# 1. Setup mock AWX response
responses.add(
responses.POST,
"https://awx.example.com/api/v2/job_templates/1/launch/",
json={
"job": 42,
"status": "pending"
},
status=201
)
# 2. Make API call
response = requests.post(
"https://awx.example.com/api/v2/job_templates/1/launch/",
json={"extra_vars": {"env": "prod"}},
headers={"Authorization": "Bearer test-token"}
)
# 3. Assert results
assert response.status_code == 201
assert response.json()["job"] == 42
Testing Job Launches#
@responses.activate
def test_launch_and_monitor_job():
# Mock job launch
responses.add(
responses.POST,
"https://awx.example.com/api/v2/job_templates/1/launch/",
json={"job": 42, "status": "pending"},
status=201
)
# Mock job status check
responses.add(
responses.GET,
"https://awx.example.com/api/v2/jobs/42/",
json={"id": 42, "status": "successful", "failed": False},
status=200
)
# Launch job
launch_response = requests.post(
"https://awx.example.com/api/v2/job_templates/1/launch/",
headers={"Authorization": "Bearer test-token"}
)
job_id = launch_response.json()["job"]
# Check job status
status_response = requests.get(
f"https://awx.example.com/api/v2/jobs/{job_id}/",
headers={"Authorization": "Bearer test-token"}
)
assert status_response.json()["status"] == "successful"
Testing Workflows#
@responses.activate
def test_workflow_execution():
# Mock workflow launch
responses.add(
responses.POST,
"https://awx.example.com/api/v2/workflow_job_templates/1/launch/",
json={"workflow_job": 25, "status": "pending"},
status=201
)
# Mock workflow status
responses.add(
responses.GET,
"https://awx.example.com/api/v2/workflow_jobs/25/",
json={
"id": 25,
"status": "successful",
"workflow_nodes": [
{"job": 42, "status": "successful"},
{"job": 43, "status": "successful"}
]
},
status=200
)
# Launch workflow
response = requests.post(
"https://awx.example.com/api/v2/workflow_job_templates/1/launch/",
headers={"Authorization": "Bearer test-token"}
)
workflow_job_id = response.json()["workflow_job"]
# Check workflow status
status = requests.get(
f"https://awx.example.com/api/v2/workflow_jobs/{workflow_job_id}/",
headers={"Authorization": "Bearer test-token"}
)
assert status.json()["status"] == "successful"
Testing Inventory Updates#
@responses.activate
def test_inventory_sync():
# Mock inventory source sync
responses.add(
responses.POST,
"https://awx.example.com/api/v2/inventory_sources/5/update/",
json={"inventory_update": 15, "status": "pending"},
status=202
)
# Mock inventory update status
responses.add(
responses.GET,
"https://awx.example.com/api/v2/inventory_updates/15/",
json={"id": 15, "status": "successful", "total_hosts": 50},
status=200
)
# Trigger sync
sync_response = requests.post(
"https://awx.example.com/api/v2/inventory_sources/5/update/",
headers={"Authorization": "Bearer test-token"}
)
update_id = sync_response.json()["inventory_update"]
# Check sync status
status = requests.get(
f"https://awx.example.com/api/v2/inventory_updates/{update_id}/",
headers={"Authorization": "Bearer test-token"}
)
assert status.json()["total_hosts"] == 50
Common AWX API Patterns#
List Resources (GET with Pagination)#
responses.add(
responses.GET,
"https://awx.example.com/api/v2/job_templates/",
json={
"count": 100,
"next": "/api/v2/job_templates/?page=2",
"previous": None,
"results": [
{"id": 1, "name": "Template 1"},
{"id": 2, "name": "Template 2"}
]
},
status=200
)
Create Resource (POST)#
responses.add(
responses.POST,
"https://awx.example.com/api/v2/job_templates/",
json={
"id": 3,
"name": "New Template",
"created": "2026-02-16T12:00:00Z"
},
status=201
)
Update Resource (PATCH/PUT)#
responses.add(
responses.PATCH,
"https://awx.example.com/api/v2/job_templates/1/",
json={
"id": 1,
"name": "Updated Template",
"modified": "2026-02-16T12:00:00Z"
},
status=200
)
Delete Resource (DELETE)#
responses.add(
responses.DELETE,
"https://awx.example.com/api/v2/job_templates/1/",
status=204 # No content
)
Authentication Errors#
responses.add(
responses.GET,
"https://awx.example.com/api/v2/job_templates/",
json={"detail": "Authentication credentials were not provided."},
status=401
)
AWX API Authentication Methods#
AWX supports multiple authentication methods:
Bearer Token (Recommended for testing)#
Basic Auth#
import base64
auth = base64.b64encode(b"username:password").decode()
headers = {"Authorization": f"Basic {auth}"}
Session Auth (OAuth2)#
# Mock OAuth token endpoint
responses.add(
responses.POST,
"https://awx.example.com/api/o/token/",
json={"access_token": "test-token", "expires_in": 36000},
status=200
)
Testing Error Scenarios#
Validation Errors#
@responses.activate
def test_invalid_job_template():
responses.add(
responses.POST,
"https://awx.example.com/api/v2/job_templates/",
json={
"name": ["This field is required."],
"inventory": ["Invalid inventory ID."]
},
status=400
)
response = requests.post(
"https://awx.example.com/api/v2/job_templates/",
json={"description": "Missing required fields"},
headers={"Authorization": "Bearer test-token"}
)
assert response.status_code == 400
assert "name" in response.json()
Permission Errors#
@responses.activate
def test_insufficient_permissions():
responses.add(
responses.POST,
"https://awx.example.com/api/v2/job_templates/1/launch/",
json={"detail": "You do not have permission to perform this action."},
status=403
)
response = requests.post(
"https://awx.example.com/api/v2/job_templates/1/launch/",
headers={"Authorization": "Bearer limited-token"}
)
assert response.status_code == 403
Resource Not Found#
@responses.activate
def test_template_not_found():
responses.add(
responses.GET,
"https://awx.example.com/api/v2/job_templates/999/",
json={"detail": "Not found."},
status=404
)
response = requests.get(
"https://awx.example.com/api/v2/job_templates/999/",
headers={"Authorization": "Bearer test-token"}
)
assert response.status_code == 404
Debugging Mock Tests#
Inspect Requests Made#
@responses.activate
def test_with_debugging():
responses.add(responses.GET, "https://awx.example.com/api/v2/jobs/42/", json={}, status=200)
requests.get("https://awx.example.com/api/v2/jobs/42/", headers={"Authorization": "Bearer test"})
# Debug: see what was sent
print(f"Total requests: {len(responses.calls)}")
print(f"URL: {responses.calls[0].request.url}")
print(f"Headers: {dict(responses.calls[0].request.headers)}")
Verify Query Parameters#
@responses.activate
def test_with_query_params():
responses.add(
responses.GET,
"https://awx.example.com/api/v2/job_templates/",
json={"results": []},
status=200,
match=[responses.matchers.query_param_matcher({"page": "2", "page_size": "20"})]
)
# This will only match if query params are exactly as specified
response = requests.get(
"https://awx.example.com/api/v2/job_templates/",
params={"page": 2, "page_size": 20}
)
Performance#
Mock AWX tests are extremely fast:
# 20 comprehensive AWX API tests in ~1.2 seconds
poetry run pytest tests/integration/test_awx_mock.py -v
# 20 passed in 1.20s
Compare to real AWX API calls which might take 1-3 seconds each due to: - Network latency - TLS handshake - Authentication - Database queries - Job scheduling
CI/CD Integration#
<Mock tests work perfectly in CI/CD:
# .github/workflows/test.yml
- name: Run AWX Mock Tests
run: poetry run pytest tests/integration/test_awx_mock.py -v
No AWX infrastructure needed!
Combining with Chef Server Mocks#
Test complete migration scenarios by mocking both Chef Server AND AWX:
@responses.activate
def test_chef_to_awx_migration():
# Mock Chef Server node query
responses.add(
responses.GET,
"https://chef.example.com/organizations/default/search/node",
json={"rows": [{"name": "web-01", "platform": "ubuntu"}]},
status=200
)
# Mock AWX inventory creation
responses.add(
responses.POST,
"https://awx.example.com/api/v2/inventories/",
json={"id": 5, "name": "Chef Migrated Hosts"},
status=201
)
# Mock AWX host creation
responses.add(
responses.POST,
"https://awx.example.com/api/v2/inventories/5/hosts/",
json={"id": 10, "name": "web-01"},
status=201
)
# Your migration code here...
# 1. Query Chef Server for nodes
# 2. Create AWX inventory
# 3. Populate AWX inventory with nodes
Real AWX Testing#
For testing with a real AWX instance, see: - AWX Official Docs - AWX CLI - AWX API Guide
Further Reading#
- AWX API Documentation: https://docs.ansible.com/ansible-tower/latest/html/towerapi/
- responses library: https://github.com/getsentry/responses
- Mock tests:
tests/integration/test_awx_mock.py(repository source) - Chef Server mocks: MOCK_CHEF_SERVER.md