Implement Ansible testing with Molecule and TestInfra for infrastructure automation validation

Intermediate 45 min Apr 01, 2026 14 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up comprehensive testing for Ansible playbooks using Molecule framework and TestInfra validation. Create automated test scenarios, integrate with CI/CD pipelines, and ensure infrastructure code reliability in production environments.

Prerequisites

  • Python 3.9 or higher
  • Docker installed and running
  • Basic Ansible knowledge
  • Git version control system

What this solves

Ansible testing with Molecule and TestInfra provides automated validation for your infrastructure code before deploying to production. This prevents configuration drift, catches errors early, and ensures playbooks work consistently across different environments and operating systems.

Step-by-step installation

Update system packages and install Python dependencies

Start by updating your system and installing Python development tools required for Molecule and TestInfra.

sudo apt update && sudo apt upgrade -y
sudo apt install -y python3-pip python3-dev python3-venv git docker.io
sudo dnf update -y
sudo dnf install -y python3-pip python3-devel git docker
sudo systemctl enable --now docker

Configure Docker permissions

Add your user to the docker group to run containers without sudo. This is required for Molecule's Docker driver.

sudo usermod -aG docker $USER
newgrp docker
Note: Log out and back in if the newgrp command doesn't work immediately.

Create Python virtual environment for testing tools

Set up an isolated Python environment to avoid conflicts with system packages.

mkdir -p ~/ansible-testing
cd ~/ansible-testing
python3 -m venv molecule-env
source molecule-env/bin/activate

Install Ansible, Molecule, and TestInfra

Install the core testing framework components with Docker driver support.

pip install --upgrade pip
pip install ansible molecule[docker] testinfra yamllint ansible-lint
pip install molecule-plugins[docker]

Verify installation

Check that all tools are installed correctly and can communicate with Docker.

molecule --version
ansible --version
testinfra --version
docker version

Step-by-step configuration

Create sample Ansible role structure

Initialize a new Ansible role that we'll use to demonstrate testing capabilities.

cd ~/ansible-testing
molecule init role nginx_role --driver-name docker

Configure the sample Ansible role

Create a basic nginx installation role to test against multiple operating systems.

---
  • name: Update package cache (Ubuntu/Debian)
apt: update_cache: yes when: ansible_os_family == "Debian"
  • name: Update package cache (RHEL/CentOS)
dnf: update_cache: yes when: ansible_os_family == "RedHat"
  • name: Install nginx
package: name: nginx state: present
  • name: Start and enable nginx
systemd: name: nginx state: started enabled: yes
  • name: Create custom index page
copy: content: | Test Server

Nginx Test Server Running

dest: /var/www/html/index.html owner: root group: root mode: '0644' notify: restart nginx

Create role handlers

Define handlers for service restarts that will be triggered by configuration changes.

---
  • name: restart nginx
systemd: name: nginx state: restarted

Configure Molecule scenarios

Set up Molecule to test against multiple Linux distributions with comprehensive test scenarios.

---
dependency:
  name: galaxy
driver:
  name: docker
platforms:
  - name: ubuntu-24
    image: ubuntu:24.04
    pre_build_image: true
    privileged: true
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw
    cgroupns_mode: host
    command: "/lib/systemd/systemd"
    tmpfs:
      - /run
      - /tmp
  - name: debian-12
    image: debian:12
    pre_build_image: true
    privileged: true
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw
    cgroupns_mode: host
    command: "/lib/systemd/systemd"
    tmpfs:
      - /run
      - /tmp
  - name: almalinux-9
    image: almalinux:9
    pre_build_image: true
    privileged: true
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw
    cgroupns_mode: host
    command: "/usr/lib/systemd/systemd"
    tmpfs:
      - /run
      - /tmp
provisioner:
  name: ansible
  config_options:
    defaults:
      interpreter_python: auto_silent
      callback_whitelist: profile_tasks, timer, yaml
    ssh_connection:
      pipelining: false
verifier:
  name: testinfra
