ops-scenario
raw JSON → 8.7.0 verified Fri May 01 auth: no python
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.
pip install ops[testing] Common errors
error ImportError: cannot import name 'Context' from 'ops.testing' ↓
cause ops-scenario not installed or incompatible version; Context moved to ops.testing in v8.
fix
Install with 'pip install ops[testing]' and use 'from ops.testing import Context'.
error AttributeError: module 'ops.testing' has no attribute 'State' ↓
cause Using an older version (pre-3.0) where State was named ScenarioState or not available.
fix
Upgrade to latest ops: 'pip install --upgrade ops[testing]'.
error pytest.fixture() got an unexpected keyword argument 'scope' ↓
cause Incorrectly using a fixture decorator with arguments unsupported by pytest (e.g., from ops.testing.fixture).
fix
Use the standard pytest fixture decorator: @pytest.fixture, not ops.testing.fixture.
error TypeError: __init__() got an unexpected keyword argument 'meta' ↓
cause CharmSpec constructor changed; 'meta' is no longer a keyword argument (use positional or other keys).
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.
Warnings
gotcha 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. ↓
fix Use string event names: ctx.run('install', State()).
breaking In ops-scenario 8.0.0, the module was renamed from 'scenario' to 'ops.testing'. Old imports from 'scenario' will break. ↓
fix Replace 'from scenario import ...' with 'from ops.testing import ...'.
deprecated The 'CharmSpec' constructor argument 'meta' is deprecated; use keyword arguments matching the fields of CharmSpec (like meta, actions, config). ↓
fix Use explicit keyword args: CharmSpec(MyCharm, meta={...}, actions={...}, config={...}).
gotcha 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. ↓
fix Always create a fresh State for each run, or use copy.deepcopy if you need to reuse.
gotcha The 'ops' library and 'ops-scenario' must be in sync; using mismatched versions can cause ImportError or AttributeError. ↓
fix Ensure both come from the same release: pip install 'ops[testing]' handles this automatically.
Install
pip install ops-scenario Imports
- Context wrong
from scenario import Contextcorrectfrom ops.testing import Context - State wrong
from ops.scenario import Statecorrectfrom ops.testing import State - CharmSpec
from ops.testing import CharmSpec
Quickstart
import os
import yaml
from ops.charm import CharmBase
from ops.testing import Context, State, CharmSpec
class MyCharm(CharmBase):
def __init__(self, *args):
super().__init__(*args)
self.framework.observe(self.on.install, self._on_install)
def _on_install(self, event):
self.unit.status.set_active()
def test_charm():
metadata = yaml.safe_load('''
name: my-charm
summary: test
series: [focal]
''')
spec = CharmSpec(MyCharm, meta=metadata)
ctx = Context(spec, meta={'name': 'my-charm'}, actions=None, config=None)
state = ctx.run('install', State())
assert state.unit_status.name == 'active'
print('Test passed')
if __name__ == '__main__':
test_charm()