Testing Roles with Molecule
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 ofinit scenariohas been removed — the driver is configured inmolecule.yml/create.yml. - Molecule requires the role to have a Galaxy-compliant name (via
ansible_compat): if you runmolecule testwith a bare role name likebaselinethat isn't in a collection or lacks a propermeta/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.