Skip to main content

Add your description here

Project description

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.

[!TIP] Use jubilant.Juju as the type annotation for the juju fixture in your tests for better linting and IDE autocompletions.

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.

[!TIP] Use pytest_jubilant.JujuFactory as the type annotation for the juju_factory fixture in your tests for better linting and IDE autocompletions.

Note that the exposed JujuFactory type is just a protocol, and can't be used to directly create a Juju factory. Whenever you need one, request the juju_factory fixture.

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.

[!WARNING] It's an error to pass --no-juju-setup without also specifying --juju-model.

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.

[!WARNING] The --keep-models flag used by pytest-operator is unsupported as of pytest-jubilant 2.0! Be sure to use --no-juju-teardown instead.

Usage:

pytest tests/integration --no-juju-teardown

[!TIP] The last line of output will tell you the --juju-model value to use if you want to rerun your tests using the same models. Be sure to pass --no-juju-setup as well to avoid failures when trying to perform setup steps that are already done.

--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.

[!WARNING] Note that models created with this prefix will be torn down at the end of the test run just like any other, so if you're targeting existing models you care about, don't forget the --no-juju-teardown flag!

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.

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, logs aren't dumped, and juju debug-log is only executed if tests failed, so that the last log lines can be sent to stderr.
  • 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.

[!TIP] Use --juju-dump-logs in combination with actions/upload-artifact to make your logs available in CI.

For example:

  # In your integration test job
  - run: tox -e integration -- --juju-dump-logs logs
  - name: Upload logs
    if: ${{ !cancelled() }}
    uses: actions/upload-artifact@v4
    with:
      name: juju-dump-logs
      path: logs

Note that dumping to the default location .logs/ will require you to set include-hidden-files: true.

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.

[!TIP] To run only your juju_setup tests and leave the models set up for manual interaction, try:

pytest tests/integration -m juju_setup --no-juju-teardown

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytest_jubilant-2.1.1.tar.gz (18.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytest_jubilant-2.1.1-py3-none-any.whl (14.9 kB view details)

Uploaded Python 3

File details

Details for the file pytest_jubilant-2.1.1.tar.gz.

File metadata

  • Download URL: pytest_jubilant-2.1.1.tar.gz
  • Upload date:
  • Size: 18.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pytest_jubilant-2.1.1.tar.gz
Algorithm Hash digest
SHA256 04c083ef1366f92237da79e3d499094ca50f50519a19462f388c38c699ee495a
MD5 0aec467f79a1283a7ff6bf936c676e0c
BLAKE2b-256 bb113baee677f3cf51ef41e06a7cbcfe8551613723695a458997b9d63026e0b5

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytest_jubilant-2.1.1.tar.gz:

Publisher: build-and-publish.yaml on canonical/pytest-jubilant

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pytest_jubilant-2.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for pytest_jubilant-2.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d710cf9c41fb8daed6218d7b6ce68f4d4c234c5b0ee066d2be1dc9dd07265d35
MD5 20c842e2b346fcbb777be00a3fff16d5
BLAKE2b-256 ddea33ecce9a52a022547544ab3b6393368944b1b55480a35ae00b7d9bd02f39

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytest_jubilant-2.1.1-py3-none-any.whl:

Publisher: build-and-publish.yaml on canonical/pytest-jubilant

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page