This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 9c40220598d Remove mongomock dependency from mongo provider and
introduce MongoDbContainer testcontainer for testing. (#53773)
9c40220598d is described below
commit 9c40220598d706f26db62a967ad72fbc017a8946
Author: GPK <[email protected]>
AuthorDate: Sat Jul 26 13:25:45 2025 +0100
Remove mongomock dependency from mongo provider and introduce
MongoDbContainer testcontainer for testing. (#53773)
* Remove mongomock dependency from mongo provider
* Fix mongo integration tests
---
providers/mongo/pyproject.toml | 7 +-
providers/mongo/tests/conftest.py | 9 +
.../mongo/tests/unit/mongo/hooks/test_mongo.py | 249 ++++++++++-----------
scripts/ci/docker-compose/integration-mongo.yml | 4 +-
4 files changed, 132 insertions(+), 137 deletions(-)
diff --git a/providers/mongo/pyproject.toml b/providers/mongo/pyproject.toml
index 40c0f0595b3..39c36474a6c 100644
--- a/providers/mongo/pyproject.toml
+++ b/providers/mongo/pyproject.toml
@@ -59,10 +59,7 @@ requires-python = ">=3.10"
dependencies = [
"apache-airflow>=2.10.0",
"dnspython>=1.13.0",
- # mongomock does not work with pymongo 4.11.
- # See https://github.com/apache/airflow/issues/46215 and this one can be
removed after
- # https://github.com/mongomock/mongomock/issues/912 is closed by mongomock.
- "pymongo>=4.3.0,<4.11.0",
+ "pymongo>=4.13.2",
]
[dependency-groups]
@@ -71,7 +68,7 @@ dev = [
"apache-airflow-task-sdk",
"apache-airflow-devel-common",
# Additional devel dependencies (do not remove this line and add extra
development dependencies)
- "mongomock>=4.3.0",
+ "testcontainers>=4.12.0"
]
# To build docs:
diff --git a/providers/mongo/tests/conftest.py
b/providers/mongo/tests/conftest.py
index f56ccce0a3f..c6a2457e991 100644
--- a/providers/mongo/tests/conftest.py
+++ b/providers/mongo/tests/conftest.py
@@ -16,4 +16,13 @@
# under the License.
from __future__ import annotations
+import pytest
+from testcontainers.mongodb import MongoDbContainer
+
pytest_plugins = "tests_common.pytest_plugin"
+
+
[email protected](scope="session")
+def mongodb_container():
+ with MongoDbContainer("mongo:latest") as mongo:
+ yield mongo.get_connection_url()
diff --git a/providers/mongo/tests/unit/mongo/hooks/test_mongo.py
b/providers/mongo/tests/unit/mongo/hooks/test_mongo.py
index 2122895dea1..02e7b451e42 100644
--- a/providers/mongo/tests/unit/mongo/hooks/test_mongo.py
+++ b/providers/mongo/tests/unit/mongo/hooks/test_mongo.py
@@ -17,11 +17,9 @@
# under the License.
from __future__ import annotations
-import importlib
-from typing import TYPE_CHECKING
-
import pymongo
import pytest
+from pymongo import MongoClient
from pymongo.errors import CollectionInvalid
from airflow.exceptions import AirflowConfigException
@@ -30,20 +28,30 @@ from airflow.providers.mongo.hooks.mongo import MongoHook
from tests_common.test_utils.compat import connection_as_json
-if TYPE_CHECKING:
- from types import ModuleType
-mongomock: ModuleType | None
[email protected]
+def container_mongo_client(mongodb_container):
+ client = MongoClient(mongodb_container)
+
+ yield client
+
+ client.close()
+
+
[email protected]
+def mongo_collection(container_mongo_client):
+ collection = container_mongo_client.test.test_collection
-try:
- mongomock = importlib.import_module("mongomock")
-except ImportError:
- mongomock = None
+ yield collection
+
+ collection.drop()
@pytest.fixture(scope="module", autouse=True)
-def mongo_connections():
+def mongo_connections(mongodb_container):
"""Create MongoDB connections which use for testing purpose."""
+ (mongodb_container_host, mongodb_container_port) =
mongodb_container.split("@")[1].split(":")
+
connections = [
Connection(conn_id="mongo_default", conn_type="mongo", host="mongo",
port=27017),
Connection(
@@ -69,6 +77,15 @@ def mongo_connections():
),
# Mongo establishes connection during initialization, so we need to
have this connection
Connection(conn_id="fake_connection", conn_type="mongo", host="mongo",
port=27017),
+ Connection(
+ conn_id="mongo_default_with_test_container",
+ conn_type="mongo",
+ login="test",
+ password="test",
+ host=mongodb_container_host,
+ port=int(mongodb_container_port),
+ extra='{"allow_insecure": true, "ssl": false}',
+ ),
]
with pytest.MonkeyPatch.context() as mp:
@@ -77,23 +94,9 @@ def mongo_connections():
yield
-class MongoHookTest(MongoHook):
- """
- Extending hook so that a mockmongo collection object can be passed in
- to get_collection()
- """
-
- def __init__(self, mongo_conn_id="mongo_default", *args, **kwargs):
- super().__init__(mongo_conn_id=mongo_conn_id, *args, **kwargs)
-
- def get_collection(self, mock_collection, mongo_db=None):
- return mock_collection
-
-
[email protected](mongomock is None, reason="mongomock package not present")
class TestMongoHook:
def setup_method(self):
- self.hook = MongoHookTest(mongo_conn_id="mongo_default")
+ self.hook =
MongoHook(mongo_conn_id="mongo_default_with_test_container")
self.conn = self.hook.get_conn()
def teardown_method(self):
@@ -106,8 +109,10 @@ class TestMongoHook:
assert MongoHook("fake_connection").mongo_conn_id == "fake_connection"
def test_get_conn(self):
- assert self.hook.connection.port == 27017
- assert isinstance(self.conn, pymongo.MongoClient)
+ hook = MongoHook(mongo_conn_id="mongo_default")
+ conn = hook.get_conn()
+ assert hook.connection.port == 27017
+ assert isinstance(conn, pymongo.MongoClient)
def test_srv(self):
hook = MongoHook(mongo_conn_id="mongo_default_with_srv")
@@ -131,144 +136,130 @@ class TestMongoHook:
with pytest.raises(AirflowConfigException, match="srv URI should not
specify a port"):
MongoHook(mongo_conn_id="mongo_invalid_srv_with_port")
- def test_insert_one(self):
- collection = mongomock.MongoClient().db.collection
+ def test_insert_one(self, mongo_collection):
obj = {"test_insert_one": "test_value"}
- self.hook.insert_one(collection, obj)
+ self.hook.insert_one("test_collection", obj, mongo_db="test")
- result_obj = collection.find_one(filter=obj)
+ result_obj = mongo_collection.find_one(filter=obj)
assert obj == result_obj
- def test_insert_many(self):
- collection = mongomock.MongoClient().db.collection
+ def test_insert_many(self, mongo_collection):
objs = [{"test_insert_many_1": "test_value"}, {"test_insert_many_2":
"test_value"}]
- self.hook.insert_many(collection, objs)
+ self.hook.insert_many("test_collection", objs, mongo_db="test")
- result_objs = list(collection.find())
+ result_objs = list(mongo_collection.find())
assert len(result_objs) == 2
- def test_update_one(self):
- collection = mongomock.MongoClient().db.collection
+ def test_update_one(self, mongo_collection):
obj = {"_id": "1", "field": 0}
- collection.insert_one(obj)
+ mongo_collection.insert_one(obj)
filter_doc = obj
update_doc = {"$inc": {"field": 123}}
- self.hook.update_one(collection, filter_doc, update_doc)
+ self.hook.update_one("test_collection", filter_doc, update_doc,
mongo_db="test")
- result_obj = collection.find_one(filter="1")
+ result_obj = mongo_collection.find_one(filter="1")
assert result_obj["field"] == 123
- def test_update_one_with_upsert(self):
- collection = mongomock.MongoClient().db.collection
-
+ def test_update_one_with_upsert(self, mongo_collection):
filter_doc = {"_id": "1", "field": 0}
update_doc = {"$inc": {"field": 123}}
- self.hook.update_one(collection, filter_doc, update_doc, upsert=True)
+ self.hook.update_one("test_collection", filter_doc, update_doc,
upsert=True, mongo_db="test")
- result_obj = collection.find_one(filter="1")
+ result_obj = mongo_collection.find_one(filter="1")
assert result_obj["field"] == 123
- def test_update_many(self):
- collection = mongomock.MongoClient().db.collection
+ def test_update_many(self, mongo_collection):
obj1 = {"_id": "1", "field": 0}
obj2 = {"_id": "2", "field": 0}
- collection.insert_many([obj1, obj2])
+ mongo_collection.insert_many([obj1, obj2])
filter_doc = {"field": 0}
update_doc = {"$inc": {"field": 123}}
- self.hook.update_many(collection, filter_doc, update_doc)
+ self.hook.update_many("test_collection", filter_doc, update_doc,
mongo_db="test")
- result_obj = collection.find_one(filter="1")
+ result_obj = mongo_collection.find_one(filter="1")
assert result_obj["field"] == 123
- result_obj = collection.find_one(filter="2")
+ result_obj = mongo_collection.find_one(filter="2")
assert result_obj["field"] == 123
- def test_update_many_with_upsert(self):
- collection = mongomock.MongoClient().db.collection
-
+ def test_update_many_with_upsert(self, mongo_collection):
filter_doc = {"_id": "1", "field": 0}
update_doc = {"$inc": {"field": 123}}
- self.hook.update_many(collection, filter_doc, update_doc, upsert=True)
+ self.hook.update_many("test_collection", filter_doc, update_doc,
upsert=True, mongo_db="test")
- result_obj = collection.find_one(filter="1")
+ result_obj = mongo_collection.find_one(filter="1")
assert result_obj["field"] == 123
- def test_replace_one(self):
- collection = mongomock.MongoClient().db.collection
+ def test_replace_one(self, mongo_collection):
obj1 = {"_id": "1", "field": "test_value_1"}
obj2 = {"_id": "2", "field": "test_value_2"}
- collection.insert_many([obj1, obj2])
+ mongo_collection.insert_many([obj1, obj2])
obj1["field"] = "test_value_1_updated"
- self.hook.replace_one(collection, obj1)
+ self.hook.replace_one("test_collection", obj1, mongo_db="test")
- result_obj = collection.find_one(filter="1")
+ result_obj = mongo_collection.find_one(filter="1")
assert result_obj["field"] == "test_value_1_updated"
# Other document should stay intact
- result_obj = collection.find_one(filter="2")
+ result_obj = mongo_collection.find_one(filter="2")
assert result_obj["field"] == "test_value_2"
- def test_replace_one_with_filter(self):
- collection = mongomock.MongoClient().db.collection
+ def test_replace_one_with_filter(self, mongo_collection):
obj1 = {"_id": "1", "field": "test_value_1"}
obj2 = {"_id": "2", "field": "test_value_2"}
- collection.insert_many([obj1, obj2])
+ mongo_collection.insert_many([obj1, obj2])
obj1["field"] = "test_value_1_updated"
- self.hook.replace_one(collection, obj1, {"field": "test_value_1"})
+ self.hook.replace_one("test_collection", obj1, {"field":
"test_value_1"}, mongo_db="test")
- result_obj = collection.find_one(filter="1")
+ result_obj = mongo_collection.find_one(filter="1")
assert result_obj["field"] == "test_value_1_updated"
# Other document should stay intact
- result_obj = collection.find_one(filter="2")
+ result_obj = mongo_collection.find_one(filter="2")
assert result_obj["field"] == "test_value_2"
- def test_replace_one_with_upsert(self):
- collection = mongomock.MongoClient().db.collection
-
+ def test_replace_one_with_upsert(self, mongo_collection):
obj = {"_id": "1", "field": "test_value_1"}
- self.hook.replace_one(collection, obj, upsert=True)
+ self.hook.replace_one("test_collection", obj, upsert=True,
mongo_db="test")
- result_obj = collection.find_one(filter="1")
+ result_obj = mongo_collection.find_one(filter="1")
assert result_obj["field"] == "test_value_1"
- def test_replace_many(self):
- collection = mongomock.MongoClient().db.collection
+ def test_replace_many(self, mongo_collection):
obj1 = {"_id": "1", "field": "test_value_1"}
obj2 = {"_id": "2", "field": "test_value_2"}
- collection.insert_many([obj1, obj2])
+ mongo_collection.insert_many([obj1, obj2])
obj1["field"] = "test_value_1_updated"
obj2["field"] = "test_value_2_updated"
- self.hook.replace_many(collection, [obj1, obj2])
+ self.hook.replace_many("test_collection", [obj1, obj2],
mongo_db="test")
- result_obj = collection.find_one(filter="1")
+ result_obj = mongo_collection.find_one(filter="1")
assert result_obj["field"] == "test_value_1_updated"
- result_obj = collection.find_one(filter="2")
+ result_obj = mongo_collection.find_one(filter="2")
assert result_obj["field"] == "test_value_2_updated"
- def test_replace_many_with_upsert(self):
- collection = mongomock.MongoClient().db.collection
+ def test_replace_many_with_upsert(self, mongo_collection):
obj1 = {"_id": "1", "field": "test_value_1"}
obj2 = {"_id": "2", "field": "test_value_2"}
- self.hook.replace_many(collection, [obj1, obj2], upsert=True)
+ self.hook.replace_many("test_collection", [obj1, obj2], upsert=True,
mongo_db="test")
- result_obj = collection.find_one(filter="1")
+ result_obj = mongo_collection.find_one(filter="1")
assert result_obj["field"] == "test_value_1"
- result_obj = collection.find_one(filter="2")
+ result_obj = mongo_collection.find_one(filter="2")
assert result_obj["field"] == "test_value_2"
def test_create_uri_with_all_creds(self):
@@ -283,6 +274,7 @@ class TestMongoHook:
self.hook.connection.login = None
self.hook.connection.password = None
self.hook.connection.port = None
+ self.hook.connection.host = "mongo"
assert self.hook._create_uri() == "mongodb://mongo"
def test_create_uri_srv_true(self):
@@ -294,121 +286,118 @@ class TestMongoHook:
self.hook.connection.schema = "test_db"
assert self.hook._create_uri() ==
"mongodb+srv://test_user:test_password@test_host:1234/test_db"
- def test_delete_one(self):
- collection = mongomock.MongoClient().db.collection
+ def test_delete_one(self, mongo_collection):
obj = {"_id": "1"}
- collection.insert_one(obj)
+ mongo_collection.insert_one(obj)
- self.hook.delete_one(collection, {"_id": "1"})
+ self.hook.delete_one("test_collection", {"_id": "1"}, mongo_db="test")
- assert collection.count_documents({}) == 0
+ assert mongo_collection.count_documents({}) == 0
- def test_delete_many(self):
- collection = mongomock.MongoClient().db.collection
+ def test_delete_many(self, mongo_collection):
obj1 = {"_id": "1", "field": "value"}
obj2 = {"_id": "2", "field": "value"}
- collection.insert_many([obj1, obj2])
+ mongo_collection.insert_many([obj1, obj2])
- self.hook.delete_many(collection, {"field": "value"})
+ self.hook.delete_many("test_collection", {"field": "value"},
mongo_db="test")
- assert collection.count_documents({}) == 0
+ assert mongo_collection.count_documents({}) == 0
- def test_find_one(self):
- collection = mongomock.MongoClient().db.collection
+ def test_find_one(self, mongo_collection):
obj = {"test_find_one": "test_value"}
- collection.insert_one(obj)
+ mongo_collection.insert_one(obj)
- result_obj = self.hook.find(collection, {}, find_one=True)
+ result_obj = self.hook.find("test_collection", {}, find_one=True,
mongo_db="test")
result_obj = {result: result_obj[result] for result in result_obj}
assert obj == result_obj
- def test_find_many(self):
- collection = mongomock.MongoClient().db.collection
+ def test_find_many(self, mongo_collection):
objs = [{"_id": 1, "test_find_many_1": "test_value"}, {"_id": 2,
"test_find_many_2": "test_value"}]
- collection.insert_many(objs)
+ mongo_collection.insert_many(objs)
- result_objs = self.hook.find(mongo_collection=collection, query={},
projection={}, find_one=False)
+ result_objs = self.hook.find(
+ mongo_collection="test_collection", query={}, projection={},
find_one=False, mongo_db="test"
+ )
assert len(list(result_objs)) > 1
- def test_find_many_with_projection(self):
- collection = mongomock.MongoClient().db.collection
+ def test_find_many_with_projection(self, mongo_collection):
objs = [
{"_id": "1", "test_find_many_1": "test_value", "field_3": "a"},
{"_id": "2", "test_find_many_2": "test_value", "field_3": "b"},
]
- collection.insert_many(objs)
+ mongo_collection.insert_many(objs)
projection = {"_id": 0}
result_objs = self.hook.find(
- mongo_collection=collection, query={}, projection=projection,
find_one=False
+ mongo_collection="test_collection",
+ query={},
+ projection=projection,
+ find_one=False,
+ mongo_db="test",
)
assert "_id" not in result_objs[0]
- def test_aggregate(self):
- collection = mongomock.MongoClient().db.collection
+ def test_aggregate(self, mongo_collection):
objs = [
{"test_id": "1", "test_status": "success"},
{"test_id": "2", "test_status": "failure"},
{"test_id": "3", "test_status": "success"},
]
- collection.insert_many(objs)
+ mongo_collection.insert_many(objs)
aggregate_query = [{"$match": {"test_status": "success"}}]
- results = self.hook.aggregate(collection, aggregate_query)
+ results = self.hook.aggregate("test_collection", aggregate_query,
mongo_db="test")
assert len(list(results)) == 2
- def test_distinct(self):
- collection = mongomock.MongoClient().db.collection
+ def test_distinct(self, mongo_collection):
objs = [
{"test_id": "1", "test_status": "success"},
{"test_id": "2", "test_status": "failure"},
{"test_id": "3", "test_status": "success"},
]
- collection.insert_many(objs)
+ mongo_collection.insert_many(objs)
- results = self.hook.distinct(collection, "test_status")
+ results = self.hook.distinct("test_collection", "test_status",
mongo_db="test")
assert len(results) == 2
- def test_distinct_with_filter(self):
- collection = mongomock.MongoClient().db.collection
+ def test_distinct_with_filter(self, mongo_collection):
objs = [
{"test_id": "1", "test_status": "success"},
{"test_id": "2", "test_status": "failure"},
{"test_id": "3", "test_status": "success"},
]
- collection.insert_many(objs)
+ mongo_collection.insert_many(objs)
- results = self.hook.distinct(collection, "test_id", {"test_status":
"failure"})
+ results = self.hook.distinct(
+ "test_collection", "test_id", {"test_status": "failure"},
mongo_db="test"
+ )
assert len(results) == 1
- def test_create_standard_collection(self):
- mock_client = mongomock.MongoClient()
- self.hook.get_conn = lambda: mock_client
+ def test_create_standard_collection(self, container_mongo_client):
+ self.hook.get_conn = lambda: container_mongo_client
self.hook.connection.schema = "test_db"
collection =
self.hook.create_collection(mongo_collection="plain_collection")
assert collection.name == "plain_collection"
- assert "plain_collection" in
mock_client["test_db"].list_collection_names()
+ assert "plain_collection" in
container_mongo_client["test_db"].list_collection_names()
- def test_return_if_exists_true_returns_existing(self):
- mock_client = mongomock.MongoClient()
- self.hook.get_conn = lambda: mock_client
+ def test_return_if_exists_true_returns_existing(self,
container_mongo_client):
+ self.hook.get_conn = lambda: container_mongo_client
self.hook.connection.schema = "test_db"
first = self.hook.create_collection(mongo_collection="foo")
second = self.hook.create_collection(mongo_collection="foo",
return_if_exists=True)
assert first.full_name == second.full_name
- assert "foo" in mock_client["test_db"].list_collection_names()
+ assert "foo" in
container_mongo_client["test_db"].list_collection_names()
- def test_return_if_exists_false_raises(self):
- mock_client = mongomock.MongoClient()
- self.hook.get_conn = lambda: mock_client
+ def test_return_if_exists_false_raises(self, container_mongo_client):
+ self.hook.get_conn = lambda: container_mongo_client
self.hook.connection.schema = "test_db"
self.hook.create_collection(mongo_collection="bar")
diff --git a/scripts/ci/docker-compose/integration-mongo.yml
b/scripts/ci/docker-compose/integration-mongo.yml
index 0b35442fbc4..32a6b1c11f0 100644
--- a/scripts/ci/docker-compose/integration-mongo.yml
+++ b/scripts/ci/docker-compose/integration-mongo.yml
@@ -17,13 +17,13 @@
---
services:
mongo:
- image: mongo:3
+ image: mongo:latest
labels:
breeze.description: "Integration required for MongoDB hooks."
volumes:
- mongo-db-volume:/data/db
healthcheck:
- test: echo 'db.runCommand("ping").ok' | mongo localhost:27017/test
--quiet
+ test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test
--quiet
interval: 5s
timeout: 30s
retries: 50