Search code examples
pythonpytestrpcmonkeypatchingjson-rpc

Unable to monkeypatch an rpc server class method


I need to monkeypatch a class method that is decorated with the @method annotation of the jsonrpcserver library. The class is implementing a rpc server that is started as an asyncio server and is launched using a pytest fixture like this

# conftest.py
@pytest.fixture(autouse=True, scope="module")
@pytest.mark.asyncio
async def rpc_server(...):

    rpc_server = RpcServer(
        addr="127.0.0.1",
        port=9500,
        ...
    )

    task = asyncio.create_task(rpc_server.start())
    yield rpc_server
    task.cancel()

The test should monkeypatch one of the method of the RpcServer class

# test_rpc.py
@pytest.mark.asyncio
async def test_rpc_server_exception(
    rpc_server: RpcServer,
    ...
    monkeypatch: MonkeyPatch,
):
    async def raise_runtime_error():
        raise RuntimeError()

    monkeypatch.setattr(
        RpcServer, "method_to_be_patched", raise_runtime_error, raising=True
    )

    ... # making an rpc request to launch method_to_be_patched

    assert ...

method_to_be_patched is invoked by async_dispatch of the jsonrpcserver library once a new request is received and looks like this

# rpc_server.py
@method
async def method_to_be_patched(self, ...) -> str:
    ...
    return ...

The problem is that monkeypatch is not patching anything and the test pass without raising any exception (like I need to). I've tried to monkeypatch RpcServer and the instance yield from the pytest fixture without any success yet by debugging it seems that the class method correctly points to the dummy function but still the original one is invoked.

EDIT: the issue arises because of python imports work. As far as I understood when importing like from ... import ... I'm creating a new reference so basically I'm patching the reference created from test_rpc.py and not the one in rpc_server.py (correct me if I'm wrong).

So I tried

# test_rpc.py
@pytest.mark.asyncio
async def test_rpc_server_exception(
    rpc_server: RpcServer,
    ...
    monkeypatch: MonkeyPatch,
):
    async def raise_runtime_error():
        raise RuntimeError()
    import network # the package containing rpc_server.py
    monkeypatch.setattr(
        network.rpc_server.RpcServer, "method_to_be_patched", raise_runtime_error, raising=True
    )

    ... # making an rpc request to launch method_to_be_patched

    assert ...

but still not getting the intended behaviour.

The project tree is like this

/src
    |rpc_server.py
/test
    |conftest.py
    /e2e
        |test_rpc.py

Solution

  • The solution is to monkeypatch where the method is invoked so since I'm using jsonrpcserver here I had to monkeypatch the call method defined inside the async_dispatch module and now it is working as I expected