Testing Roles with Molecule

K
Kai··5 min read

The role you wrote (Article 8) — how do you make sure it's correct before applying it to a real fleet? Testing on a production server is dangerous. Molecule solves this: it stands up a throwaway environment (usually a container), applies the role to it, checks idempotency, and verifies the result — automatically, repeatably, and CI-ready. This is the quality best practice for shared roles.

Why test roles

An untested role easily hides bugs: it runs on your machine but breaks on a different OS, or it isn't idempotent (running it a second time still reports changed — wrong). Molecule automates these checks in a clean environment, re-runnable every time you edit the role. For a shared role (a collection — Article 9), molecule is practically mandatory.

Installation

pip install molecule "molecule-plugins[docker]" docker
molecule --version
molecule 26.4.0 using python 3.11
    ansible:2.19.10

Molecule needs a driver to build the throwaway environment — the most common is docker (light, fast, clean containers). You need Docker running on the machine (Docker series).

Creating a scenario

Inside the role's directory, create a test scenario:

cd roles/baseline
molecule init scenario
INFO  default ➜ init: Initialized scenario in roles/baseline/molecule/default successfully.

It creates the molecule/default/ directory:

molecule/default/
├── molecule.yml      # config: driver, platform (image), test sequence
├── create.yml        # how to build the throwaway environment
├── converge.yml      # playbook that applies the ROLE under test
├── verify.yml        # check the result (assertions)
└── destroy.yml       # tear down the environment

The two files you'll edit most:

converge.yml — applies the role under test to the throwaway environment:

---
- name: Converge
  hosts: all
  tasks:
    - name: Apply the baseline role
      ansible.builtin.include_role:
        name: baseline

verify.yml — checks the role did the right thing (like a unit test):

---
- name: Verify
  hosts: all
  tasks:
    - name: The config file must exist
      ansible.builtin.stat:
        path: /etc/baseline.conf
      register: conf

    - name: Assert the file is present
      ansible.builtin.assert:
        that:
          - conf.stat.exists
        fail_msg: "Missing /etc/baseline.conf — the role is broken"

molecule.yml configures the driver and platform (the container image to test on, e.g. an Ubuntu/Fedora image with Ansible available). You test the role on the exact target OS.

The test lifecycle: molecule test

The main command is molecule test, which runs a full test sequence:

molecule test

The default sequence (from molecule.yml):

   dependency → create → converge → idempotence → verify → destroy
        │          │         │           │            │         │
   pull needed   build    apply ROLE   RUN converge   run      delete the
   role/         container into it     AGAIN,         verify   throwaway
   collection               (1st time) MUST changed=0 (assert) environment

The most important step is idempotence: Molecule runs converge a second time and requires changed=0. If your role isn't idempotent (Article 5) — for example using command without creates: — this step fails, exposing a bug that's hard to spot by eye. This is molecule's biggest value: it forces the role to be truly idempotent.

When developing, you usually run individual steps instead of the whole sequence:

molecule create      # build the container once
molecule converge    # apply the role (run repeatedly while editing the role)
molecule verify      # run the checks
molecule login       # SSH into the container to inspect manually
molecule destroy     # tear down

The converge → edit role → converge loop is very fast (the container is already up), well suited to development.

Version caveats (seen in practice)

Molecule changes quite a bit between major versions. With Molecule 26.x (the latest at the time of writing):

  • The -d <driver> flag of init scenario has been removed — the driver is configured in molecule.yml/create.yml.
  • Molecule requires the role to have a Galaxy-compliant name (via ansible_compat): if you run molecule test with a bare role name like baseline that isn't in a collection or lacks a proper meta/main.yml, you hit the error "Computed fully qualified role name ... does not follow current galaxy requirements".

This is actually a best-practice lesson (Article 15): a serious role should have a meta/main.yml declaring galaxy_info (author, license, namespace) and ideally live inside a collection. The fix: fill in a complete meta/main.yml, or place the role in a collection structure (Article 9), or pin an older molecule version if you need to match older docs. Always read molecule --version and the docs for the right version — this is a fast-evolving tool.

Molecule in CI

The real power of molecule is running it automatically in CI (GitHub Actions...) every time you edit a role: push code → CI runs molecule test → you immediately know whether the role is still idempotent and correct, across multiple OSes. Combine it with ansible-lint (Article 15) to catch style issues. This is the workflow behind the quality roles on Galaxy.

🧹 Cleanup

Molecule auto-destroys the container after test. The sample scenario is in the nghiadaulau/ansible-series repo, directory 14-molecule.

Wrap-up

Molecule tests roles automatically in a throwaway environment (a container via the docker driver). molecule init scenario creates a scenario (converge.yml applies the role, verify.yml checks it). molecule test runs the sequence create → converge → idempotence → verify → destroy — where idempotence (running converge a second time, requiring changed=0) is the most valuable check, forcing the role to be truly idempotent. For development, use molecule converge/verify step by step. Note that molecule evolves quickly and recent versions require roles to follow the Galaxy convention (so have a meta/main.yml/collection). Run molecule + ansible-lint in CI for quality roles.

Now that we can write, extend, optimize, and test — Article 15 pulls it all together into best practices: project structure, role design, naming conventions, and ansible-lint.