Skip to main content

title: agentskills-testing description: Provider conformance suites, in-memory test doubles, and pytest fixtures.

Conformance suite and test doubles for Agent Skills providers.

SkillProvider is an abstract base class, which enforces that five methods exist and nothing whatsoever about what they do. The requirements that actually matter are the ones an ABC cannot express: that an unknown skill ID raises SkillNotFoundError rather than returning {}, that ../../etc/passwd is refused rather than resolved, and that a provider advertising resource listing can actually list. This package turns those into tests you inherit.

pip install agentskills-testing

Conformance suite

Subclass ProviderConformanceSuite, supply a provider fixture, and pytest collects the provider contract. Save this runnable example as tests/test_provider.py:

import pytest

from agentskills_testing import (
    ASSET_BYTES,
    ASSET_NAME,
    REFERENCE_BYTES,
    REFERENCE_NAME,
    SCRIPT_BYTES,
    SCRIPT_NAME,
    SKILL_ID,
    InMemorySkillProvider,
    ProviderConformanceSuite,
    build_skill,
)


class TestProvider(ProviderConformanceSuite):
    @pytest.fixture
    def provider(self) -> InMemorySkillProvider:
        skill = build_skill(
            SKILL_ID,
            references={REFERENCE_NAME: REFERENCE_BYTES},
            scripts={SCRIPT_NAME: SCRIPT_BYTES},
            assets={ASSET_NAME: ASSET_BYTES},
        )
        return InMemorySkillProvider({SKILL_ID: skill})

Run python -m pytest tests/test_provider.py -q. For your own backend, populate it with the same names and bytes below, then replace the fixture's provider construction. Use a yield fixture and close clients after the yield when your provider owns network resources.

Fixture contract

The provider fixture must expose exactly one skill:

id conformance-skill
metadata name == "conformance-skill", plus a non-empty description
body non-empty markdown
references/ notes.md containing b"# Notes\n\nA reference document.\n"
scripts/ run.sh containing b"#!/bin/sh\necho conformance\n"
assets/ diagram.svg containing b"<svg></svg>\n"

It must not define a skill called no-such-skill-anywhere, or any resource called no-such-resource.txt.

Every name and byte string above is exported as a constant (SKILL_ID, REFERENCE_NAME, REFERENCE_BYTES, and so on), so a fixture can be built from them rather than from copied literals. CONTRACT holds the same description as a string, and is what the default fixture prints when you forget to override it.

What it checks

  • Metadata carries name and a non-empty description, and does not carry the body: a metadata call that includes the body has already spent the tokens progressive disclosure exists to save.
  • Metadata is not shared mutable state: one caller mutating the returned dict must not affect the next.
  • Repeated reads agree. A provider that streams without buffering passes the first read and returns empty on the second; caching bugs look the same.
  • Each resource getter returns exact bytes, not str. A resource may be a PNG, and decoding on the way out makes that unreachable.
  • Unknown skills raise SkillNotFoundError; unknown resources raise ResourceNotFoundError.
  • list_resources() agrees with supports_resource_listing. Callers branch on that flag, so a provider whose flag and behaviour disagree breaks them whichever way it lies.
  • discover() agrees with supports_discovery, and everything it reports can actually be read. register_all() validates the whole list, so one phantom ID fails the entire registration.
  • Traversal identifiers are refused: parent traversal, absolute paths, Windows separators, percent-encoded traversal, and embedded NUL bytes, as both skill IDs and resource names. These are not opt-out.
  • Concurrent reads through a single instance return consistent content, which catches per-call state stored on self.

Size limits

ContentLimitConformanceSuite is separate and opt-in. A size limit is not part of the universal contract: an in-memory provider has no external source to bound, and demanding one would assert a filesystem's constraints against a dict. It is required of any provider that reads bytes it did not author: from disk, from a network, from anywhere a caller can grow without asking.

For a concrete bounded-provider example, also install agentskills-fs:

from pathlib import Path

import pytest

from agentskills_fs import LocalFileSystemSkillProvider
from agentskills_testing import (
  ASSET_BYTES,
  ASSET_NAME,
  REFERENCE_BYTES,
  REFERENCE_NAME,
  SCRIPT_BYTES,
  SCRIPT_NAME,
  SKILL_ID,
  ContentLimitConformanceSuite,
  build_skill,
  render_skill_md,
)


