Skip to content

Commit da4cf7d

Browse files
authored
Merge pull request #3 from strawberry-graphql/agent/add-strawberry-field-coverage
Add Strawberry field coverage
2 parents 8147ca5 + 7c93f9c commit da4cf7d

14 files changed

Lines changed: 2141 additions & 9 deletions

File tree

.github/workflows/test.yml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,24 @@ jobs:
5353
- run: uv sync --locked
5454
- run: uv run --locked --with "${{ matrix.pytest-version }}" pytest -vv
5555

56+
compatibility:
57+
name: 🧩 Strawberry compatibility
58+
runs-on: ubuntu-latest
59+
60+
steps:
61+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
62+
with:
63+
persist-credentials: false
64+
- name: Install uv
65+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
66+
with:
67+
enable-cache: true
68+
python-version: "3.14"
69+
70+
- run: uv sync --locked
71+
- name: Run compatibility sessions
72+
run: uv run nox --tags compatibility
73+
5674
quality:
5775
name: ✨ Quality
5876
runs-on: ubuntu-latest
@@ -76,7 +94,7 @@ jobs:
7694
required-ci:
7795
name: Required CI
7896
if: ${{ always() }}
79-
needs: [tests, quality]
97+
needs: [tests, compatibility, quality]
8098
runs-on: ubuntu-latest
8199

82100
steps:

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ The package registers itself with pytest through the `pytest11` entry point and
1010
includes the project automation needed to test and publish future plugin
1111
features. It does not expose fixtures, hooks, or command-line options yet.
1212

