Name | ops-scenario JSON |
Version |
8.1.0
JSON |
| download |
home_page | None |
Summary | Python library providing a state-transition testing API for Operator Framework charms. |
upload_time | 2025-07-30 02:26:51 |
maintainer | The Charm Tech team at Canonical Ltd. |
docs_url | None |
author | None |
requires_python | >=3.10 |
license | Apache-2.0 |
keywords |
juju
test
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# ops-scenario, the unit testing framework for ops charms
`ops-scenario` is a Python library that provides state-transition testing for
[Ops](https://ops.readthedocs.io) charms. These tests are higher level than
typical unit tests, but run at similar speeds and are the recommended approach
for testing charms within requiring a full [Juju](https://juju.is) installation.
Test are written in the arrange/act/assert pattern, arranging an object
representing the current Juju state, acting by emulating an event from Juju, and
then asserting on the (simulated) output Juju state.
## Writing tests
Here's a test that verifies that a unit is active after the `start` event, with a very minimal initial state:
```python
from ops import testing
# 'src/charm.py' typically contains the charm class.
from charm import MyCharm
def test_start():
ctx = testing.Context(MyCharm)
state_in = testing.State()
state_out = ctx.run(ctx.on.start(), state_in)
assert state_out.unit_status == testing.ActiveStatus()
```
More comprehensive tests will include relations, containers, secrets, and other
components in the input state, and assertions against both the output state and
the context. The 'act' stage remains a simple single call, although additional
arguments may be required for the event, such as the relation or container that
triggered it. For example:
```python
import pytest
from ops import testing
from charm import MyCharm
@pytest.mark.parametrize(
'leader',
[pytest.param(True, id='leader'), pytest.param(False, id='non-leader')],
)
def test_(leader: bool):
# Arrange:
ctx = testing.Context(MyCharm)
relation = testing.Relation('db', local_app_data={'hostname': 'example.com'})
peer_relation = testing.PeerRelation('peer')
container = testing.Container('workload', can_connect=True)
relation_secret = testing.Secret({'certificate': 'xxxxxxxx'})
user_secret = testing.Secret({'username': 'admin', 'password': 'xxxxxxxx'})
config = {'port': 8443, 'admin-credentials': 'secret:1234'}
state_in = testing.State(
leader=leader,
config=config,
relations={relation, peer_relation},
containers={container},
secrets={relation_secret, user_secret},
unit_status=testing.BlockedStatus(),
workload_version='1.0.1',
)
# Act:
state_out = ctx.run(ctx.on.relation_changed(relation), state_in)
# Assert:
assert testing.JujuLogLine(level='INFO', message='Distributing secret.') in ctx.juju_log
peer_relation_out = state_out.get_relation(peer_relation.id)
assert peer_relation_out.peers_data[0] == {'secret_id': relation_secret.id}
```
You don't have to use pytest for your charm tests, but it's what we recommend.
pytest's `assert`-based approach is a straightforward way to write tests, and
its fixtures are helpful for structuring setup and teardown.
## Installation
For charm tests, install the testing framework by adding the `testing` extra of
ops in your unit testing environment. For example, in `pyproject.toml`:
```toml
[dependency-groups]
test = ['ops[testing]<4.0']
```
Ops checks if `ops-scenario` is installed, and, if so, makes the classes
(such as `Context`, `State`, and `Relation`) available in the `ops.testing`
namespace. Use `from ops import testing` rather than importing the `scenario`
package.
`ops-scenario` supports the same platforms and Python versions as ops itself.
## Documentation
* To get started, work through our ['Write your first Kubernetes charm' tutorial](https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/create-a-minimal-kubernetes-charm.html#write-unit-tests-for-your-charm), following the instructions for adding
unit tests at the end of each chapter.
* When you need to write a test that involves specific ops functionality,
refer to our [how-to guides](https://ops.readthedocs.io/en/latest/howto/index.html)
which all conclude with examples of tests of the ops functionality.
* Use our extensive [reference documentation](https://ops.readthedocs.io/en/latest/reference/ops-testing.html#ops-testing) when you need to know how each `testing` object works. These
docs are also available via the standard Python `help()` functionality and in
your IDE.
[**Read the full documentation**](https://ops.readthedocs.io/)
## Community
`ops-scenario` is a member of the Charming family. It's an open source project
that warmly welcomes community contributions, suggestions, fixes and
constructive feedback.
* Read our [code of conduct](https://ubuntu.com/community/ethos/code-of-conduct):
As a community we adhere to the Ubuntu code of conduct.
* [Get support](https://discourse.charmhub.io/): Discourse is the go-to forum
for all Ops-related discussions, including around testing.
* Join our [online chat](https://matrix.to/#/#charmhub-charmdev:ubuntu.com):
Meet us in the #charmhub-charmdev channel on Matrix.
* [Report bugs](https://github.com/canonical/operator/issues): We want to know
about the problems so we can fix them.
* [Contribute docs](https://github.com/canonical/operator/blob/main/HACKING.md#contributing-documentation):
Get started on GitHub.
## Contributing and developing
Anyone can contribute to ops and `ops-scenario`. It's best to start by
[opening an issue](https://github.com/canonical/operator/issues) with a clear
description of the problem or feature request, but you can also
[open a pull request](https://github.com/canonical/operator/pulls) directly.
Read our [guide](./CONTRIBUTING.md) for more details on how to work on and
contribute to `ops-scenario`.
Currently, releases of `ops-scenario` are done in lockstep with releases of ops
itself, with matching minor and bugfix release numbers. The ops documentation
outlines how to create a new release.
Raw data
{
"_id": null,
"home_page": null,
"name": "ops-scenario",
"maintainer": "The Charm Tech team at Canonical Ltd.",
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "juju, test",
"author": null,
"author_email": "Pietro Pasotti <pietro.pasotti@canonical.com>",
"download_url": "https://files.pythonhosted.org/packages/c4/c9/260d8c7a62894e80ba1e16857d7e4ab539b7559fc5d28f0636909570652e/ops_scenario-8.1.0.tar.gz",
"platform": null,
"description": "# ops-scenario, the unit testing framework for ops charms\n\n`ops-scenario` is a Python library that provides state-transition testing for\n[Ops](https://ops.readthedocs.io) charms. These tests are higher level than\ntypical unit tests, but run at similar speeds and are the recommended approach\nfor testing charms within requiring a full [Juju](https://juju.is) installation.\n\nTest are written in the arrange/act/assert pattern, arranging an object\nrepresenting the current Juju state, acting by emulating an event from Juju, and\nthen asserting on the (simulated) output Juju state.\n\n## Writing tests\n\nHere's a test that verifies that a unit is active after the `start` event, with a very minimal initial state:\n\n```python\nfrom ops import testing\n\n# 'src/charm.py' typically contains the charm class.\nfrom charm import MyCharm\n\ndef test_start():\n ctx = testing.Context(MyCharm)\n state_in = testing.State()\n state_out = ctx.run(ctx.on.start(), state_in)\n assert state_out.unit_status == testing.ActiveStatus()\n```\n\nMore comprehensive tests will include relations, containers, secrets, and other\ncomponents in the input state, and assertions against both the output state and\nthe context. The 'act' stage remains a simple single call, although additional\narguments may be required for the event, such as the relation or container that\ntriggered it. For example:\n\n```python\nimport pytest\nfrom ops import testing\n\nfrom charm import MyCharm\n\n@pytest.mark.parametrize(\n 'leader',\n [pytest.param(True, id='leader'), pytest.param(False, id='non-leader')],\n)\ndef test_(leader: bool):\n # Arrange:\n ctx = testing.Context(MyCharm)\n relation = testing.Relation('db', local_app_data={'hostname': 'example.com'})\n peer_relation = testing.PeerRelation('peer')\n container = testing.Container('workload', can_connect=True)\n relation_secret = testing.Secret({'certificate': 'xxxxxxxx'})\n user_secret = testing.Secret({'username': 'admin', 'password': 'xxxxxxxx'})\n config = {'port': 8443, 'admin-credentials': 'secret:1234'}\n state_in = testing.State(\n leader=leader,\n config=config,\n relations={relation, peer_relation},\n containers={container},\n secrets={relation_secret, user_secret},\n unit_status=testing.BlockedStatus(),\n workload_version='1.0.1',\n )\n\n # Act:\n state_out = ctx.run(ctx.on.relation_changed(relation), state_in)\n\n # Assert:\n assert testing.JujuLogLine(level='INFO', message='Distributing secret.') in ctx.juju_log\n peer_relation_out = state_out.get_relation(peer_relation.id)\n assert peer_relation_out.peers_data[0] == {'secret_id': relation_secret.id}\n```\n\nYou don't have to use pytest for your charm tests, but it's what we recommend.\npytest's `assert`-based approach is a straightforward way to write tests, and\nits fixtures are helpful for structuring setup and teardown.\n\n## Installation\n\nFor charm tests, install the testing framework by adding the `testing` extra of\nops in your unit testing environment. For example, in `pyproject.toml`:\n\n```toml\n[dependency-groups]\ntest = ['ops[testing]<4.0']\n```\n\nOps checks if `ops-scenario` is installed, and, if so, makes the classes\n(such as `Context`, `State`, and `Relation`) available in the `ops.testing`\nnamespace. Use `from ops import testing` rather than importing the `scenario`\npackage.\n\n`ops-scenario` supports the same platforms and Python versions as ops itself.\n\n## Documentation\n\n * To get started, work through our ['Write your first Kubernetes charm' tutorial](https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/create-a-minimal-kubernetes-charm.html#write-unit-tests-for-your-charm), following the instructions for adding\n unit tests at the end of each chapter.\n * When you need to write a test that involves specific ops functionality,\n refer to our [how-to guides](https://ops.readthedocs.io/en/latest/howto/index.html)\n which all conclude with examples of tests of the ops functionality.\n * Use our extensive [reference documentation](https://ops.readthedocs.io/en/latest/reference/ops-testing.html#ops-testing) when you need to know how each `testing` object works. These\n docs are also available via the standard Python `help()` functionality and in\n your IDE.\n\n[**Read the full documentation**](https://ops.readthedocs.io/)\n\n## Community\n\n`ops-scenario` is a member of the Charming family. It's an open source project\nthat warmly welcomes community contributions, suggestions, fixes and\nconstructive feedback.\n\n* Read our [code of conduct](https://ubuntu.com/community/ethos/code-of-conduct):\n As a community we adhere to the Ubuntu code of conduct.\n* [Get support](https://discourse.charmhub.io/): Discourse is the go-to forum\n for all Ops-related discussions, including around testing.\n* Join our [online chat](https://matrix.to/#/#charmhub-charmdev:ubuntu.com):\n Meet us in the #charmhub-charmdev channel on Matrix.\n* [Report bugs](https://github.com/canonical/operator/issues): We want to know\n about the problems so we can fix them.\n* [Contribute docs](https://github.com/canonical/operator/blob/main/HACKING.md#contributing-documentation):\n Get started on GitHub.\n\n## Contributing and developing\n\nAnyone can contribute to ops and `ops-scenario`. It's best to start by\n[opening an issue](https://github.com/canonical/operator/issues) with a clear\ndescription of the problem or feature request, but you can also\n[open a pull request](https://github.com/canonical/operator/pulls) directly.\n\nRead our [guide](./CONTRIBUTING.md) for more details on how to work on and\ncontribute to `ops-scenario`.\n\nCurrently, releases of `ops-scenario` are done in lockstep with releases of ops\nitself, with matching minor and bugfix release numbers. The ops documentation\noutlines how to create a new release.\n",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "Python library providing a state-transition testing API for Operator Framework charms.",
"version": "8.1.0",
"project_urls": {
"Bug Tracker": "https://github.com/canonical/operator/issues",
"Homepage": "https://github.com/canonical/operator"
},
"split_keywords": [
"juju",
" test"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "767b7dd20a749e4c5d2317344a803c5c815af48a0d39b05ea5b2af1c81bbbf3f",
"md5": "6d92b94d66a41fab6555fa73048b82af",
"sha256": "740df042474bdfed6836be184c9f9cc1f92af7a3a9d2ab234276e4291aa8312c"
},
"downloads": -1,
"filename": "ops_scenario-8.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "6d92b94d66a41fab6555fa73048b82af",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 63745,
"upload_time": "2025-07-30T02:26:46",
"upload_time_iso_8601": "2025-07-30T02:26:46.383281Z",
"url": "https://files.pythonhosted.org/packages/76/7b/7dd20a749e4c5d2317344a803c5c815af48a0d39b05ea5b2af1c81bbbf3f/ops_scenario-8.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "c4c9260d8c7a62894e80ba1e16857d7e4ab539b7559fc5d28f0636909570652e",
"md5": "b8fd6bfb2ee198dbfe3feb4f8f637703",
"sha256": "6b199d882710f3950348a885ff3b1d371806cb678f58e2c077b33029c6db9857"
},
"downloads": -1,
"filename": "ops_scenario-8.1.0.tar.gz",
"has_sig": false,
"md5_digest": "b8fd6bfb2ee198dbfe3feb4f8f637703",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 109375,
"upload_time": "2025-07-30T02:26:51",
"upload_time_iso_8601": "2025-07-30T02:26:51.599409Z",
"url": "https://files.pythonhosted.org/packages/c4/c9/260d8c7a62894e80ba1e16857d7e4ab539b7559fc5d28f0636909570652e/ops_scenario-8.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-30 02:26:51",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "canonical",
"github_project": "operator",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"tox": true,
"lcname": "ops-scenario"
}