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]
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.
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.
pip install ops-scenario

Minimal test: creates a charm, runs the install event, asserts active status.

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()