How to add standard tests to an integration
When creating either a custom class for yourself or to publish in a LangChain integration, it is important to add standard tests to ensure it works as expected. This guide will show you how to add standard tests to each integration type.
Setup
First, let's install 2 dependencies:
langchain-core
will define the interfaces we want to import to define our custom tool.langchain-tests
will provide the standard tests we want to use, as well as pytest plugins necessary to run them. Recommended to pin to the latest version:
Because added tests in new versions of langchain-tests
can break your CI/CD pipelines, we recommend pinning the
version of langchain-tests
to avoid unexpected changes.
- Poetry
- Pip
If you followed the previous guide, you should already have these dependencies installed!
poetry add langchain-core
poetry add --group test langchain-tests==<latest_version>
poetry install --with test
pip install -U langchain-core langchain-tests
# install current package in editable mode
pip install --editable .
Add and configure standard tests
There are 2 namespaces in the langchain-tests
package:
- unit tests (
langchain_tests.unit_tests
): designed to be used to test the component in isolation and without access to external services - integration tests (
langchain_tests.integration_tests
): designed to be used to test the component with access to external services (in particular, the external service that the component is designed to interact with).
Both types of tests are implemented as pytest
class-based test suites.
By subclassing the base classes for each type of standard test (see below), you get all of the standard tests for that type, and you can override the properties that the test suite uses to configure the tests.
In order to run the tests in the same way as this guide, we recommend subclassing these classes in test files under two test subdirectories:
tests/unit_tests
for unit teststests/integration_tests
for integration tests
Implementing standard tests
In the following tabs, we show how to implement the standard tests for each component type:
- Chat models
- Vector stores
- Embeddings
- Tools
- Retrievers
To configure standard tests for a chat model, we subclass ChatModelUnitTests
and ChatModelIntegrationTests
. On each subclass, we override the following @property
methods to specify the chat model to be tested and the chat model's configuration:
Property | Description |
---|---|
chat_model_class | The class for the chat model to be tested |
chat_model_params | The parameters to pass to the chat |
model's constructor |
Additionally, chat model standard tests test a range of behaviors, from the most basic requirements (generating a response to a query) to optional capabilities like multi-modal support and tool-calling. For a test run to be successful:
- If a feature is intended to be supported by the model, it should pass;
- If a feature is not intended to be supported by the model, it should be skipped.
Tests for "optional" capabilities are controlled via a set of properties that can be overridden on the test model subclass.
You can see the entire list of configurable capabilities in the API references for unit tests and integration tests.
For example, to enable integration tests for image inputs, we can implement
@property
def supports_image_inputs(self) -> bool:
return True
on the integration test class.
Details on what tests are run, how each test can be skipped, and troubleshooting tips for each test can be found in the API references. See details:
Unit test example:
"""Test chat model integration."""
from typing import Type
from langchain_parrot_link.chat_models import ChatParrotLink
from langchain_tests.unit_tests import ChatModelUnitTests
class TestChatParrotLinkUnit(ChatModelUnitTests):
@property
def chat_model_class(self) -> Type[ChatParrotLink]:
return ChatParrotLink
@property
def chat_model_params(self) -> dict:
# These should be parameters used to initialize your integration for testing
return {
"model": "bird-brain-001",
"temperature": 0,
"parrot_buffer_length": 50,
}
Integration test example:
"""Test ChatParrotLink chat model."""
from typing import Type
from langchain_parrot_link.chat_models import ChatParrotLink
from langchain_tests.integration_tests import ChatModelIntegrationTests
class TestChatParrotLinkIntegration(ChatModelIntegrationTests):
@property
def chat_model_class(self) -> Type[ChatParrotLink]:
return ChatParrotLink
@property
def chat_model_params(self) -> dict:
# These should be parameters used to initialize your integration for testing
return {
"model": "bird-brain-001",
"temperature": 0,
"parrot_buffer_length": 50,
}
Here's how you would configure the standard tests for a typical vector store (using
ParrotVectorStore
as a placeholder):
Vector store tests do not have optional capabilities to be configured at this time.
from typing import Generator
import pytest
from langchain_parrot_link.vectorstores import ParrotVectorStore
from langchain_core.vectorstores import VectorStore
from langchain_tests.integration_tests import VectorStoreIntegrationTests
class TestParrotVectorStore(VectorStoreIntegrationTests):
@pytest.fixture()
def vectorstore(self) -> Generator[VectorStore, None, None]: # type: ignore
"""Get an empty vectorstore for unit tests."""
store = ParrotVectorStore(self.get_embeddings())
# note: store should be EMPTY at this point
# if you need to delete data, you may do so here
try:
yield store
finally:
# cleanup operations, or deleting data
pass
Configuring the tests consists of implementing pytest fixtures for setting up an empty vector store and tearing down the vector store after the test run ends.
Fixture | Description |
---|---|
vectorstore | A generator that yields an empty vector store for unit tests. The vector store is cleaned up after the test run ends. |
For example, below is the VectorStoreIntegrationTests
class for the Chroma
integration:
from typing import Generator
import pytest
from langchain_core.vectorstores import VectorStore
from langchain_tests.integration_tests.vectorstores import VectorStoreIntegrationTests
from langchain_chroma import Chroma
class TestChromaStandard(VectorStoreIntegrationTests):
@pytest.fixture()
def vectorstore(self) -> Generator[VectorStore, None, None]: # type: ignore
"""Get an empty vectorstore for unit tests."""
store = Chroma(embedding_function=self.get_embeddings())
try:
yield store
finally:
store.delete_collection()
pass
Note that before the initial yield
, we instantiate the vector store with an
embeddings object. This is a pre-defined
"fake" embeddings model
that will generate short, arbitrary vectors for documents. You can use a different
embeddings object if desired.
In the finally
block, we call whatever integration-specific logic is needed to
bring the vector store to a clean state. This logic is executed in between each test
(e.g., even if tests fail).
Details on what tests are run and troubleshooting tips for each test can be found in the API reference.
To configure standard tests for an embeddings model, we subclass EmbeddingsUnitTests
and EmbeddingsIntegrationTests
. On each subclass, we override the following @property
methods to specify the embeddings model to be tested and the embeddings model's configuration:
Property | Description |
---|---|
embeddings_class | The class for the embeddings model to be tested |
embedding_model_params | The parameters to pass to the embeddings model's constructor |
Details on what tests are run, how each test can be skipped, and troubleshooting tips for each test can be found in the API references. See details:
Unit test example:
"""Test embedding model integration."""
from typing import Type
from langchain_parrot_link.embeddings import ParrotLinkEmbeddings
from langchain_tests.unit_tests import EmbeddingsUnitTests
class TestParrotLinkEmbeddingsUnit(EmbeddingsUnitTests):
@property
def embeddings_class(self) -> Type[ParrotLinkEmbeddings]:
return ParrotLinkEmbeddings
@property
def embedding_model_params(self) -> dict:
return {"model": "nest-embed-001"}
Integration test example:
from typing import Type
from langchain_parrot_link.embeddings import ParrotLinkEmbeddings
from langchain_tests.integration_tests import EmbeddingsIntegrationTests
class TestParrotLinkEmbeddingsIntegration(EmbeddingsIntegrationTests):
@property
def embeddings_class(self) -> Type[ParrotLinkEmbeddings]:
return ParrotLinkEmbeddings
@property
def embedding_model_params(self) -> dict:
return {"model": "nest-embed-001"}
"""Test ParrotLink embeddings."""
from typing import Type
from langchain_parrot_link.embeddings import ParrotLinkEmbeddings
from langchain_tests.integration_tests import EmbeddingsIntegrationTests
class TestParrotLinkEmbeddingsIntegration(EmbeddingsIntegrationTests):
@property
def embeddings_class(self) -> Type[ParrotLinkEmbeddings]:
return ParrotLinkEmbeddings
@property
def embedding_model_params(self) -> dict:
return {"model": "nest-embed-001"}
To configure standard tests for a tool, we subclass ToolsUnitTests
and
ToolsIntegrationTests
. On each subclass, we override the following @property
methods
to specify the tool to be tested and the tool's configuration:
Property | Description |
---|---|
tool_constructor | The constructor for the tool to be tested, or an instantiated tool. |
tool_constructor_params | The parameters to pass to the tool (optional). |
tool_invoke_params_example | An example of the parameters to pass to the tool's invoke method. |
If you are testing a tool class and pass a class like MyTool
to tool_constructor
, you can pass the parameters to the constructor in tool_constructor_params
.
If you are testing an instantiated tool, you can pass the instantiated tool to tool_constructor
and do not
override tool_constructor_params
.
Details on what tests are run, how each test can be skipped, and troubleshooting tips for each test can be found in the API references. See details:
from typing import Type
from langchain_parrot_link.tools import ParrotTool
from langchain_tests.unit_tests import ToolsUnitTests
class TestParrotMultiplyToolUnit(ToolsUnitTests):
@property
def tool_constructor(self) -> Type[ParrotTool]:
return ParrotTool
@property
def tool_constructor_params(self) -> dict:
# if your tool constructor instead required initialization arguments like
# `def __init__(self, some_arg: int):`, you would return those here
# as a dictionary, e.g.: `return {'some_arg': 42}`
return {}
@property
def tool_invoke_params_example(self) -> dict:
"""
Returns a dictionary representing the "args" of an example tool call.
This should NOT be a ToolCall dict - i.e. it should not
have {"name", "id", "args"} keys.
"""
return {"a": 2, "b": 3}
from typing import Type
from langchain_parrot_link.tools import ParrotTool
from langchain_tests.integration_tests import ToolsIntegrationTests
class TestParrotMultiplyToolIntegration(ToolsIntegrationTests):
@property
def tool_constructor(self) -> Type[ParrotTool]:
return ParrotTool
@property
def tool_constructor_params(self) -> dict:
# if your tool constructor instead required initialization arguments like
# `def __init__(self, some_arg: int):`, you would return those here
# as a dictionary, e.g.: `return {'some_arg': 42}`
return {}
@property
def tool_invoke_params_example(self) -> dict:
"""
Returns a dictionary representing the "args" of an example tool call.
This should NOT be a ToolCall dict - i.e. it should not
have {"name", "id", "args"} keys.
"""
return {"a": 2, "b": 3}
To configure standard tests for a retriever, we subclass RetrieversUnitTests
and
RetrieversIntegrationTests
. On each subclass, we override the following @property
methods
Property | Description |
---|---|
retriever_constructor | The class for the retriever to be tested |
retriever_constructor_params | The parameters to pass to the retriever's constructor |
retriever_query_example | An example of the query to pass to the retriever's invoke method |
Details on what tests are run and troubleshooting tips for each test can be found in the API reference.
from typing import Type
from langchain_parrot_link.retrievers import ParrotRetriever
from langchain_tests.integration_tests import (
RetrieversIntegrationTests,
)
class TestParrotRetriever(RetrieversIntegrationTests):
@property
def retriever_constructor(self) -> Type[ParrotRetriever]:
"""Get an empty vectorstore for unit tests."""
return ParrotRetriever
@property
def retriever_constructor_params(self) -> dict:
return {"k": 2}
@property
def retriever_query_example(self) -> str:
"""
Returns a str representing the "query" of an example retriever call.
"""
return "example query"
Running the tests
You can run these with the following commands from your project root
- Poetry
- Pip
# run unit tests without network access
poetry run pytest --disable-socket --allow-unix-socket --asyncio-mode=auto tests/unit_tests
# run integration tests
poetry run pytest --asyncio-mode=auto tests/integration_tests
# run unit tests without network access
pytest --disable-socket --allow-unix-socket --asyncio-mode=auto tests/unit_tests
# run integration tests
pytest --asyncio-mode=auto tests/integration_tests
Test suite information and troubleshooting
For a full list of the standard test suites that are available, as well as information on which tests are included and how to troubleshoot common issues, see the Standard Tests API Reference.
You can see troubleshooting guides under the individual test suites listed in that API Reference. For example,
here is the guide for ChatModelIntegrationTests.test_usage_metadata
.