Skip to main content

The Canonical pytest plugin for Jubilant

pytest-jubilant is a pytest plugin for Jubilant.

Jubilant is a Python library that wraps the Juju CLI, primarily for use in charm integration tests. pytest-jubilant provides additional pytest-specific functionality on top of Jubilant.

Read more: pytest-jubilant's design goals

Getting started

pytest-jubilant's features are available to use as long as it's installed in the Python environment where you're invoking pytest. The best way to ensure this is to add both pytest and pytest-jubilant to your dependencies like this.

# pyproject.toml
[dependency-groups]
integration = [
    "pytest>=9,<10",
    "pytest-jubilant>=2,<3",
]

And ensure that the integration dependency group is installed when running your integration tests, for example with:

uv run --group integration pytest tests/integration

Get started writing your own Jubilant integration tests with the how-to guide in the Ops docs.

Read on for an explanation of the fixtures, CLI options, and markers provided by pytest-jubilant.

Fixtures

pytest-jubilant's fixtures are available as long as pytest-jubilant is installed. You can request a fixture by declaring it as an argument for the test that needs it.

juju

This is a module-scoped fixture that creates a temporary Juju model and tears it down when the tests in the module have finished.

You can use combinations of the --juju-model, --no-juju-setup, and --no-juju-teardown options to reuse models across multiple integration test runs.

Usage:

# test_smoke.py
"""Test that the charm can be deployed and go to active status."""

import jubilant


def test_deploy(juju: jubilant.Juju):
    juju.deploy("./foo.charm", "foo")
    juju.wait(lambda status: jubilant.all_active(status, "foo"), timeout=1000)

This test will spin up a temporary model named jubilant-<randomhex>-test-smoke. It will be torn down when the module-scoped juju fixture context exits.

juju_factory

This is a module-scoped fixture that you can use to manage multiple temporary Juju models. It's what the juju fixture is using behind the scenes. It's useful if you have test cases that require multiple models, for example testing cross-model relations.

Usage:

# test_cmr.py
"""Test cross model relations."""

import jubilant
import pytest
import pytest_jubilant


@pytest.fixture(scope="module")
def istio(juju_factory: pytest_jubilant.JujuFactory):
    yield juju_factory.get_juju(suffix="istio")


def test_offer_consume_relate(juju: jubilant.Juju, istio: jubilant.Juju):
    istio.deploy("istio-k8s", "istio")
    istio.wait(lambda status: all_active(status, "istio"), timeout=1000)

    juju.deploy("./foo.charm", "foo")
    juju.wait(lambda status: jubilant.all_active(status, "foo"), timeout=1000)

    juju.cli("offer", "foo:bar")
    istio.cli("consume", f"{juju.model}:foo")
    istio.cli("relate", "istio", "foo:bar")

This test will spin up two temporary models, one called jubilant-<randomhex>-test-cmr, and one called jubilant-<randomhex>-test-cmr-istio. They'll be torn down when the module context exits.

pytest tests/integration --juju-model hello will use hello instead of jubilant-<randomhex>. The module names combined with the suffixes you defined in the fixtures will give all generated models predictable names. The tests will reuse the existing models (if found) or create new ones with those names.

You can optionally pass controller and cloud to juju_factory.get_juju to deploy individual models on specific controllers or clouds. This is useful for testing cross-model relations between different deployment types, such as machine and Kubernetes models on separate controllers.

CLI options

pytest-jubilant extends pytest with several commandline arguments that you can add directly to your pytest invocation.

--no-juju-setup

Skip all tests marked with juju_setup and don't create any new models. This option is for re-running a test on an existing model which is already set up. Since setup can be very lengthy, it's often helpful to avoid re-running it when iterating on tests that assume the charm is up and running, for example when testing actions.

Usage:

pytest tests/integration --no-juju-teardown
# Check the last line of output for the <model prefix>!
pytest tests/integration --no-juju-setup --juju-model <model prefix>

--no-juju-teardown

Skip all tests marked with juju_teardown and skip destroying the models. Useful to inspect the state of a model after a (failed) test run.

Usage:

pytest tests/integration --no-juju-teardown

--juju-model

By default, created Juju model names are prefixed with jubilant-<randomhex>, where <randomhex> is randomly generated each pytest run. Set --juju-model on the commandline to use a fixed prefix instead.

Usage:

pytest tests/integration/test_foo.py --juju-model hello
# Runs the test on new 'hello-test-foo' model and tears it down afterwards.

pytest tests/integration/test_foo.py --juju-model hello --no-juju-teardown
# Runs the test on new 'hello-test-foo' model and keeps it.

pytest tests/integration/test_foo.py --juju-model hello --no-juju-setup --no-juju-teardown
# Runs the test on the existing 'hello-test-foo' model and keeps it.
# Note that we don't want to run the setup tests since they already ran.
juju add-model hello-test-bar  # A whole new model.
pytest tests/integration/test_bar.py --juju-model hello --no-juju-teardown
# Runs the test on the existing 'hello-test-bar' model and keeps it.
# Note that we want to run the setup tests to deploy the charm(s) etc.
# since this is a new model.

--juju-controller

