diff --git a/providers/common/ai/tests/unit/common/ai/hooks/test_langchain.py b/providers/common/ai/tests/unit/common/ai/hooks/test_langchain.py index ed2f95819377a..646f72aa10779 100644 --- a/providers/common/ai/tests/unit/common/ai/hooks/test_langchain.py +++ b/providers/common/ai/tests/unit/common/ai/hooks/test_langchain.py @@ -16,6 +16,7 @@ # under the License. from __future__ import annotations +import sys from unittest.mock import MagicMock, patch import pytest @@ -23,6 +24,28 @@ from airflow.providers.common.ai.hooks.langchain import LangChainHook +@pytest.fixture(autouse=True) +def _stub_langchain_modules(): + # langchain is an optional dep; stub sys.modules so @patch can resolve + # langchain.* targets without it being installed. + # Submodule entries are derived from parent mock attributes so @patch + # (which resolves via getattr) and the hook's lazy imports (which read + # sys.modules["langchain.chat_models"]) see the same object. + lc = MagicMock() + lc_core = MagicMock() + mocks = { + "langchain": lc, + "langchain.chat_models": lc.chat_models, + "langchain.embeddings": lc.embeddings, + "langchain_core": lc_core, + "langchain_core.embeddings": lc_core.embeddings, + "langchain_core.language_models": lc_core.language_models, + "langchain_core.language_models.chat_models": lc_core.language_models.chat_models, + } + with patch.dict(sys.modules, mocks): + yield + + def _conn(password: str = "", host: str = "", extra: dict | None = None) -> MagicMock: mock_conn = MagicMock() mock_conn.password = password