Skip to main content

Do testing and stuff

Project description

snektest

A Python testing framework with first-class support for async and static typing.

Installation

uv add snektest

Quick Start

Create a test_*.py file:

from collections.abc import AsyncGenerator

from snektest import test, load_fixture
from snektest.assertions import assert_eq

async def provide_number() -> AsyncGenerator[int, None]:
    yield 2

@test()
async def test_basic_math():
    given_number = await load_fixture(provide_number())
    result = given_number * 2
    assert_eq(result, 4)

@test()
def test_strings():
    assert_eq("hello".upper(), "HELLO")

Run your tests:

snektest

Features

Session Fixtures

Set up and tear down test dependencies with session-scoped fixtures:

from collections.abc import AsyncGenerator
from snektest import test, session_fixture, load_fixture
from snektest.assertions import assert_eq

@session_fixture()
async def connection_pool() -> AsyncGenerator[dict[str, str], None]:
    # Setup: runs once for all tests
    pool = {"host": "localhost", "status": "connected"}
    yield pool
    # Teardown: runs after all tests
    pool["status"] = "disconnected"

@test()
async def test_connection():
    pool = await load_fixture(connection_pool())
    assert_eq(pool["status"], "connected")

Rich Assertions

Get helpful error messages with custom assertions:

from snektest import test
from snektest.assertions import assert_eq, assert_true, assert_in

@test()
def test_with_assertions():
    assert_eq([1, 2, 3], [1, 2, 3])
    assert_true(5 > 3)
    assert_in("hello", ["hello", "world"])

Async Support

Write async tests as naturally as sync ones:

import asyncio
from snektest import test
from snektest.assertions import assert_eq

@test()
async def test_async_operation():
    await asyncio.sleep(0.1)
    result = "completed"
    assert_eq(result, "completed")

Parameterized Tests

Run the same test with different inputs:

from snektest import test, Param
from snektest.assertions import assert_eq

@test([
    Param(value="hello", name="lowercase"),
    Param(value="WORLD", name="uppercase"),
    Param(value="MiXeD", name="mixed"),
])
def test_string_length(value: str):
    assert_eq(len(value), 5)

# Test with multiple parameter combinations (cartesian product)
@test(
    [Param(value="hello", name="hello"), Param(value="hi", name="hi")],
    [Param(value=" world", name="world"), Param(value=" there", name="there")],
)
def test_concatenation(greeting: str, target: str):
    result = greeting + target
    assert_eq(result[0], greeting[0])

Static type checking

...

Running Tests

# Run all tests
snektest

# Run specific file
snektest tests/test_myfeature.py

# Run specific test
snektest tests/test_myfeature.py::test_something

How It Works

snektest discovers tests by walking your project directory and finding functions decorated with @test(). Tests run concurrently by default, with a producer thread discovering tests while consumer coroutines execute them in parallel.

The fixture system uses Python generators to handle setup and teardown—code before yield runs before the test, code after yield runs after. Function fixtures are created fresh for each test, while session fixtures are shared across all tests for efficiency.

Assertions Reference

All assertion functions accept an optional msg keyword argument for custom error messages.

Equality and Inequality

assert_eq(actual, expected, *, msg=None) - Assert that actual == expected

from snektest import test
from snektest.assertions import assert_eq

@test()
def test_equality():
    assert_eq(2 + 2, 4)
    assert_eq("hello", "hello")
    assert_eq([1, 2, 3], [1, 2, 3])

assert_ne(actual, expected, *, msg=None) - Assert that actual != expected

from snektest import test
from snektest.assertions import assert_ne

@test()
def test_inequality():
    assert_ne(2 + 2, 5)
    assert_ne("hello", "world")

Boolean Values

assert_true(value, *, msg=None) - Assert that value is True (identity check, not truthiness)

from snektest import test
from snektest.assertions import assert_true

@test()
def test_true():
    assert_true(5 > 3)
    assert_true(True)

assert_false(value, *, msg=None) - Assert that value is False (identity check, not falsiness)

from snektest import test
from snektest.assertions import assert_false

@test()
def test_false():
    assert_false(5 < 3)
    assert_false(False)

None Checks

assert_is_none(value, *, msg=None) - Assert that value is None

from snektest import test
from snektest.assertions import assert_is_none

@test()
def test_none():
    result = None
    assert_is_none(result)

assert_is_not_none(value, *, msg=None) - Assert that value is not None

from snektest import test
from snektest.assertions import assert_is_not_none

@test()
def test_not_none():
    result = "something"
    assert_is_not_none(result)

Identity Checks

assert_is(actual, expected, *, msg=None) - Assert that actual is expected

from snektest import test
from snektest.assertions import assert_is

@test()
def test_identity():
    a = [1, 2, 3]
    b = a
    assert_is(a, b)

assert_is_not(actual, expected, *, msg=None) - Assert that actual is not expected