13-
This release was contributed by [@patrick91](https://github.com/patrick91) in [#2](https://github.com/strawberry-graphql/pytest-strawberry/pull/2)
13+
This release was contributed by [@patrick91](https://github.com/patrick91) in [#2](https://github.com/strawberry-graphql/pytest-strawberry/pull/2)

README.md

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
> Pytest plugin for Strawberry GraphQL
66
7-
This repository currently provides the package and release infrastructure for
8-
`pytest-strawberry`. The installed package is discovered automatically by
9-
pytest, but it does not expose fixtures, hooks, or command-line options yet.
7+
`pytest-strawberry` measures which Strawberry GraphQL schema fields are reached
8+
while your pytest suite runs. It reports schema field coverage independently of
9+
Python source coverage.
1010

1111
## Installation
1212

@@ -16,6 +16,83 @@ pip install pytest-strawberry
1616

1717
Pytest loads the plugin automatically through its `pytest11` entry point.
1818

19+
## Field coverage
20+
21+
Enable coverage on the pytest command line:
22+
23+
```shell
24+
pytest --strawberry-coverage
25+
```
26+
27+
By default, the report covers fields with explicit Strawberry resolvers or
28+
custom field resolution supplied by integrations such as Strawberry Django. It
29+
excludes fields handled only by Strawberry's ordinary attribute lookup:
30+
31+
```text
32+
============================= Strawberry coverage =============================
33+
Subscriptions: excluded (requires graphql-core 3.3+); graphql-core 3.2.11
34+
35+
Schema b6016cac
36+
┌───────────────────┬────────┬──────┬─────────┬───────────────────────┐
37+
│ Python type │ Fields │ Miss │ Cover │ Missing fields │
38+
├───────────────────┼────────┼──────┼─────────┼───────────────────────┤
39+
│ QueryRoot [Query] │ 2 │ 0 │ 100.00% │ │
40+
│ UserModel [User] │ 3 │ 1 │ 66.67% │ email_address [email] │
41+
├───────────────────┼────────┼──────┼─────────┼───────────────────────┤
42+
│ All types │ 5 │ 1 │ 80.00% │ │
43+
└───────────────────┴────────┴──────┴─────────┴───────────────────────┘
44+
45+
Overall coverage: 80.00% (4/5 fields, 1 missing)
46+
```
47+
48+
The table uses Python class and field names so uncovered resolvers are directly
49+
searchable in the codebase. When `name=` explicitly changes a GraphQL name, the
50+
GraphQL alias is shown in brackets. Automatic camel-casing is not repeated.
51+
52+
Use `all` mode to include Strawberry fields that use default attribute
53+
resolution:
54+
55+
```shell
56+
pytest --strawberry-coverage --strawberry-coverage-mode=all
57+
```
58+
59+
A field counts as covered when GraphQL execution reaches its resolver or
60+
default lookup. A resolver that raises still counts. Fields skipped by a
61+
directive, omitted from the operation, or bypassed by null propagation do not.
62+
Aliases and fragments do not create additional field coordinates.
63+
64+
You can enforce a minimum combined percentage:
65+
66+
```shell
67+
pytest --strawberry-coverage --strawberry-coverage-fail-under=90
68+
```
69+
70+
The threshold is compared with the displayed percentage rounded to two decimal
71+
places. Mode and threshold options require `--strawberry-coverage`.
72+
73+
Schema executions with different eligible fields or Python mappings receive
74+
separate fingerprinted tables. Executions with the same field set are combined.
75+
The final threshold uses their combined field and hit totals. Coverage from
76+
pytest-xdist workers is merged automatically.
77+
78+
A runnable [Strawberry Django example](./examples/strawberry_django) shows how
79+
generated model fields participate in resolver coverage without adding a
80+
runtime dependency on Strawberry Django.
81+
82+
### Subscriptions
83+
84+
Subscription resolver extensions are supported by graphql-core 3.3 and newer.
85+
With that capability available, subscription source and payload fields are
86+
included normally. graphql-core 3.2 does not run resolver extensions for
87+
subscriptions, so subscription-only fields are excluded and the plugin emits
88+
one warning if a subscription executes. Query and mutation coverage remains
89+
available in the same run.
90+
91+
Only schemas used to execute an operation during the pytest session are
92+
reported. An observed schema with no eligible fields is 100% covered; a session
93+
that observes no schemas is 0% covered. Reporting and threshold enforcement are
94+
disabled under `--collect-only`.
95+
1996
## Development
2097

2198
Install the project and its development dependencies with
@@ -35,6 +112,13 @@ uv run mypy
35112
uv build
36113
```
37114

115+
Run the Strawberry, graphql-core, and Strawberry Django compatibility checks
116+
on Python 3.14 with:
117+
118+
```shell
119+
uv run nox --tags compatibility
120+
```
121+
38122
## Releases
39123

40124
Release changes are proposed through pull requests containing a `RELEASE.md`.

RELEASE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
release type: minor
3+
---
4+
5+
Add runtime Strawberry GraphQL field coverage with resolver-only and all-field
6+
modes, custom field resolver support including Strawberry Django, Python-first
7+
terminal reporting, fail-under enforcement, subscription capability detection,
8+
and pytest-xdist aggregation.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Strawberry Django example
2+
3+
This example uses an unsaved Django model instance, so it needs no database
4+
migrations or external services. Run its dedicated compatibility session from
5+
the repository root with:
6+
7+
```shell
8+
uv run nox -s strawberry_django
9+
```
10+
11+
The session runs the plugin's test suite with Strawberry Django installed, then
12+
runs this example with coverage enabled.
13+
14+
The report includes the generated Django model fields:
15+
16+
```text
17+
Schema b7dc8af0
18+
┌─────────────┬────────┬──────┬─────────┬────────────────┐
19+
│ Python type │ Fields │ Miss │ Cover │ Missing fields │
20+
├─────────────┼────────┼──────┼─────────┼────────────────┤
21+
│ FruitType │ 2 │ 1 │ 50.00% │ description │
22+
│ Query │ 1 │ 0 │ 100.00% │ │
23+
├─────────────┼────────┼──────┼─────────┼────────────────┤
24+
│ All types │ 3 │ 1 │ 66.67% │ │
25+
└─────────────┴────────┴──────┴─────────┴────────────────┘
26+
27+
Overall coverage: 66.67% (2/3 fields, 1 missing)
28+
```
29+
30+
Resolver mode includes `FruitType.name` and `FruitType.description` because
31+
`StrawberryDjangoField` supplies custom field resolution. The query covers
32+
`name`, leaving `description` visible in the missing-fields column.
33+
34+
Add `--strawberry-coverage-mode=all` to include ordinary Strawberry attribute
35+
lookups too.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import django
2+
import strawberry
3+
import strawberry_django
4+
from django.conf import settings
5+
from django.db import models
6+
7+
if not settings.configured:
8+
settings.configure(
9+
DATABASES={
10+
"default": {
11+
"ENGINE": "django.db.backends.sqlite3",
12+
"NAME": ":memory:",
13+
}
14+
},
15+
INSTALLED_APPS=[],
16+
)
17+
django.setup()
18+
19+
20+
class Fruit(models.Model):
21+
name = models.CharField(max_length=100)
22+
description = models.TextField()
23+
24+
class Meta:
25+
app_label = "coverage_example"
26+
27+
def __str__(self) -> str:
28+
return self.name
29+
30+
31+
@strawberry_django.type(Fruit)
32+
class FruitType:
33+
name: strawberry.auto
34+
description: strawberry.auto
35+
36+
37+
@strawberry.type
38+
class Query:
39+
@strawberry.field
40+
def fruit(self) -> FruitType:
41+
return Fruit(name="Strawberry", description="A very good berry")
42+
43+
44+
schema = strawberry.Schema(query=Query)
45+
46+
47+
def test_fruit_name() -> None:
48+
result = schema.execute_sync("{ fruit { name } }")
49+
50+
assert result.errors is None
51+
assert result.data == {"fruit": {"name": "Strawberry"}}

noxfile.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Compatibility test sessions."""
2+
3+
from __future__ import annotations
4+
5+
import nox
6+
7+
nox.needs_version = ">=2026.8.10"
8+
nox.options.default_venv_backend = "uv"
9+
10+
PYTHON = "3.14"
11+
PYTEST_XDIST = "pytest-xdist>=3.6.0,<4.0.0"
12+
13+
14+
@nox.session(python=PYTHON, tags=["compatibility"])
15+
def strawberry_min(session: nox.Session) -> None:
16+
"""Test the lowest supported Strawberry and graphql-core versions."""
17+
session.install(
18+
".",
19+
PYTEST_XDIST,
20+
"strawberry-graphql==0.316.0",
21+
"graphql-core==3.2.11",
22+
)
23+
session.run("pytest", "-vv")
24+
25+
26+
@nox.session(python=PYTHON, tags=["compatibility"])
27+
def graphql_core_33(session: nox.Session) -> None:
28+
"""Test subscription coverage with graphql-core 3.3."""
29+
session.install(".", PYTEST_XDIST, "graphql-core>=3.3.0rc0,<3.4")
30+
session.run("pytest", "-vv")
31+
32+
33+
@nox.session(python=PYTHON, tags=["compatibility"])
34+
def strawberry_django(session: nox.Session) -> None:
35+
"""Test Strawberry Django support and its runnable example."""
36+
session.install(".", PYTEST_XDIST, "strawberry-graphql-django==0.87.0")
37+
session.run("pytest", "-vv")
38+
session.run(
39+
"pytest",
40+
"examples/strawberry_django",
41+
"-q",
42+
"--strawberry-coverage",
43+
)

pyproject.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ classifiers = [
2222
"Topic :: Software Development :: Testing",
2323
]
2424
requires-python = ">=3.10,<4.0"
25-
dependencies = ["pytest>=8.0,<10.0"]
25+
dependencies = [
26+
"pytest>=8.0,<10.0",
27+
"strawberry-graphql>=0.316.0",
28+
]
2629

2730
[project.urls]
2831
repository = "https://github.com/strawberry-graphql/pytest-strawberry"
@@ -34,7 +37,9 @@ pytest-strawberry = "pytest_strawberry.plugin"
3437
[dependency-groups]
3538
dev = [
3639
"mypy>=1.15.0,<3.0.0",
40+
"nox>=2026.8.10,<2027.0.0",
3741
"pre-commit>=4.0.0,<5.0.0",
42+
"pytest-xdist>=3.6.0,<4.0.0",
3843
"ruff>=0.14.1,<0.17.0",
3944
]
4045

@@ -78,6 +83,7 @@ ignore = [
7883
]
7984

8085
[tool.ruff.lint.per-file-ignores]
86+
"examples/**/*.py" = ["D", "INP001", "S101"]
8187
"tests/*" = ["D", "S101"]
8288

8389
[build-system]

0 commit comments

Comments
 (0)