{"id":23134,"library":"ops-scenario","title":"ops-scenario","description":"A Python library for state-transition testing of Juju charms built with the Operator Framework. Provides the `ops.testing` module with the `Context` API to simulate charm lifecycle events and validate state changes. Current version: 8.7.0 (2026). Requires Python >=3.10. Active development with frequent releases.","status":"active","version":"8.7.0","language":"python","source_language":"en","source_url":"https://github.com/canonical/operator","tags":["juju","charm","testing","state-transition","operator-framework"],"install":[{"cmd":"pip install ops[testing]","lang":"bash","label":"Standard install"},{"cmd":"pip install ops-scenario","lang":"bash","label":"Alternative install (same package)"}],"dependencies":[{"reason":"Core ops library; ops-scenario is part of the same project.","package":"ops","optional":false},{"reason":"Used for YAML handling in charm metadata and actions.","package":"pyyaml","optional":true},{"reason":"Recommended test runner; not strictly required but commonly used.","package":"pytest","optional":true}],"imports":[{"note":"'scenario' is not a valid module; the correct import is from ops.testing.","wrong":"from scenario import Context","symbol":"Context","correct":"from ops.testing import Context"},{"note":"State is part of ops.testing, not a separate ops.scenario module.","wrong":"from ops.scenario import State","symbol":"State","correct":"from ops.testing import State"},{"note":"CharmSpec is available in ops.testing; no common wrong import.","wrong":"","symbol":"CharmSpec","correct":"from ops.testing import CharmSpec"}],"quickstart":{"code":"import os\nimport yaml\nfrom ops.charm import CharmBase\nfrom ops.testing import Context, State, CharmSpec\n\nclass MyCharm(CharmBase):\n    def __init__(self, *args):\n        super().__init__(*args)\n        self.framework.observe(self.on.install, self._on_install)\n\n    def _on_install(self, event):\n        self.unit.status.set_active()\n\ndef test_charm():\n    metadata = yaml.safe_load('''\nname: my-charm\nsummary: test\nseries: [focal]\n''')\n    spec = CharmSpec(MyCharm, meta=metadata)\n    ctx = Context(spec, meta={'name': 'my-charm'}, actions=None, config=None)\n    state = ctx.run('install', State())\n    assert state.unit_status.name == 'active'\n    print('Test passed')\n\nif __name__ == '__main__':\n    test_charm()","lang":"python","description":"Minimal test: creates a charm, runs the install event, asserts active status."},"warnings":[{"fix":"Use string event names: ctx.run('install', State()).","message":"State-transition tests ('run' with events) require Juju event names as strings (e.g. 'install', 'config-changed', 'update-status'). Using the event type class (e.g. InstallEvent) will fail.","severity":"gotcha","affected_versions":">=3.0.0"},{"fix":"Replace 'from scenario import ...' with 'from ops.testing import ...'.","message":"In ops-scenario 8.0.0, the module was renamed from 'scenario' to 'ops.testing'. Old imports from 'scenario' will break.","severity":"breaking","affected_versions":"8.0.0+"},{"fix":"Use explicit keyword args: CharmSpec(MyCharm, meta={...}, actions={...}, config={...}).","message":"The 'CharmSpec' constructor argument 'meta' is deprecated; use keyword arguments matching the fields of CharmSpec (like meta, actions, config).","severity":"deprecated","affected_versions":">=7.0.0"},{"fix":"Always create a fresh State for each run, or use copy.deepcopy if you need to reuse.","message":"When using 'ctx.run', the State object is not automatically deep-copied. Mutations to the returned State can affect subsequent runs if the same object is reused.","severity":"gotcha","affected_versions":">=3.0.0"},{"fix":"Ensure both come from the same release: pip install 'ops[testing]' handles this automatically.","message":"The 'ops' library and 'ops-scenario' must be in sync; using mismatched versions can cause ImportError or AttributeError.","severity":"gotcha","affected_versions":">=3.0.0"}],"env_vars":null,"last_verified":"2026-05-01T00:00:00.000Z","next_check":"2026-07-30T00:00:00.000Z","problems":[{"fix":"Install with 'pip install ops[testing]' and use 'from ops.testing import Context'.","cause":"ops-scenario not installed or incompatible version; Context moved to ops.testing in v8.","error":"ImportError: cannot import name 'Context' from 'ops.testing'"},{"fix":"Upgrade to latest ops: 'pip install --upgrade ops[testing]'.","cause":"Using an older version (pre-3.0) where State was named ScenarioState or not available.","error":"AttributeError: module 'ops.testing' has no attribute 'State'"},{"fix":"Use the standard pytest fixture decorator: @pytest.fixture, not ops.testing.fixture.","cause":"Incorrectly using a fixture decorator with arguments unsupported by pytest (e.g., from ops.testing.fixture).","error":"pytest.fixture() got an unexpected keyword argument 'scope'"},{"fix":"Use CharmSpec(MyCharm, meta={...}) is correct; if error persists, check version >=7.0 and use: CharmSpec(MyCharm, meta={...}) still works. This error may be from mixing versions.","cause":"CharmSpec constructor changed; 'meta' is no longer a keyword argument (use positional or other keys).","error":"TypeError: __init__() got an unexpected keyword argument 'meta'"}],"ecosystem":"pypi","meta_description":null,"install_score":null,"install_tag":null,"quickstart_score":null,"quickstart_tag":null}