Getting Started

This guide will walk you through the process of writing your first test case using pytest-lsp.

If you have not done so already, you can install the pytest-lsp package using pip:

pip install pytest-lsp

A Simple Language Server

Before we can write any tests, we need a language server to test! For the purposes of this example we’ll write a simple language server in Python using the pygls library but note that pytest-lsp should work with language servers written in any language or framework.

The following server implements the textDocument/completion method

from lsprotocol.types import TEXT_DOCUMENT_COMPLETION
from lsprotocol.types import CompletionItem
from lsprotocol.types import CompletionParams
from pygls.server import LanguageServer

server = LanguageServer("hello-world", "v1")


@server.feature(TEXT_DOCUMENT_COMPLETION)
def completion(ls: LanguageServer, params: CompletionParams):
    return [
        CompletionItem(label="hello"),
        CompletionItem(label="world"),
    ]

Copy and paste the above code into a file named server.py.

A Simple Test Case

Now we can go ahead and test it. Copy the following code and save it into a file named test_server.py, in the same directory as the server.py file you created in the previous step.

import sys

from lsprotocol.types import ClientCapabilities
from lsprotocol.types import CompletionList
from lsprotocol.types import CompletionParams
from lsprotocol.types import InitializeParams
from lsprotocol.types import Position
from lsprotocol.types import TextDocumentIdentifier

import pytest
import pytest_lsp
from pytest_lsp import ClientServerConfig
from pytest_lsp import LanguageClient


@pytest_lsp.fixture(
    config=ClientServerConfig(server_command=[sys.executable, "server.py"]),
)
async def client(lsp_client: LanguageClient):
    # Setup
    params = InitializeParams(capabilities=ClientCapabilities())
    await lsp_client.initialize_session(params)

    yield

    # Teardown
    await lsp_client.shutdown_session()

This creates a pytest fixture named client, it uses the given server_command to automatically launch the server in a background process and connect it to a LanguageClient instance.

The setup code (everything before the yield) statement is executed before any tests run, calling initialize_session() on the client to open the LSP session.

Once all test cases have been called, the code after the yield statement will be called to shutdown the server and close the session

With the framework in place, we can go ahead and define our first test case

@pytest.mark.asyncio
async def test_completions(client: LanguageClient):
    """Ensure that the server implements completions correctly."""

    results = await client.text_document_completion_async(
        params=CompletionParams(
            position=Position(line=1, character=0),
            text_document=TextDocumentIdentifier(uri="file:///path/to/file.txt"),
        )
    )
    assert results is not None

    if isinstance(results, CompletionList):
        items = results.items
    else:
        items = results

    labels = [item.label for item in items]
    assert labels == ["hello", "world"]

All that’s left is to run the test suite!

$ pytest
================================================ test session starts ================================================
platform linux -- Python 3.11.3, pytest-7.2.0, pluggy-1.0.0
rootdir: /tmp/pytest-of-alex/pytest-38/test_getting_started_fail0, configfile: tox.ini
plugins: asyncio-0.21.0, typeguard-3.0.2, lsp-0.3.0
asyncio: mode=Mode.AUTO
collected 1 item

test_server.py E                                                                                              [100%]

====================================================== ERRORS =======================================================
________________________________________ ERROR at setup of test_completions _________________________________________

lsp_client = <pytest_lsp.client.LanguageClient object at 0x7fa2c4168310>

    @pytest_lsp.fixture(
        config=ClientServerConfig(server_command=[sys.executable, "server.py"]),
    )
    async def client(lsp_client: LanguageClient):
        # Setup
        params = InitializeParams(capabilities=ClientCapabilities())
>       await lsp_client.initialize_session(params)

test_server.py:21:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/var/home/alex/Projects/lsp-devtools/.env/lib64/python3.11/site-packages/pytest_lsp/client.py:137: in initialize_sess
ion
    response = await self.initialize_async(params)
/var/home/alex/Projects/lsp-devtools/.env/lib64/python3.11/site-packages/pygls/lsp/client.py:349: in initialize_async
    return await self.protocol.send_request_async("initialize", params)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <pytest_lsp.protocol.LanguageClientProtocol object at 0x7fa2c417a190>, method = 'initialize'
params = InitializeParams(capabilities=ClientCapabilities(workspace=None, text_document=None, notebook_document=None,
 window=No..., root_path=None, root_uri=None, initialization_options=None, trace=None, work_done_token=None, workspac
e_folders=None)

    async def send_request_async(self, method, params=None):
>       result = await super().send_request_async(method, params)
E       asyncio.exceptions.CancelledError: Server process exited with return code: 0

/var/home/alex/Projects/lsp-devtools/.env/lib64/python3.11/site-packages/pytest_lsp/protocol.py:42: CancelledError
============================================== short test summary info ==============================================
ERROR test_server.py::test_completions - asyncio.exceptions.CancelledError: Server process exited with return code: 0
================================================= 1 error in 1.15s ==================================================

We forgot to start the server! Add the following to the bottom of server.py.

if __name__ == "__main__":
    server.start_io()

Let’s try again

$ pytest
================================================ test session starts =================================================
platform linux -- Python 3.11.2, pytest-7.2.0, pluggy-1.0.0
rootdir: /var/home/user/Projects/lsp-devtools/lib/pytest-lsp, configfile: pyproject.toml
plugins: typeguard-2.13.3, asyncio-0.20.2, lsp-0.2.1
asyncio: mode=Mode.AUTO
collected 1 item

test_server.py .                                                                                               [100%]

================================================= 1 passed in 0.96s ==================================================

Much better!