scenario:
  test_sequence:
    - dependency
    - cleanup
    - destroy
    - syntax
    - create
    - prepare
    - converge
    - idempotence
    - side_effect
    - verify
    - cleanup
    - destroy

Create prepare playbook for container setup

Configure containers to properly run systemd services during testing.

---
  • name: Prepare containers
hosts: all gather_facts: false tasks: - name: Install systemd and python3 (Ubuntu/Debian) raw: | apt-get update apt-get install -y systemd python3 python3-apt when: ansible_os_family == "Debian" or inventory_hostname is match("ubuntu.") or inventory_hostname is match("debian.") changed_when: false - name: Install systemd and python3 (RHEL/CentOS/AlmaLinux) raw: | dnf install -y systemd python3 python3-dnf when: ansible_os_family == "RedHat" or inventory_hostname is match("alma.") or inventory_hostname is match("rocky.") changed_when: false - name: Gather facts after python installation setup:

Create comprehensive TestInfra validation tests

Define infrastructure tests that verify nginx installation, configuration, and service status.

import os
import testinfra.utils.ansible_runner

testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']
).get_hosts('all')

def test_nginx_package_installed(host):
    """Test that nginx package is installed"""
    nginx = host.package("nginx")
    assert nginx.is_installed

def test_nginx_service_running(host):
    """Test that nginx service is running and enabled"""
    nginx = host.service("nginx")
    assert nginx.is_running
    assert nginx.is_enabled

def test_nginx_listening_port_80(host):
    """Test that nginx is listening on port 80"""
    socket = host.socket("tcp://0.0.0.0:80")
    assert socket.is_listening

def test_nginx_config_valid(host):
    """Test that nginx configuration is valid"""
    cmd = host.run("nginx -t")
    assert cmd.rc == 0
    assert "syntax is ok" in cmd.stderr
    assert "test is successful" in cmd.stderr

def test_custom_index_page_exists(host):
    """Test that custom index page was created"""
    index_file = host.file("/var/www/html/index.html")
    assert index_file.exists
    assert index_file.user == "root"
    assert index_file.group == "root"
    assert index_file.mode == 0o644
    assert "Test Server Running" in index_file.content_string

def test_nginx_serves_custom_page(host):
    """Test that nginx serves the custom index page"""
    cmd = host.run("curl -s http://localhost/")
    assert cmd.rc == 0
    assert "Test Server Running" in cmd.stdout
    assert "Test Server" in cmd.stdout

def test_nginx_process_running(host):
    """Test that nginx processes are running"""
    nginx_processes = host.process.filter(comm="nginx")
    assert len(nginx_processes) >= 1

def test_nginx_log_files_exist(host):
    """Test that nginx log files exist and are writable"""
    access_log = host.file("/var/log/nginx/access.log")
    error_log = host.file("/var/log/nginx/error.log")
    
    # Files should exist (created by package or after first request)
    if access_log.exists:
        assert access_log.user == "root"
    if error_log.exists:
        assert error_log.user == "root"

Create converge playbook

Define the main playbook that Molecule will use to test role execution.

---
  • name: Converge
hosts: all tasks: - name: "Include nginx_role" include_role: name: "nginx_role"

Configure linting rules

Set up yamllint and ansible-lint configurations for code quality checks.

extends: default

rules:
  line-length:
    max: 120
    level: warning
  comments-indentation: disable
  comments: disable
  document-start: disable

Run complete Molecule test suite

Execute the full test sequence including syntax, idempotence, and infrastructure validation.

cd nginx_role
molecule test
Note: The first run will take several minutes as Docker images are downloaded and containers are created.

Step-by-step CI/CD integration

Create GitHub Actions workflow

Set up automated testing in CI/CD pipelines with matrix builds for multiple distributions.