Set the default Juju controller to use when creating new models. This is equivalent to passing --controller to juju add-model. It can be overridden by passing the controller argument to JujuFactory.get_juju. If neither is specified, Juju falls back to the currently active controller.

Usage:

pytest tests/integration --juju-controller my-controller

--juju-cloud

Set the default Juju cloud (or cloud/region) to use when creating new models. This is equivalent to passing the cloud argument to juju add-model. It can be overridden by passing the cloud argument to JujuFactory.get_juju. If neither is specified, Juju falls back to the currently active cloud.

When no cloud is specified (neither here nor via JujuFactory.get_juju), each model created by the fixtures is given an arch constraint matching the architecture of the machine running the tests, so charms are placed on machines of the same architecture. Specifying a cloud suppresses this, since that cloud may run a different architecture. To override, call juju.model_constraints({"arch": arch}) with the architecture you require.

Usage:

pytest tests/integration --juju-cloud localhost
pytest tests/integration --juju-cloud aws/us-east-1

--juju-switch

Switch to the model that is currently in scope, so you can keep an eye on the juju status as the tests progress. This won't be very helpful if you're running multiple test modules in parallel!

Only switches to models created by the juju fixture, not those created by juju_factory.

Usage:

pytest tests/integration -k test_something --juju-switch
# will switch you to the 'jubilant-<randomhex>-<module>' model as soon as it's created

pytest tests/integration -k test_something --juju-model hello --juju-switch
# will switch you to the 'hello-<module>' model as soon as it's created

--juju-dump-logs

When all the tests in a module have completed, but prior to tearing down the models owned by a juju_factory, dump the juju debug-log for each managed model into the specified directory.

  • By default, juju debug-log is not run, and logs aren't dumped.
  • If --juju-dump-logs is passed, logs are dumped to <CWD>/.logs/.
  • If --juju-dump-logs <target dir> is passed, logs are dumped to <target dir>/.

The file naming scheme is:

<module prefix>-<module name>[-<suffix>]-juju-debug.log

Usage:

pytest tests/integration/test_ingress.py --juju-dump-logs=debug_logs
# Once the tests are done, you'll find the logs in:
# ./debug_logs/jubilant-abcd1234-test-ingress-juju-debug.log

pytest tests/integration/test_ingress.py --juju-model foo --juju-dump-logs
# Once the tests are done, you'll find the logs in the default directory:
# ./.logs/foo-test-ingress-juju-debug.log

pytest integration/test_ingress.py
# No logs will be saved.

Markers

pytest-jubilant declares markers that you can apply to your tests with @pytest.mark.<marker>.

juju_setup

Marker for tests that prepare a model for use in later tests.

The --no-juju-setup option will skip any tests marked with juju_setup, in addition to not destroying the Juju models themselves.

Usage:

import jubilant
import pytest


@pytest.mark.juju_setup
def test_deploy(juju: jubilant.Juju):
    juju.deploy("A")
    juju.deploy("B")


@pytest.mark.juju_setup
def test_relate(juju: jubilant.Juju):
    juju.integrate("A", "B")

juju_teardown

Marker for tests that perform destructive actions on a model.

The --no-juju-teardown option will skip any tests marked with juju_teardown, in addition to not destroying the Juju models themselves.

Usage:

import jubilant
import pytest


@pytest.mark.juju_teardown
def test_disintegrate(juju: jubilant.Juju):
    juju.remove_relation("A", "B")


@pytest.mark.juju_teardown
def test_destroy(juju: jubilant.Juju):
    juju.remove_application("A")
    juju.remove_application("B")

Project and community

pytest-jubilant is an open source project that warmly welcomes community contributions, suggestions, fixes and constructive feedback.

For support, join Charm Development on Matrix.

To follow along with updates and tips about charm development, join our Discourse forum.

Metadata

Release files for pytest-jubilant 2.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytest-jubilant 2.3.1
File Size Uploaded Provenance Actions
pytest_jubilant-2.3.1.tar.gz 19.2 kB With provenance Details for pytest_jubilant-2.3.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pytest-jubilant 2.3.1
File Interpreter ABI Platform Actions
pytest_jubilant-2.3.1-py3-none-any.whl Python 3 none any Details for pytest_jubilant-2.3.1-py3-none-any.whl

Total release size: 34.3 kB

Release files / pytest_jubilant-2.3.1.tar.gz

Download URL pytest_jubilant-2.3.1.tar.gz
Size 19.2 kB
Tags Source
SHA-256 checksum
How to use checksums
cc34837c191dbf500db77a1c010fbb49ad712513252851e386b6de3487a38073
BLAKE2b-256 checksum
How to use checksums
4391f9f1e4cc34f9b4fcde0b0a3faced98178eb04ce7db64bf9d0c1121ebdf5d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.

Transparency log

Release files / pytest_jubilant-2.3.1-py3-none-any.whl

Download URL pytest_jubilant-2.3.1-py3-none-any.whl
Size 15.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e8ce3aa4bafaf638c7e66ff44a9cba8b76802a5339003c70e7e14eb735e16bec
BLAKE2b-256 checksum
How to use checksums
e58617ed460bb5b4572f3da7d1a8fc80da857cb06877f5a764440d84a1bb791b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.

Transparency log

Security for pytest-jubilant 2.3.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pytest_jubilant-2.3.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page