Skip to content

testing

**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**: Molecule 6.0+ / ansible-lint 6.0+ / ansible-core 2.14+ **Generated**: 2026-04-04 — verify against current Molecule and ansible-lint documentation

From plugin
vexjoy-agent
413198 skills198 agents10 commands86 hooks
Install
$ npx -y skills add notque/vexjoy-agent --agent claude-code

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**: Molecule 6.0+ / ansible-lint 6.0+ / ansible-core 2.14+ **Generated**: 2026-04-04 — verify against current Molecule and ansible-lint documentation

Agent definition

testing.md

Ansible Testing Reference

> **Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns > **Version range**: Molecule 6.0+ / ansible-lint 6.0+ / ansible-core 2.14+ > **Generated**: 2026-04-04 — verify against current Molecule and ansible-lint documentation

---

Overview

Ansible testing has three layers that catch different failure classes: ansible-lint catches code quality and style violations before execution, check mode (`--check`) previews changes without applying them, and Molecule provides full scenario-based integration testing with real containers. Most automation breakage happens because only manual testing was done — automating these three layers catches 90% of issues before production.

---

Pattern Table

| Tool | Version | Use When | What It Catches | |------|---------|----------|-----------------| | `ansible-lint` | 6.0+ | Pre-commit, CI/CD | Style, deprecated syntax, security patterns | | `--check` mode | all | Pre-production run | Which tasks would change (no side effects) | | `--diff` mode | all | With `--check` | Exact file/template changes | | Molecule `verify` | 6.0+ | Post-convergence | State assertions on converged instance | | `idempotency` scenario | 6.0+ | Full role testing | Tasks that always report `changed` |

---

Correct Patterns

Run ansible-lint in CI with Explicit Rules

# .github/workflows/lint.yml
- name: Run ansible-lint
  uses: ansible/ansible-lint-action@v6
  with:
    args: "--profile production"

# .ansible-lint — project-level config
profile: production
exclude_paths:
  - molecule/
warn_list:
  - yaml[truthy]
skip_list:
  - 'no-changed-when'  # Only if you've deliberately handled changed_when

**Why**: The `production` profile enforces a stricter rule set than default. Without explicit profile selection, CI may pass with rules that flag security issues.

---

Molecule Scenario Structure for Role Testing

roles/myrole/
└── molecule/
    └── default/
        ├── molecule.yml       # Platform/driver config
        ├── converge.yml       # The playbook to test
        ├── verify.yml         # State assertions
        └── prepare.yml        # Pre-test setup (optional)
# molecule/default/molecule.yml
---
driver:
  name: docker
platforms:
  - name: instance
    image: geerlingguy/docker-ubuntu2204-ansible:latest
    pre_build_image: true

provisioner:
  name: ansible
  lint:
    name: ansible-lint

verifier:
  name: ansible
# molecule/default/verify.yml — assert actual state, not just "it ran"
---
- name: Verify
  hosts: all
  tasks:
    - name: Check nginx is running
      service_facts:

    - name: Assert nginx service is active
      assert:
        that:
          - "'nginx' in services"
          - "services['nginx'].state == 'running'"
        fail_msg: "nginx service is not running"

    - name: Check config file exists and has correct content
      stat:
        path: /etc/nginx/sites-enabled/default
      register: nginx_conf

    - name: Assert config file exists
      assert:
        that: nginx_conf.stat.exists

**Why**: `verify.yml` that only checks "did the task run" provides false confidence. Assert actual state: service running, file exists and has correct permissions, port is listening.

---

Idempotency Test Pattern

# molecule/default/molecule.yml — add idempotency check
provisioner:
  name: ansible
  playbooks:
    converge: converge.yml
  lint:
    name: ansible-lint

# Or run manually:
# molecule converge && molecule idempotency
# Manual idempotency check — second run should report 0 changed tasks
ansible-playbook site.yml --check 2>&1 | grep -E "changed=|failed="
# Expected: changed=0 failed=0

**Why**: A role that reports `changed` on every run breaks pipeline assumptions (e.g., "if nothing changed, skip downstream steps"). Idempotency failures also indicate state corruption on repeated runs.

---

Pattern Catalog

Set changed_when on All command/shell Tasks

**Detection**:

# Find command/shell tasks missing changed_when
grep -rn -A5 "^\s*\(command\|shell\):" roles/ playbooks/ \
  | grep -v "changed_when\|register\|when:"

# Or with ripgrep
rg -t yaml '^\s+(command|shell):' roles/ playbooks/ -A5 \
  | grep -B3 -v "changed_when"

**Signal**:

- name: Check disk usage
  command: df -h
  register: disk_result
  # Missing: changed_when: false

**Why this matters**: `command` and `shell` always report `changed` unless told otherwise. This breaks idempotency checks (idempotency scenario always fails), makes `--check` output noisy, and triggers handlers incorrectly.

**Preferred action**:

- name: Check disk usage
  command: df -h
  register: disk_result
  changed_when: false  # Read-only command, never changes state

- name: Run migration script
  command: /app/bin/migrate up
  register: migration_result
  changed_when: "'No migrations' not in migration_result.stdout"
  # Only reports changed when migrations actually ran

---

Write Meaningful Assertions in verify.yml

**Detection**:

# Find verify.yml files without assert tasks
grep -rL "assert:" molecule/*/verify.yml

# Find verify.yml files with only ping/gather_facts
grep -rn "tasks:" molecule/*/verify.yml -A5 \
  | grep -v "assert\|stat\|uri\|service_facts\|command"

**Signal**:

# molecule/default/verify.yml — provides zero value
---
- name: Verify
  hosts: all
  tasks:
    - name: Check connectivity
      ping:

**Why this matters**: Ping passing means the container is alive, not that the role worked. A role that fails to install nginx will still pass this verify.

**Preferred action**:

---
- name: Verify
  hosts: all
  gather_facts: false
  tasks:
    - name: Check nginx listening on port 80
      wait_for:
        port: 80
        timeout: 5

    - name: Check nginx config syntax
      command: nginx -t
      changed_when: false

    - name: Verify nginx config file d
Read more
Ships withvexjoy-agent

Essays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.

Get the whole plugin, auto-invoked