from snektest import test
from snektest.assertions import assert_is_not

@test()
def test_not_identity():
    a = [1, 2, 3]
    b = [1, 2, 3]
    assert_is_not(a, b)

Comparisons

assert_lt(actual, expected, *, msg=None) - Assert that actual < expected

from snektest import test
from snektest.assertions import assert_lt

@test()
def test_less_than():
    assert_lt(3, 5)
    assert_lt("a", "b")

assert_gt(actual, expected, *, msg=None) - Assert that actual > expected

from snektest import test
from snektest.assertions import assert_gt

@test()
def test_greater_than():
    assert_gt(5, 3)
    assert_gt("b", "a")

assert_le(actual, expected, *, msg=None) - Assert that actual <= expected

from snektest import test
from snektest.assertions import assert_le

@test()
def test_less_or_equal():
    assert_le(3, 5)
    assert_le(5, 5)

assert_ge(actual, expected, *, msg=None) - Assert that actual >= expected

from snektest import test
from snektest.assertions import assert_ge

@test()
def test_greater_or_equal():
    assert_ge(5, 3)
    assert_ge(5, 5)

Membership

assert_in(member, container, *, msg=None) - Assert that member in container

from snektest import test
from snektest.assertions import assert_in

@test()
def test_membership():
    assert_in(2, [1, 2, 3])
    assert_in("hello", "hello world")
    assert_in("key", {"key": "value"})

assert_not_in(member, container, *, msg=None) - Assert that member not in container

from snektest import test
from snektest.assertions import assert_not_in

@test()
def test_not_membership():
    assert_not_in(5, [1, 2, 3])
    assert_not_in("foo", "hello world")

Type Checks

assert_isinstance(obj, classinfo, *, msg=None) - Assert that isinstance(obj, classinfo) is True

from snektest import test
from snektest.assertions import assert_isinstance

@test()
def test_instance():
    assert_isinstance("hello", str)
    assert_isinstance(42, int)
    assert_isinstance([1, 2], list)
    assert_isinstance(5, (int, float))

assert_not_isinstance(obj, classinfo, *, msg=None) - Assert that isinstance(obj, classinfo) is False

from snektest import test
from snektest.assertions import assert_not_isinstance

@test()
def test_not_instance():
    assert_not_isinstance("hello", int)
    assert_not_isinstance(42, str)

Length

assert_len(obj, expected_length, *, msg=None) - Assert that len(obj) == expected_length

from snektest import test
from snektest.assertions import assert_len

@test()
def test_length():
    assert_len([1, 2, 3], 3)
    assert_len("hello", 5)
    assert_len({"a": 1, "b": 2}, 2)

Exception Assertions

assert_raises(*expected_exceptions, msg=None) - Assert that code raises an expected exception

Use as a context manager to verify that a specific exception is raised:

from snektest import test, assert_raises, assert_eq

@test()
def test_division_by_zero():
    with assert_raises(ZeroDivisionError):
        1 / 0

@test()
def test_multiple_exception_types():
    # Can accept multiple exception types
    with assert_raises(ValueError, TypeError):
        int("not a number")

@test()
def test_access_exception():
    # Access the caught exception via the exception property
    with assert_raises(ValueError) as exc_info:
        raise ValueError("custom message")

    assert_eq(exc_info.exception.args[0], "custom message")

Unconditional Failure

fail(msg=None) - Raise an AssertionFailure unconditionally

from snektest import test, fail

@test()
def test_unreachable():
    if False:
        pass
    else:
        fail("This code path should never execute")

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

snektest-0.5.0.tar.gz (40.0 kB view details)

Uploaded Source

Built Distribution

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

snektest-0.5.0-py3-none-any.whl (24.3 kB view details)

Uploaded Python 3

File details

Details for the file snektest-0.5.0.tar.gz.

File metadata

  • Download URL: snektest-0.5.0.tar.gz
  • Upload date:
  • Size: 40.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.1

File hashes

Hashes for snektest-0.5.0.tar.gz
Algorithm Hash digest
SHA256 d8e1d7348d95a1f49a90de57f62d3fc59d99ffadc6216277e5e878b90440e930
MD5 29d0c1967c2cf88daf50f4b72c61dc43
BLAKE2b-256 dac8397c99b1c2913a16ad8c5db51d4acc82bb068d7e6c075a7a4cedd55869b0

See more details on using hashes here.

File details

Details for the file snektest-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: snektest-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 24.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.1

File hashes

Hashes for snektest-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 34edd2120634f0044ba060f9b39ef8316abf52b1195c41ca2fecd281e150df1d
MD5 2931fca9b4b428e0a8ea1aad00657a2b
BLAKE2b-256 110ef23902206f7829c9264870e32c129c8fffc7ec965a4ba7ff3cd0e98a5ccf

See more details on using hashes here.

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