Metadata-Version: 2.4
Name: pytest-qgis
Version: 5.0.0
Summary: A pytest plugin for testing QGIS python plugins
Keywords: pytest,qgis,QGIS,PyQGIS
Author: Gispo Ltd., OSGeo Suomi
Author-email: Gispo Ltd. <info@gispo.fi>, OSGeo Suomi <info@osgeo.fi>
License-Expression: GPL-2.0-or-later
License-File: LICENSE
Classifier: Development Status :: 5 - Production/Stable
Classifier: Framework :: Pytest
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Testing
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Operating System :: OS Independent
Requires-Dist: pytest>=6.0
Maintainer: OSGeo Suomi
Maintainer-email: OSGeo Suomi <info@osgeo.fi>
Requires-Python: >=3.10
Project-URL: homepage, https://github.com/osgeosuomi/pytest-qgis
Project-URL: repository, https://github.com/osgeosuomi/pytest-qgis
Project-URL: changelog, https://github.com/osgeosuomi/pytest-qgis/blob/main/CHANGELOG.md
Description-Content-Type: text/markdown

# pytest-qgis

[![PyPI version](https://badge.fury.io/py/pytest-qgis.svg)](https://badge.fury.io/py/pytest-qgis)
[![Downloads](https://img.shields.io/pypi/dm/pytest-qgis.svg)](https://pypistats.org/packages/pytest-qgis)
![CI](https://github.com/osgeosuomi/pytest-qgis/workflows/CI/badge.svg)
[![Code on Github](https://img.shields.io/badge/Code-GitHub-brightgreen)](https://github.com/osgeosuomi/pytest-qgis)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
[![codecov.io](https://codecov.io/github/osgeosuomi/pytest-qgis/coverage.svg?branch=main)](https://codecov.io/github/osgeosuomi/pytest-qgis?branch=main)

A [pytest](https://docs.pytest.org) plugin for testing QGIS python plugins.

## Features

This plugin makes it easier to write QGIS plugin tests with the help of some fixtures
and hooks:

### Fixtures

* `qgis_app` returns and eventually exits fully
  configured [`QgsApplication`](https://qgis.org/pyqgis/master/core/QgsApplication.html).
  This fixture is called automatically on the start of pytest session.
* `qgis_bot` returns a [`QgisBot`](#qgisbot), which holds common utility methods for
  interacting with QGIS.
* `qgis_canvas` returns [`QgsMapCanvas`](https://qgis.org/pyqgis/master/gui/QgsMapCanvas.html).
* `qgis_parent` returns the QWidget used as parent of the `qgis_canvas`
* `qgis_iface` returns stubbed
  [`QgisInterface`](https://qgis.org/pyqgis/master/gui/QgisInterface.html). All the
  methods that are not implemented return a MagicMock that can be used for testing the
  calls.
* `qgis_new_project` makes sure that all the map layers and configurations are removed.
  This should be used with tests that add stuff to
  [`QgsProject`](https://qgis.org/pyqgis/master/core/QgsProject.html). Fixture returns
  the `QgsProject` instance.
* `qgis_processing` initializes the processing framework. This can be used when testing
  code that calls `processing.run(...)`.
* `qgis_version` returns QGIS version number as integer.
* `qgis_world_map_geopackage` returns Path to the world_map.gpkg that ships with QGIS
* `qgis_countries_layer` returns Natural Earth countries layer from world_map.gpkg as
  QgsVectorLayer

### Markers

* `qgis_show_map` lets developer inspect the QGIS map visually during the test and also
  at the teardown of the test. Full signature of the marker is:

  ```python
  @pytest.mark.qgis_show_map(timeout: int = 30, add_basemap: bool = False, zoom_to_common_extent: bool = True, extent: QgsRectangle = None)
  ```

  * `timeout` is the time in seconds until the map is closed. If timeout is zero, the map
    will be closed in teardown.
  * `add_basemap` when set to True, adds Natural Earth countries layer as the basemap for
    the map.
  * `zoom_to_common_extent` when set to True, centers the map around all layers in the
    project.
  * `extent` is alternative to `zoom_to_common_extent` and lets user specify the extent
      as [`QgsRectangle`](https://qgis.org/pyqgis/master/core/QgsRectangle.html)

Check the marker api [documentation](https://docs.pytest.org/en/latest/mark.html)
and
[examples](https://docs.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules)
for the ways markers can be used.

### Hooks

* `pytest_configure` hook is used to initialize and
  configure [`QgsApplication`](https://qgis.org/pyqgis/master/core/QgsApplication.html).
  It is also used to patch `qgis.utils.iface` with `qgis_iface` automatically.

  > Be careful not to import modules importing `qgis.utils.iface` in the root of conftest,
  > because the `pytest_configure` hook has not yet patched `iface` in that point. See
  > [this issue](https://github.com/osgeosuomi/pytest-qgis/issues/35) for details.

* `pytest_runtest_teardown` hook is used to ensure that all layer fixtures of any scope
  are cleaned properly without causing segmentation faults. The layer fixtures that are
  cleaned automatically must have some of the following keywords in their name: "layer",
  "lyr", "raster", "rast", "tif".

### Utility tools

* `wait` and `wait_until` functions in `pytest_qgis.utils` can be used to wait certain
  time or until a condition is met. `wait` runs a real event loop for the given
  milliseconds. If you use [pytest-qt](https://pytest-qt.readthedocs.io/), its
  `qtbot.wait`, `qtbot.waitUntil` and `qtbot.waitSignal` offer similar functionality.
* `clean_qgis_layer` decorator found in `pytest_qgis.utils` can be used with
  `QgsMapLayer` fixtures to ensure that they are cleaned properly if they are used but
  not added to the `QgsProject`. This is only needed with layers with other than memory
  provider.

  This decorator works only with fixtures that **return** QgsMapLayer instances.
  There is no support for fixtures that use yield.

  This decorator is an alternative way of cleaning the layers, since
  `pytest_runtest_teardown` hook cleans layer fixtures automatically by the keyword.

  ```python
  # conftest.py or start of a test file
  import pytest
  from pytest_qgis.utils import clean_qgis_layer
  from qgis.core import QgsVectorLayer


  @pytest.fixture()
  @clean_qgis_layer
  def geojson() -> QgsVectorLayer:
      return QgsVectorLayer("layer_file.geojson", "some layer")


  # This will be cleaned automatically since it contains the keyword "layer" in its name
  @pytest.fixture()
  def geojson_layer() -> QgsVectorLayer:
      return QgsVectorLayer("layer_file2.geojson", "some layer")
  ```

### Command line options

* `--qgis_disable_gui` can be used to disable graphical user interface in tests. This
  speeds up the tests that use Qt widgets of the plugin.
* `--qgis_disable_init` can be used to prevent QGIS (QgsApplication) from initializing.
  Mainly used in internal testing.
* `--qgis_disable_exit` can be used to prevent QGIS (QgsApplication) from exiting in
  teardown. This might be useful if C++ errors occur.

### ini-options

* `qgis_gui_enabled` whether the QUI will be visible or not. Defaults to `True`. Command
  line option `--qgis_disable_gui` will override this.
* `qgis_canvas_width` width of the QGIS canvas in pixels. Defaults to 600.
* `qgis_canvas_height` height of the QGIS canvas in pixels. Defaults to 600.
* `qgis_server` support qgis server only plugin testing. This prevent initializing qgis
  interface and allow instantiating QgsServer() safely.
* `qgis_disable_exit` whether to disable QGIS (QgsApplication) from exiting in teardown.
  This might be useful if C++ errors occur.

### Custom QGIS settings

When running tests, a directory named `.qgis-settings` will be created
containings all QGIS default profile as well as QGIS settings.
Most of the time you can ignore this directory, but it may be useful for inspecting
created default settings.

You may define custom settings to be loaded at startup: in your
root tests directory, create a file `qgis_settings.ini` containing all your default
QGIS3 settings: this file will be used as the default settings for the QGIS tests
session.

### Event loop and threads

Queued and cross-thread signal deliveries require the receiving thread to
process its event queue. pytest-qgis handles this automatically:

* `QgsApplication` is initialized for every test process, including every
  [pytest-xdist](#pytest-xdist) worker.
* Queued events and deferred deletions (`deleteLater`) are flushed after
  each test.
* `wait` in `pytest_qgis.utils` runs a real event loop for the given
  milliseconds.

Signals between objects living in a `QThread` require the thread to run an
event loop. `QThread`'s default `run()` implementation provides one.

> **Note:** Decorate slots with `@pyqtSlot()` (or connect only after
> `moveToThread`), otherwise PyQt may run the slot in the thread where the
> connection was made.

### pytest-xdist

Tests can be run in parallel with
[pytest-xdist](https://pypi.org/project/pytest-xdist/) (`pytest -n auto`)
without configuration: each worker gets its own `QgsApplication` and
settings directory (`.qgis-settings/<worker_id>`).

## QgisBot

Class to hold common utility methods for interacting with QGIS. Check
[test_qgis_bot.py](test/test_qgis_bot.py) for usage examples.  Here are some of the
methods:

* `create_feature_with_attribute_dialog` method can be used to create a feature with
  default values using QgsAttributeDialog. This ensures that all the default values are
  honored and for example boolean fields are either true or false, not null.
* `open_project` method clears the current project and opens the given `.qgs` or `.qgz`
  file. The map canvas shows the visible layers of the project in the layer tree order
  with the extent and CRS saved in the project. The project stays open after the test,
  so use the `qgis_new_project` fixture in tests that need an empty project.
* `get_qgs_attribute_dialog_widgets_by_name` function can be used to get dictionary of
  the `QgsAttributeDialog` widgets. Check the test
  [test_qgis_ui.py::test_attribute_dialog_change](./test/visual/test_qgis_ui.py) for a
  usage example.

## Requirements

This pytest plugin requires QGIS >= 3.34 to work though versions up until
pytest-qgis<=2.1.0 should work with QGIS >= 3.16.

## Installation

Install with `pip`:

```bash
pip install pytest-qgis
```

## Development

See [DEVELOPMENT.md](./DEVELOPMENT.md).

## Contributing

Contributions are very welcome. Get started by reading OSGeo Suomi
[CONTRIBUTING guidelines](https://github.com/osgeosuomi/.github/blob/main/CONTRIBUTING.md).

## License

Distributed under the terms of the `GNU GPL v2.0` license, or (at your option) any later
version. "pytest-qgis" is free and open source software.