---
name: Molecule CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  molecule:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        scenario:
          - default
        python-version:
          - '3.9'
          - '3.10'
          - '3.11'
    
    steps:
      - name: Check out code
        uses: actions/checkout@v4
        with:
          path: 'nginx_role'
      
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
      
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install molecule[docker] testinfra yamllint ansible-lint
          pip install molecule-plugins[docker]
      
      - name: Run Molecule tests
        run: |
          cd nginx_role
          molecule test --scenario-name ${{ matrix.scenario }}
        env:
          PY_COLORS: '1'
          ANSIBLE_FORCE_COLOR: '1'
          MOLECULE_NO_LOG: 'false'

Create GitLab CI configuration

Configure GitLab CI/CD pipeline for organizations using GitLab for version control and automation.

---
stages:
  - test
  - deploy

variables:
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"

cache:
  paths:
    - .cache/pip/
    - venv/

molecule-test:
  stage: test
  image: python:3.11
  services:
    - docker:24-dind
  variables:
    DOCKER_HOST: tcp://docker:2376
    DOCKER_TLS_VERIFY: 1
    DOCKER_CERT_PATH: "/certs/client"
  before_script:
    - apt-get update && apt-get install -y docker.io
    - python -m venv venv
    - source venv/bin/activate
    - pip install --upgrade pip
    - pip install molecule[docker] testinfra yamllint ansible-lint
    - pip install molecule-plugins[docker]
  script:
    - cd nginx_role
    - molecule test
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

lint-code:
  stage: test
  image: python:3.11
  before_script:
    - pip install yamllint ansible-lint
  script:
    - yamllint nginx_role/
    - ansible-lint nginx_role/
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Create Jenkins pipeline configuration

Set up Jenkins declarative pipeline for teams using Jenkins for continuous integration.

pipeline {
    agent any
    
    environment {
        VENV_PATH = "${WORKSPACE}/molecule-venv"
        PATH = "${VENV_PATH}/bin:${PATH}"
    }
    
    stages {
        stage('Setup') {
            steps {
                script {
                    sh '''
                        python3 -m venv ${VENV_PATH}
                        . ${VENV_PATH}/bin/activate
                        pip install --upgrade pip
                        pip install molecule[docker] testinfra yamllint ansible-lint
                        pip install molecule-plugins[docker]
                    '''
                }
            }
        }
        
        stage('Lint') {
            steps {
                script {
                    sh '''
                        . ${VENV_PATH}/bin/activate
                        yamllint nginx_role/
                        ansible-lint nginx_role/
                    '''
                }
            }
        }
        
        stage('Test') {
            steps {
                script {
                    sh '''
                        . ${VENV_PATH}/bin/activate
                        cd nginx_role
                        molecule test
                    '''
                }
            }
        }
    }
    
    post {
        always {
            script {
                sh '''
                    . ${VENV_PATH}/bin/activate || true
                    cd nginx_role || true
                    molecule cleanup || true
                    molecule destroy || true
                '''
            }
        }
    }
}

Verify your setup

Run these commands to verify that your Molecule testing environment is working correctly.

cd ~/ansible-testing/nginx_role
source ../molecule-env/bin/activate
molecule --version
molecule list
molecule syntax
molecule create
molecule converge
molecule verify
molecule destroy
Note: Each command should complete successfully. The molecule list command should show your configured platforms (ubuntu-24, debian-12, almalinux-9).

Common issues

SymptomCauseFix
Docker permission deniedUser not in docker groupsudo usermod -aG docker $USER && newgrp docker
Systemd services fail in containersMissing privileged mode or cgroup mountsEnsure privileged: true and proper volume mounts in molecule.yml
Python module import errorsMissing dependencies or wrong virtual environmentpip install molecule-plugins[docker] and activate virtual environment
Container creation timeoutSlow Docker image downloadPre-pull images: docker pull ubuntu:24.04 debian:12 almalinux:9
Idempotence test failuresTasks not properly idempotentAdd changed_when: false or proper conditionals to tasks
TestInfra import errorsMissing test file or wrong pathEnsure test file is in molecule/default/tests/ directory

Next steps

Automated install script

Run this to automate the entire setup

#ansible #molecule #testinfra #infrastructure testing #automation testing

Need help?

Don't want to manage this yourself?

We handle infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.

Talk to an engineer