Skip to content

Unit Testing in Ansible

Overview

Unit testing in Ansible focuses on testing individual components like custom modules, filters, and plugins. This ensures each component works correctly in isolation before being integrated into larger playbooks.

Testing Custom Modules

Directory Structure

custom_module/
├── library/
│   └── my_custom_module.py
└── tests/
    └── unit/
        ├── test_my_custom_module.py
        └── conftest.py

Example Custom Module

# library/my_custom_module.py
from ansible.module_utils.basic import AnsibleModule

def run_module():
    module_args = dict(
        name=dict(type='str', required=True),
        state=dict(type='str', default='present', choices=['present', 'absent']),
        value=dict(type='str', required=False)
    )

    result = dict(
        changed=False,
        original_message='',
        message=''
    )

    module = AnsibleModule(
        argument_spec=module_args,
        supports_check_mode=True
    )

    # Module logic here
    if module.params['state'] == 'present':
        result['changed'] = True
        result['message'] = f"Created {module.params['name']}"
    else:
        result['message'] = f"Removed {module.params['name']}"

    module.exit_json(**result)

if __name__ == '__main__':
    run_module()

Unit Test Example

# tests/unit/test_my_custom_module.py
import pytest
from ansible.module_utils import basic
from ansible.module_utils.common.text.converters import to_bytes
import json

def set_module_args(args):
    """Prepare arguments so that they will be picked up during module creation"""
    args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
    basic._ANSIBLE_ARGS = to_bytes(args)

class TestMyCustomModule:
    def test_module_create(self, capfd):
        """Test if module creates resource correctly"""
        set_module_args({
            'name': 'test_resource',
            'state': 'present'
        })

        with pytest.raises(SystemExit):
            import my_custom_module

        out, err = capfd.readouterr()
        result = json.loads(out)

        assert result['changed']
        assert "Created test_resource" in result['message']

    def test_module_remove(self, capfd):
        """Test if module removes resource correctly"""
        set_module_args({
            'name': 'test_resource',
            'state': 'absent'
        })

        with pytest.raises(SystemExit):
            import my_custom_module

        out, err = capfd.readouterr()
        result = json.loads(out)

        assert not result['changed']
        assert "Removed test_resource" in result['message']

Testing Filters and Plugins

Custom Filter Example

# filter_plugins/custom_filters.py
class FilterModule(object):
    def filters(self):
        return {
            'format_hostname': self.format_hostname
        }

    def format_hostname(self, hostname, domain=None):
        if domain:
            return f"{hostname}.{domain}"
        return hostname

Filter Unit Test

# tests/unit/test_filters.py
import pytest
from filter_plugins.custom_filters import FilterModule

class TestCustomFilters:
    def setup_method(self):
        self.filters = FilterModule().filters()

    def test_format_hostname(self):
        assert self.filters['format_hostname']('web01', 'example.com') == 'web01.example.com'
        assert self.filters['format_hostname']('web01') == 'web01'

Running Unit Tests

# Install testing requirements
pip install pytest pytest-mock ansible

# Run tests with pytest
pytest tests/unit/ -v

# Run with coverage
pytest tests/unit/ --cov=library --cov=filter_plugins

Best Practices for Unit Testing

  1. Test Organization
  2. Group related tests in classes
  3. Use meaningful test names
  4. Follow the Arrange-Act-Assert pattern

  5. Mock External Dependencies python def test_with_mock(mocker): mock_cmd = mocker.patch('subprocess.run') mock_cmd.return_value.returncode = 0 # Test code that uses subprocess.run

  6. Test Edge Cases python def test_edge_cases(self): with pytest.raises(ValueError): self.module.process_input(None) with pytest.raises(TypeError): self.module.process_input(123)

  7. Parameterized Testing python @pytest.mark.parametrize("input,expected", [ ("test1", "TEST1"), ("test2", "TEST2"), ("", ""), ]) def test_multiple_cases(self, input, expected): assert self.module.transform(input) == expected