Skip to content

Testing

Call the method directly

@method returns your function unchanged, so a test can call it like any other function and compare the result:

from jsonrpcserver import Error, Result, Success, method


@method
def divide(a: float, b: float) -> Result:
    if b == 0:
        return Error(1, "Can't divide by zero")
    return Success(a / b)


def test_divide() -> None:
    assert divide(6, 3) == Success(2)
    assert divide(1, 0) == Error(1, "Can't divide by zero")


test_divide()

Success and Error results compare equal when they hold the same values.

Test through dispatch

To test what the client sees, including the JSON-RPC parts and the parameter check, call dispatch_to_serializable with a dict of methods. It gives a dict, which is easier to compare than a string:

from jsonrpcserver import dispatch_to_serializable


def test_divide_request() -> None:
    response = dispatch_to_serializable(
        '{"jsonrpc": "2.0", "method": "divide", "params": [1, 0], "id": 1}',
        {"divide": divide},
    )
    assert response == {
        "jsonrpc": "2.0",
        "error": {"code": 1, "message": "Can't divide by zero"},
        "id": 1,
    }


def test_divide_wrong_params() -> None:
    response = dispatch_to_serializable(
        '{"jsonrpc": "2.0", "method": "divide", "params": [1], "id": 1}',
        {"divide": divide},
    )
    assert response is not None
    assert response["error"]["code"] == -32602


test_divide_request()
test_divide_wrong_params()

Passing the dict keeps the test to the methods you meant. Without it, dispatch uses every method registered with @method anywhere in the test process.

The global methods

@method adds to one dict for the whole process, jsonrpcserver.methods.global_methods. In a test suite, every test module that's imported adds to it. A later @method with the same name replaces an earlier one without a warning. If two test modules define a method called ping, whichever is imported last wins.

Either pass methods explicitly in tests, as above, or give test methods unique names. To start a test with no registered methods, clear the dict and put it back afterwards. With pytest:

from typing import Any, Dict, Iterator

from jsonrpcserver.methods import global_methods


def clean_methods() -> Iterator[Dict[str, Any]]:  # Decorate with @pytest.fixture.
    saved = dict(global_methods)
    global_methods.clear()
    yield global_methods
    global_methods.clear()
    global_methods.update(saved)

Async methods

An async method is a coroutine function, so run it with asyncio.run or pytest-asyncio's @pytest.mark.asyncio:

import asyncio


async def ping() -> Result:
    return Success("pong")


assert asyncio.run(ping()) == Success("pong")

Logged errors

An exception that a method doesn't catch is logged, and the client gets a bare Internal error. To check that in a test, use pytest's caplog fixture and look for a record from the jsonrpcserver.dispatcher logger. See Errors and logging.