Jubilant is a Python library that wraps the Juju CLI for use in charm integration tests. It provides methods that map 1:1 to Juju CLI commands, but with a type-annotated, Pythonic interface.
You should consider switching to Jubilant if your integration tests currently use pytest-operator (and they probably do). Jubilant has an API you'll pick up quickly, and it avoids some of the pain points of python-libjuju, such as websocket failures and having to use async
. Read our design goals.
Jubilant 1.0.0 was released in April 2025. We'll avoid making breaking changes to the API after this point.
Jubilant is published to PyPI, so you can install and use it with your favorite Python package manager:
$ pip install jubilant
# or
$ uv add jubilant
Because Jubilant calls the Juju CLI, you'll also need to install Juju.
To use Jubilant in Python code:
import jubilant
juju = jubilant.Juju()
juju.deploy('snappass-test')
juju.wait(jubilant.all_active)
# Or only wait for specific applications:
juju.wait(lambda status: jubilant.all_active(status, 'snappass-test', 'another-app'))
Below is an example of a charm integration test. First we define a module-scoped pytest fixture named juju
which creates a temporary model and runs the test with a Juju
instance pointing at that model. Jubilant'stemp_model
context manager creates the model during test setup and destroys it during teardown:
# conftest.py
@pytest.fixture(scope='module')
def juju():
with jubilant.temp_model() as juju:
yield juju
# test_deploy.py
def test_deploy(juju: jubilant.Juju): # Use the "juju" fixture
juju.deploy('snappass-test') # Deploy the charm
status = juju.wait(jubilant.all_active) # Wait till the app and unit are 'active'
# Hit the Snappass HTTP endpoint to ensure it's up and running.
address = status.apps['snappass-test'].units['snappass-test/0'].address
response = requests.get(f'http://{address}:5000/', timeout=10)
response.raise_for_status()
assert 'snappass' in response.text.lower()
You don't have to use pytest with Jubilant, but it's what we recommend. Pytest's assert
-based approach is a straight-forward way to write tests, and its fixtures are helpful for structuring setup and teardown.
Anyone can contribute to Jubilant. It's best to start by opening an issue with a clear description of the problem or feature request, but you can also open a pull request directly.
Jubilant uses uv
to manage Python dependencies and tools, so you'll need to install uv to work on the library. You'll also need make
to run local development tasks (but you probably have make
installed already).
After that, clone the Jubilant codebase and use make all
to run various checks and the unit tests:
$ git clone https://github.com/canonical/jubilant
Cloning into 'jubilant'...
...
$ cd jubilant
$ make all
...
========== 107 passed in 0.26s ==========
To contribute a code change, write your fix or feature, add tests and docs, then run make all
before you push and create a PR. Once you create a PR, GitHub will also run the integration tests, which takes several minutes.
To create a new release of Jubilant:
- Update the
__version__
field injubilant/__init__.py
to the new version you want to release. - Push up a PR with this change and get it reviewed and merged.
- Create a new release on GitHub with good release notes. The tag should start with a
v
, likev1.2.3
. Once you've created the release, thepublish.yaml
workflow will automatically publish it to PyPI. - Once the publish workflow has finished, check that the new version appears in the PyPI version history.