Untested roles are production experiments
A role that has only ever been run against the server it was written for works on exactly one server. The first time it meets a different distribution release, a different package version, or an already-configured host, it fails, and usually in production, because that is where the variety is.
Molecule fixes this by making role testing a normal development activity. You change a role, run one command, and a fresh instance proves that the role converges, that a second run changes nothing, and that the resulting system is what you intended. It is the testing layer in our Ansible configuration management service, sitting beside the linting pipeline.
What good looks like
- Every shared role has at least one Molecule scenario.
- The scenario matrix covers each operating system and major version you run in production.
- The idempotence check is on, and not skipped.
- Verification asserts the end state, not the existence of tasks.
- A full test run takes minutes, and developers run it locally before pushing.
How it works in practice
The test sequence
A default molecule test runs these stages: dependency, cleanup, destroy, syntax, create, prepare, converge, idempotence, side effect, verify, cleanup and destroy. The important ones:
- create starts a disposable instance.
- prepare installs what the role assumes already exists, such as Python or a package repository.
- converge applies the role.
- idempotence applies it again and fails on any change.
- verify checks the end state.
Scenario configuration
A scenario lives in molecule/default/ inside the role. A configuration using containers for two distributions:
# molecule/default/molecule.yml
driver:
name: docker
platforms:
- name: rocky9
image: geerlingguy/docker-rockylinux9-ansible
pre_build_image: true
privileged: true
command: /usr/lib/systemd/systemd
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
- name: ubuntu2204
image: geerlingguy/docker-ubuntu2204-ansible
pre_build_image: true
privileged: true
command: /lib/systemd/systemd
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
provisioner:
name: ansible
verifier:
name: ansible
The image names above are widely used community images with systemd preinstalled; we verify current tags when setting up your project and may build our own images so supply does not depend on a third party. Recent Molecule releases moved container and VM drivers into the separate molecule-plugins package, so the pinned requirements file lists it explicitly.
Converge and verify
# molecule/default/converge.yml
- name: Converge
hosts: all
become: true
roles:
- role: baseline
vars:
baseline_ssh_permit_root_login: "no"
# molecule/default/verify.yml
- name: Verify
hosts: all
gather_facts: false
tasks:
- name: Read effective sshd configuration
ansible.builtin.command: sshd -T
register: sshd_t
changed_when: false
- name: Root login is disabled
ansible.builtin.assert:
that: "'permitrootlogin no' in sshd_t.stdout"
- name: chrony is running
ansible.builtin.service_facts:
- name: Time service is active
ansible.builtin.assert:
that: "ansible_facts.services['chronyd.service'].state == 'running'"
Verification should read like a specification. If a future change breaks SSH policy, the test fails with a message about root login, not a stack trace.
Containers or virtual machines
| Aspect | Containers | VMs (Vagrant or cloud) |
|---|---|---|
| Speed | Seconds to start | Minutes |
| Fidelity | Shares host kernel | Full kernel and init |
| Good for | Packages, templates, users, services | Kernel parameters, firewalls, mounts, reboots |
| Cost | Very low | Higher, especially in CI |
We use containers for the bulk of tests and add a smaller VM scenario for roles that need it. Roles that reboot hosts or change kernel settings simply cannot be proven in a container, and we say so.
In CI
A pull request triggers a matrix job, one entry per role and per platform. We install pinned versions of ansible-core, molecule and plugins, cache pip downloads, and run only the roles whose files changed. Test output is shown in the job log, and a failing scenario can be reproduced locally with the same command. For platform choice and speed advice, see our CI/CD service and the guide to pipeline best practices.
Decisions and trade-offs
Molecule verifier or Testinfra. The Ansible verifier keeps everything in one language. Testinfra with pytest offers richer assertions for teams comfortable with Python. We follow your team’s skills.
How many platforms. Test what you run. Testing eight distributions you do not use wastes minutes on every pull request.
Unit-like tests or full-stack tests. Molecule is excellent for single roles. Testing a whole system, with a load balancer and a database, is better done in a staging environment, perhaps created with Terraform.
When not to use it. One-off playbooks that are run once, or throwaway migration scripts, do not need scenarios.
Common failure modes
- Skipping idempotence because it fails. That failure is the finding. Fix the task.
- Tests that pass trivially. Verify only checks that a file exists. Assert content.
- Image drift. A community image changes under you. We pin tags or digests and keep our own images.
- Slow suites. Tests creep to twenty minutes and are bypassed. We cap scope and parallelise.
- Cgroup and systemd quirks. Container settings differ across hosts and CI runners. We document a known-good configuration.
Delivery and handover
We add scenarios to your most important roles first, set up the CI matrix, and pair with your engineers on writing the next ones. The result is a tested role library and a contribution guide that says a role is not done until its scenario passes.
Keeping the test suite fast enough to be used
A test suite nobody waits for is a test suite nobody runs. Use lightweight containers for most scenarios and reserve full virtual machines for roles that touch kernel settings, systemd behaviour or disks. Run the quick scenarios on every pull request and the heavier ones on merge or on a nightly schedule. If a scenario regularly takes longer than the team will tolerate, split the role rather than skipping the test.
How to tell it is working
- Pull requests that break a role fail before review, with a clear assertion.
- Operating system upgrades are tested by adding one line to the platform matrix.
- Production incidents caused by role changes become rare.
- New contributors write a test before changing a role, because it is the easiest way to see their change work.
To see a scenario run against one of your own roles, contact us.