Unit Testing Guide
Relevant source files
The following files were used as context for generating this wiki page:
Unit tests verify individual functions, classes, and logic within the opsmill.infrahub collection by using mocked dependencies. These tests run without an external Infrahub instance or network connectivity, ensuring fast execution and isolated validation of state transitions. Sources: dev/constitution.md:65-66, dev/guidelines/testing.md:12-14
Overview and Purpose
Unit testing serves as the second tier in the collection's three-tier test strategy, following sanity tests and preceding integration tests. Sources: dev/constitution.md:63-68. The primary goals of unit testing in this project include:
- State Transition Coverage: Validating all possible object states including create, update, delete, no-change, and error scenarios. Sources: dev/constitution.md:66-67
- Idempotency Verification: Ensuring that running a module twice with identical parameters yields
changed: false. Sources: dev/constitution.md:37-38 - Check Mode Support: Confirming that mutations are skipped when
module.check_modeis enabled. Sources: dev/constitution.md:39-40 - SDK Abstraction Validation: Testing logic that interacts with the
InfrahubclientWrapperwithout requiring a live API. Sources: dev/constitution.md:46-47
Testing Architecture
The unit testing framework relies on pytest and extensive mocking of the Infrahub SDK. Sources: dev/guidelines/testing.md:74-75
Component Interaction Flow
The following diagram illustrates how unit tests isolate the module logic from external dependencies using mocks:
This diagram shows the isolation of Module Logic from the real SDK and Ansible environment via mocking. Sources: dev/guidelines/testing.md:74-106
Directory Structure
Unit tests follow a directory structure that mirrors the plugins/ directory:
tests/unit/plugins/module_utils/: Tests for shared utilities likeinfrahub_utils.py.tests/unit/plugins/modules/: Tests for individual module stubs.
Sources: dev/guidelines/testing.md:57-65
Tooling and Configuration
The project uses uv for dependency management and pytest as the primary test runner. Sources: AGENTS.md:18-24, pyproject.toml:35-43
Key Dependencies
| Dependency | Purpose |
|---|---|
pytest | Main testing framework |
pytest-mock | Provides the mocker fixture for easier patching |
pytest-xdist | Enables parallel test execution via invoke tests-unit -n auto |
mock | Backport of the standard library mock module |
pytest-httpx | Mocks HTTP responses for tests involving direct API calls |
| Sources: pyproject.toml:35-45, tests/AGENTS.md:67-73 |
Pytest Configuration
The pyproject.toml file defines the test paths and pythonpath to ensure modules are importable during tests.
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]
Sources: dev/guidelines/testing.md:68-72, pyproject.toml:50-55
Writing Unit Tests
Mocking the SDK
Because the InfrahubclientWrapper is mandatory for all API access, tests must patch InfrahubClientSync within the infrahub_utils module. Sources: dev/constitution.md:46-47, dev/guidelines/testing.md:76-80
from unittest.mock import MagicMock, patch
@patch("ansible_collections.opsmill.infrahub.plugins.module_utils.infrahub_utils.InfrahubClientSync")
def test_wrapper_initialization(mock_client_class):
mock_client_class.return_value = MagicMock()
# Test initialization logic here
Sources: dev/guidelines/testing.md:78-90
Testing Modules
Module tests mock the ansible.module_utils.basic.AnsibleModule to control input parameters and capture exit_json or fail_json calls. Sources: dev/guidelines/testing.md:92-94
- Define Parameters: Provide a dictionary representing
module.params. - Setup Mocks: Patch the
InfrahubClientSyncand define return values for API calls. - Assert Behavior: Verify that
changedis reported correctly and that the output matches expectations.
Sources: dev/guidelines/testing.md:96-106
Execution Procedures
Tests are executed via invoke tasks or direct pytest commands inside Docker containers. Sources: dev/guidelines/testing.md:20-22, tests/AGENTS.md:20-22
Common Commands
| Action | Command |
|---|---|
| Run all unit tests | invoke tests-unit |
| Run tests for a specific file | uv run pytest tests/unit/plugins/modules/test_node.py |
| Run tests in parallel | uv run pytest tests/unit/ -n auto |
| Verify test collection | uv run pytest tests/unit --collect-only -q |
| Sources: dev/guidelines/testing.md:20-22, tests/AGENTS.md:55-59, dev/guidelines/testing.md:120-122 |
Execution Environment
Unit tests run in the unit service defined in docker-compose.yml. This service uses the unittests multi-stage build in the Dockerfile, which installs all Python dependencies via uv. Sources: dev/guidelines/testing.md:37-44, tests/AGENTS.md:20-22
Best Practices
- Verify Collection: Always use
--collect-onlyafter adding a new test to ensure the runner sees it. Sources: dev/guidelines/testing.md:120-122 - Mutation-Check: Before finalizing a test, temporarily break the code logic to confirm the test fails as expected. Sources: dev/guidelines/testing.md:126-130
- SDK Guard: Ensure all tests respect the
HAS_INFRAHUBCLIENTguard to prevent failures when the SDK is not installed in the environment. Sources: dev/constitution.md:48-49, dev/guides/debugging-sanity-failures.md:35-42 - No Hardcoded Credentials: Use mocked environment variables or fallback values instead of hardcoding API tokens. Sources: dev/constitution.md:52-53
Summary
Unit testing provides a high-speed verification layer for the opsmill.infrahub collection. By utilizing pytest and mocking the Infrahub SDK, developers can validate complex state management, idempotency, and check mode behavior without the overhead of a live Infrahub instance. All unit tests must pass in the CI pipeline before merging to the develop branch. Sources: dev/constitution.md:65-71, dev/guidelines/testing.md:12-14, AGENTS.md:79-81