Docs / Automation & IaC / Test Ansible Roles with Molecule

Test Ansible Roles with Molecule

By Admin · Mar 15, 2026 · Updated Apr 23, 2026 · 170 views · 4 min read

Molecule is the standard testing framework for Ansible roles. It creates ephemeral test environments (using Docker, Vagrant, or cloud providers), runs your role against them, and verifies the results. This guide covers setting up Molecule for automated testing of your Ansible roles.

Why Test Ansible Roles?

  • Catch bugs early: Find issues before deploying to production servers
  • Ensure idempotency: Verify roles can run multiple times without unwanted changes
  • Test across platforms: Verify roles work on Ubuntu, CentOS, Debian, etc.
  • CI/CD integration: Automated testing on every code change
  • Documentation: Tests serve as examples of how the role should be used

Installation

# Install Molecule with Docker driver
pip install molecule molecule-docker ansible-lint yamllint

# Verify
molecule --version

# Initialize a new role with Molecule
molecule init role my_org.webserver

# Or add Molecule to an existing role
cd roles/webserver
molecule init scenario --driver-name docker

Molecule Configuration

# molecule/default/molecule.yml
---
dependency:
  name: galaxy
  options:
    requirements-file: requirements.yml

driver:
  name: docker

platforms:
  - name: ubuntu-24
    image: ubuntu:24.04
    pre_build_image: true
    command: /sbin/init
    privileged: true
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw
    tmpfs:
      - /run
      - /tmp

  - name: debian-12
    image: debian:12
    pre_build_image: true
    command: /sbin/init
    privileged: true
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw

  - name: rocky-9
    image: rockylinux:9
    pre_build_image: true
    command: /sbin/init
    privileged: true

provisioner:
  name: ansible
  config_options:
    defaults:
      gathering: smart
      fact_caching: jsonfile
      fact_caching_connection: /tmp/facts_cache
  inventory:
    group_vars:
      all:
        nginx_worker_processes: 2
        nginx_server_name: test.example.com

verifier:
  name: ansible

lint: |
  yamllint .
  ansible-lint

Test Playbook (Converge)

# molecule/default/converge.yml
---
- name: Converge
  hosts: all
  become: yes

  pre_tasks:
    - name: Update apt cache (Debian)
      apt:
        update_cache: yes
        cache_valid_time: 3600
      when: ansible_os_family == "Debian"

  roles:
    - role: webserver
      vars:
        nginx_worker_processes: 2
        nginx_server_name: test.example.com
        nginx_sites:
          - name: default
            server_name: test.example.com
            root: /var/www/html
            port: 80

Verification Tests

# molecule/default/verify.yml
---
- name: Verify
  hosts: all
  become: yes
  gather_facts: yes

  tasks:
    - name: Verify Nginx is installed
      package:
        name: nginx
        state: present
      check_mode: yes
      register: nginx_installed
      failed_when: nginx_installed.changed

    - name: Verify Nginx service is running
      service:
        name: nginx
        state: started
        enabled: yes
      check_mode: yes
      register: nginx_service
      failed_when: nginx_service.changed

    - name: Verify Nginx is listening on port 80
      wait_for:
        port: 80
        timeout: 10

    - name: Verify Nginx responds
      uri:
        url: http://localhost/
        status_code: [200, 301, 302]
      register: nginx_response

    - name: Verify config file exists
      stat:
        path: /etc/nginx/nginx.conf
      register: nginx_conf
      failed_when: not nginx_conf.stat.exists

    - name: Verify worker processes setting
      shell: grep "worker_processes 2" /etc/nginx/nginx.conf
      changed_when: false

    - name: Verify no default sites remain
      stat:
        path: /etc/nginx/sites-enabled/default
      register: default_site

    - name: Print test results
      debug:
        msg: "All verification tests passed!"

Running Tests

# Run full test sequence
molecule test

# The test sequence runs:
# 1. dependency    — Install role dependencies from Galaxy
# 2. lint          — Run yamllint and ansible-lint
# 3. cleanup       — Clean up any previous test resources
# 4. destroy       — Destroy any existing test instances
# 5. syntax        — Check playbook syntax
# 6. create        — Create test instances (Docker containers)
# 7. prepare       — Run preparation playbook
# 8. converge      — Run the role
# 9. idempotence   — Run the role again (verify no changes)
# 10. verify       — Run verification tests
# 11. cleanup      — Clean up
# 12. destroy      — Destroy test instances

# Run individual steps
molecule create      # Create test containers
molecule converge    # Run the role
molecule verify      # Run verification
molecule login -h ubuntu-24  # SSH into a test container
molecule destroy     # Clean up

CI/CD Integration

# .github/workflows/molecule.yml
name: Molecule Test
on:
  push:
    paths: ['roles/**']
  pull_request:
    paths: ['roles/**']

jobs:
  molecule:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        scenario: [default, multi-site]
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Install dependencies
        run: pip install molecule molecule-docker ansible-lint yamllint

      - name: Run Molecule
        run: molecule test -s ${{ matrix.scenario }}
        working-directory: roles/webserver
        env:
          PY_COLORS: '1'
          ANSIBLE_FORCE_COLOR: '1'

Multiple Test Scenarios

# Create additional scenarios
molecule init scenario multi-site --driver-name docker

# molecule/multi-site/converge.yml — Test with multiple sites
# molecule/multi-site/verify.yml — Verify multi-site configuration

# Run specific scenario
molecule test -s multi-site

# Run all scenarios
molecule test --all

Best Practices

  • Test across multiple platforms: Include Ubuntu, Debian, and RHEL-family in your test matrix
  • Always test idempotency: The idempotence step catches roles that make unnecessary changes
  • Use verify.yml for positive assertions — check that services run and ports are open
  • Run Molecule in CI: Catch issues before merging pull requests
  • Create multiple scenarios for different use cases (single site, multi-site, SSL, etc.)
  • Use molecule login to debug failing tests interactively

Was this article helpful?