How to provide relation data for charm tests¶
This guide describes how charm interface libraries should provide sample relation data for charm unit tests. By providing test data, charm libraries prevent charms from needing to know about the underlying relation data format. Instead, charms only need to know about the library’s public API and its testing API.
As the author of an interface library, you should provide a separate testing package for your library.
The testing package should expose functions that return fully populated ops.testing.Relation objects ready for use in state-transition tests.
Create a separate testing package¶
Your library’s testing package should be distributed separately from runtime library code.
The package should be defined in a testing subdirectory, like this:
interfaces/<name>/
├── src/charmlibs/interfaces/<name>/
│ └── __init__.py
├── pyproject.toml
└── testing/
├── src/charmlibs/interfaces/<name>_testing/
│ └── __init__.py
└── pyproject.toml
Use these naming conventions:
Distribution package:
charmlibs-interfaces-<hyphenated-interface-name>-testingImport package:
charmlibs.interfaces.<underscored_interface_name>_testing
Expose a testing extra and link the package versions¶
Keep the library and testing package versions identical. Releasing either one always implies releasing the other. Also, the library and its testing package should depend on exact versions of each other.
Assuming an interface library is currently on version 1.2.3, the dependencies in the library’s pyproject.toml file should look like this:
[project.optional-dependencies]
testing = ["charmlibs-interfaces-<name>-testing==1.2.3"]
[tool.uv.sources]
charmlibs-interfaces-my-interface-testing = { path = "testing", editable = true }
[dependency-groups]
unit = [
# If the library's unit tests use the testing package:
"charmlibs-interfaces-<name>[testing]",
]
The dependencies in testing/pyproject.toml file should then look like this:
[project]
dependencies = [
"charmlibs-interfaces-<name>==1.2.3",
]
[tool.uv.sources]
charmlibs-interfaces-my-interface = { path = "..", editable = true }
Implement the required testing API¶
Your testing package must provide these functions:
relation_for_providerrelation_for_requirer
Both functions must follow these rules:
The first argument must be
endpoint, the charm’s endpoint name for this relation. This argument must be required, and must be able to be provided positionally.Any additional arguments must be optional and keyword-only. Defaults should represent a typical valid relation, not just empty databags.
Return a fully formed
ops.testing.Relationobject.If the interface is request-response, include a
responseargument that defaults toTrue. If called withresponse=False, only the side of the relation that writes first should be populated.
If your interface needs helper utilities, include them in the testing package and document the intended usage.
One case where helpers are useful is if your library needs remote response data to match local request data. For example, response payloads might need to correspond to IDs, keys, or other values generated by the charm under test. In these cases, provide a testing API that makes this easy and explicit.
Test the testing package¶
The testing package should have its own test suites like any other package. It should only have unit tests, since the package itself only targets use in unit tests.
In the charmlibs monorepo, the testing package’s tests are run automatically in CI whenever the interface package or its testing package are changed.
You can run the tests locally like this:
just unit interfaces/<interface name>/testing
Example usage in charm tests¶
Charms should specify the version of the library that they need in their dependencies.
In their testing dependencies, they should require the library’s testing extra, but not specify any version constraints:
from charmlibs.interfaces import my_interface_testing
relation = my_interface_testing.relation_for_requirer("my-endpoint")
Tip
Charms should always depend on charmlibs-interfaces-<name>[testing], not directly on charmlibs-interfaces-<name>-testing.
This ensures that the testing package version always matches the library itself.