ops-scenario


Nameops-scenario JSON
Version 8.1.0 PyPI version JSON
download
home_pageNone
SummaryPython library providing a state-transition testing API for Operator Framework charms.
upload_time2025-07-30 02:26:51
maintainerThe Charm Tech team at Canonical Ltd.
docs_urlNone
authorNone
requires_python>=3.10
licenseApache-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"
}
        
Elapsed time: 2.05308s