Files
semantica/tests/visualization/test_optional_dependencies.py
T
Luan Taraschi c5d382ee81 test(visualization): isolate optional dependency mocks (#897)
* test(visualization): isolate optional dependency mocks

* test(visualization): stop requiring Plotly in unit tests

Removing the global sys.modules stubs left the tests that patch
`...go.Bar`, or call a visualizer, with nothing standing in for the
module level `px` and `go` aliases. Those are None when Plotly is
missing, so patch resolution and _check_dependencies() both failed.

Add a helper that substitutes a double only for the aliases that are
None, leaving the real module in place when Plotly is installed.

---------
2026-08-20 17:58:20 +05:00

116 lines
4.2 KiB
Python

import importlib
import sys
import unittest
from contextlib import contextmanager
from unittest.mock import patch
import numpy as np
from tests.visualization._plotly_doubles import plotly_doubles
@contextmanager
def import_without(module_name, *dependencies):
"""Import a module with selected optional dependencies unavailable."""
package_name, attribute = module_name.rsplit(".", 1)
package = importlib.import_module(package_name)
missing = object()
original_module = sys.modules.pop(module_name, missing)
original_attribute = getattr(package, attribute, missing)
try:
with patch.dict(sys.modules, {name: None for name in dependencies}):
yield importlib.import_module(module_name)
finally:
sys.modules.pop(module_name, None)
if original_module is not missing:
sys.modules[module_name] = original_module
if original_attribute is missing:
package.__dict__.pop(attribute, None)
else:
setattr(package, attribute, original_attribute)
class TestOptionalDependencies(unittest.TestCase):
def test_embedding_visualizer_without_umap(self):
"""Test EmbeddingVisualizer behavior when umap is missing."""
with import_without(
"semantica.visualization.embedding_visualizer", "umap"
) as module:
with plotly_doubles(module), patch.object(module, "PCA") as mock_pca_class:
mock_pca_class.return_value.fit_transform.return_value = np.zeros((4, 2))
viz = module.EmbeddingVisualizer()
embeddings = np.array([[0, 1, 2], [1, 0, 3], [0, 0, 0], [1, 1, 1]])
viz.visualize_2d_projection(embeddings, method="umap")
mock_pca_class.assert_called()
def test_ontology_visualizer_without_graphviz(self):
"""Test OntologyVisualizer behavior when graphviz is missing."""
with import_without(
"semantica.visualization.ontology_visualizer", "graphviz"
) as module:
viz = module.OntologyVisualizer()
ontology = {
"classes": [
{"name": "A", "label": "A"},
{"name": "B", "label": "B", "parent": "A"},
]
}
with self.assertRaises(module.ProcessingError) as cm:
viz.visualize_hierarchy(ontology, output="dot", file_path="test.dot")
self.assertIn("Graphviz is required for DOT export", str(cm.exception))
def test_analytics_visualizer_without_plotly(self):
"""Test AnalyticsVisualizer behavior when plotly is missing."""
with import_without(
"semantica.visualization.analytics_visualizer",
"plotly",
"plotly.express",
"plotly.graph_objects",
) as module:
viz = module.AnalyticsVisualizer()
with self.assertRaises(module.ProcessingError) as cm:
viz.visualize_centrality_rankings({})
self.assertIn("Plotly is required", str(cm.exception))
def test_semantic_network_visualizer_without_plotly(self):
"""Test SemanticNetworkVisualizer behavior when plotly is missing."""
with import_without(
"semantica.visualization.semantic_network_visualizer",
"plotly",
"plotly.express",
"plotly.graph_objects",
) as module:
viz = module.SemanticNetworkVisualizer()
with self.assertRaises(module.ProcessingError) as cm:
viz.visualize_network({})
self.assertIn("Plotly is required", str(cm.exception))
def test_temporal_visualizer_without_plotly(self):
"""Test TemporalVisualizer behavior when plotly is missing."""
with import_without(
"semantica.visualization.temporal_visualizer",
"plotly",
"plotly.express",
"plotly.graph_objects",
) as module:
viz = module.TemporalVisualizer()
with self.assertRaises(module.ProcessingError) as cm:
viz.visualize_timeline({"events": []})
self.assertIn("Plotly is required", str(cm.exception))
if __name__ == "__main__":
unittest.main()