class TestProviderLimits(ContentLimitConformanceSuite):
  @pytest.fixture
  def limited_provider(self, tmp_path: Path) -> LocalFileSystemSkillProvider:
    directory = tmp_path / SKILL_ID
    directory.mkdir()
    (directory / "SKILL.md").write_text(
      render_skill_md(build_skill(SKILL_ID)), encoding="utf-8"
    )
    for kind, name, data in (
      ("references", REFERENCE_NAME, REFERENCE_BYTES),
      ("scripts", SCRIPT_NAME, SCRIPT_BYTES),
      ("assets", ASSET_NAME, ASSET_BYTES),
    ):
      (directory / kind).mkdir()
      (directory / kind / name).write_bytes(data)
    return LocalFileSystemSkillProvider(tmp_path, max_file_bytes=8)

The fixture holds the same skill, with a limit small enough that the skill exceeds it.

Test doubles

InMemorySkillProvider is a real, spec-compliant provider backed by a dict. It passes the conformance suite above. Prefer it to an AsyncMock: a mock agrees with whatever the test asserts, including the assertions that are wrong.

import asyncio

from agentskills_core import SkillRegistry
from agentskills_testing import InMemorySkillProvider, build_skill


async def main() -> None:
  provider = InMemorySkillProvider({
    "incident-response": build_skill(
      "incident-response",
      references={"severity-levels.md": b"SEV1 is customer-visible.\n"},
    )
  })
  registry = SkillRegistry()
  await registry.register_all(provider)
  print(await registry.get_skill("incident-response").get_body())


asyncio.run(main())

A string value is taken as the body, for the common case where the content does not matter:

provider = InMemorySkillProvider({"a": "body of a", "b": "body of b"})
provider.add("c")  # a default skill named "c"

build_skill() produces frontmatter that passes validate_skill(), so a test that does not care about metadata does not have to invent any. render_skill_md(skill) renders one back to SKILL.md text, which is how you populate a temporary directory for the filesystem provider.

To emulate a backend that cannot enumerate (a static HTTP host without a manifest, for instance), pass supports_resource_listing=False, or supports_discovery=False, or both.

Fixtures

Installing the package registers a pytest plugin, so these are available with no import and no conftest.py entry:

Fixture What you get
sample_skill An InMemorySkill with one reference, one script, and one asset
skill_provider An InMemorySkillProvider serving sample_skill
skill_registry A SkillRegistry with that skill already registered
import pytest


@pytest.mark.asyncio
async def test_my_agent(skill_registry):
    catalog = await skill_registry.get_skills_catalog()
    assert "incident-response" in catalog

License

MIT. See LICENSE.

Part of the Agent Skills SDK.

Metadata

Release files for agentskills-testing 0.8.0

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

Source distribution (sdist)

Source distribution for agentskills-testing 0.8.0
File Size Uploaded Provenance Actions
agentskills_testing-0.8.0.tar.gz 11.8 kB With provenance Details for agentskills_testing-0.8.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for agentskills-testing 0.8.0
File Interpreter ABI Platform Actions
agentskills_testing-0.8.0-py3-none-any.whl Python 3 none any Details for agentskills_testing-0.8.0-py3-none-any.whl

Total release size: 24.8 kB

Release files / agentskills_testing-0.8.0.tar.gz

Download URL agentskills_testing-0.8.0.tar.gz
Size 11.8 kB
Tags Source
SHA-256 checksum
How to use checksums
f7496ed1602718f5f5cd63d1cf655c88dd6ad559e70db481859e8a918c78d9a9
BLAKE2b-256 checksum
How to use checksums
b6d14d2a8bd3125cc44d17d924a07746a5bac7cb70256d6563d8d9521a916b10
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 Oct 30, 2026.

Transparency log

Release files / agentskills_testing-0.8.0-py3-none-any.whl

Download URL agentskills_testing-0.8.0-py3-none-any.whl
Size 13.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d435a8f7a18fd9dfa8ad76d5f56623c8f8f67bda4329730f5d6f588d55059ca2
BLAKE2b-256 checksum
How to use checksums
6fd4a04fee30f52db44b6e583efff7fccc032f804a2f132feb9cefb0d562efeb
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 Oct 30, 2026.

Transparency log

Security for agentskills-testing 0.8.0

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

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for agentskills_testing-0.8.0.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 Oct 30, 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