Skip to content

tests(#2406)#2575

Open
bruno-portfolio wants to merge 2 commits intogee-community:masterfrom
bruno-portfolio:tests-2406
Open

tests(#2406)#2575
bruno-portfolio wants to merge 2 commits intogee-community:masterfrom
bruno-portfolio:tests-2406

Conversation

@bruno-portfolio
Copy link
Contributor

Summary

Adds comprehensive unit test coverage for multiple modules as requested in #2406.

Changes

Module Tests Notes
test_cli.py 5 CLI with CliRunner
test_plot.py 44 bar_chart, line_chart, histogram, pie_chart
test_osm.py 39 All OSM functions
test_ml.py 25 sklearn integration
test_timelapse.py 46 GIF manipulation, timeseries
test_ai.py 71 AI module with mocked dependencies
test_foliumap.py 32 Skipped (module state issues)
test_kepler.py 28 Skipped (keplergl not available)
test_deck.py 19 ✅ Passing
test_plotlymap.py 27 ✅ Passing
test_maplibregl.py 23 Skipped (module state issues)
Total 398

Design Notes

  • Map modules use real widget libraries but mock Earth Engine calls
  • Module-level basemap initialization makes full mocking impractical
  • Graceful skip pattern for optional dependencies

Checklist

  • Tests pass locally
  • Follows Google Python Style Guide
  • No external I/O calls
  • pre-commit hooks pass
  • isort verified

@jdbcode
Copy link
Member

jdbcode commented Feb 3, 2026

Thanks @bruno-portfolio! @schwehr can you take a look and see if this is what you had in mind?

@schwehr
Copy link
Collaborator

schwehr commented Feb 3, 2026

Thank you @bruno-portfolio! This is a great reference PR to start from.

First, apologies that docs/contributing.md is so out of date. I'll try to cover the process to get tests into geemap here and then we'll try to get the contributing.md updated.

Let's start with the simple and working test_cli.py. I verified it works for me locally:

python -m unittest discover -v tests test_cli.py
test_main_help_flag_shows_help (test_cli.TestMain.test_main_help_flag_shows_help) ... ok
test_main_help_flag_shows_usage (test_cli.TestMain.test_main_help_flag_shows_usage) ... ok
test_main_no_args_outputs_click_docs_reference (test_cli.TestMain.test_main_no_args_outputs_click_docs_reference) ... ok
test_main_no_args_outputs_message (test_cli.TestMain.test_main_no_args_outputs_message) ... ok
test_main_no_args_returns_zero (test_cli.TestMain.test_main_no_args_returns_zero) ... ok

----------------------------------------------------------------------
Ran 5 tests in 0.006s

OK

I've added comments you can use for that new PR.

@bruno-portfolio
Copy link
Contributor Author

Thanks for the detailed feedback @schwehr!

I've applied your suggestions and created a separate PR with the corrected 'test_cli.py': #2579

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please copy this file to a fresh branch and make a new PR with just test_deck.py

@schwehr
Copy link
Collaborator

schwehr commented Feb 4, 2026

Now that test_cli.py is in, how about test_deck.py? I confirmed it works locally for me:

source .venv/bin/activate

pip install pydeck

python -m unittest discover -v tests test_deck.py
test_add_basemap_valid (test_deck.TestAddBasemap.test_add_basemap_valid) ... ok
test_add_ee_layer_invalid_type_raises (test_deck.TestAddEeLayer.test_add_ee_layer_invalid_type_raises) ... ok
test_addlayer_alias (test_deck.TestAddEeLayer.test_addlayer_alias) ... ok
test_add_gdf_invalid_type_raises (test_deck.TestAddGdf.test_add_gdf_invalid_type_raises) ... ok
test_add_geojson_file_not_found_raises (test_deck.TestAddGeojson.test_add_geojson_file_not_found_raises) ... ok
test_add_kml_file_not_found_raises (test_deck.TestAddKml.test_add_kml_file_not_found_raises) ... ok
test_add_layer_pydeck_layer (test_deck.TestAddLayer.test_add_layer_pydeck_layer) ... ok
test_add_layer_url (test_deck.TestAddLayer.test_add_layer_url) ... ok
test_add_layer_with_name (test_deck.TestAddLayer.test_add_layer_with_name) ... ok
test_add_shp_file_not_found_raises (test_deck.TestAddShp.test_add_shp_file_not_found_raises) ... ok
test_add_vector_file_not_found_raises (test_deck.TestAddVector.test_add_vector_file_not_found_raises) ... ok
test_layer_init_default (test_deck.TestLayerInit.test_layer_init_default) ... ok
test_layer_init_hexagon (test_deck.TestLayerInit.test_layer_init_hexagon) ... ok
test_layer_init_with_data (test_deck.TestLayerInit.test_layer_init_with_data) ... ok
test_map_init_custom_center (test_deck.TestMapInit.test_map_init_custom_center) ... ok
test_map_init_custom_height_width (test_deck.TestMapInit.test_map_init_custom_height_width) ... ok
test_map_init_custom_map_style (test_deck.TestMapInit.test_map_init_custom_map_style) ... ok
test_map_init_custom_zoom (test_deck.TestMapInit.test_map_init_custom_zoom) ... ok
test_map_init_default_params (test_deck.TestMapInit.test_map_init_default_params) ... ok

----------------------------------------------------------------------
Ran 19 tests in 3.183s

OK

Copy link
Collaborator

@schwehr schwehr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome to have test_deck.py in. How about test_plotlymap.py next?

@@ -0,0 +1,262 @@
"""Tests for the plotlymap module."""

from __future__ import annotations
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed?

except ImportError:
PLOTLY_AVAILABLE = False

PLOTLYMAP_MODULE = None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove:

PLOTLYMAP_MODULE = None
IMPORT_ERROR = None

IMPORT_ERROR = None


def get_plotlymap():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this function

class PlotlymapTestCase(unittest.TestCase):

@classmethod
def setUpClass(cls) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove setUpClass, tearDownClass, and setUp



@unittest.skipUnless(PLOTLY_AVAILABLE, "plotly not available")
class PlotlymapTestCase(unittest.TestCase):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename PlotlymapTestCase --> PlotlymapTest

def test_add_controls_string(self) -> None:
with mock.patch("geemap.coreutils.ee_initialize"):
m = self.plotlymap.Map(ee_initialize=False)
m.add_controls("drawline")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and the tests below should be able to check that the action was taken.

url="https://tile.example.com/{z}/{x}/{y}.png",
name="Test Layer",
attribution="Test",
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There needs to be an assert at the end that tests to see if the action worked.

with mock.patch("geemap.coreutils.ee_initialize"):
m = self.plotlymap.Map(ee_initialize=False)
layer = go.Scattermapbox(lat=[37.8], lon=[-122.4], name="test")
m.add_layer(layer, name="Test Layer")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need an assert

with mock.patch("geemap.coreutils.ee_initialize"):
m = self.plotlymap.Map(ee_initialize=False)
layers = m.get_layers()
self.assertIsInstance(layers, dict)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the expected contents of layers in the default setup? What can be asserted? Same for the follow up tests.

"value": [1.0, 0.5],
}
)
m.add_heatmap(df)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert something about the map that tests if the heatmap was added

Copy link
Collaborator

@schwehr schwehr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about doing test_foliumap.py next.

Mostly the same comments before. A notable new thing is using with to create temps and have them automatically cleanup when the code exists the with scope.

except ImportError:
FOLIUM_AVAILABLE = False

FOLIUMAP_MODULE = None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove these lines

FOLIUMAP_MODULE = None
IMPORT_ERROR = None

IMPORT_ERROR = None


def get_foliumap():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this function and put the imports above in the try with import folium

}
],
}
temp_path = os.path.join(self.temp_dir, "test.geojson")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just do with tempfile.TemporaryDirectory() as tmpdir: here like in `test_common.py. And remove the temp dir stuff in the class setup.

def test_to_html_saves_file(self) -> None:
with mock.patch("geemap.coreutils.ee_initialize"):
m = self.foliumap.Map(ee_initialize=False)
temp_path = os.path.join(self.temp_dir, "test_map.html")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again use with to create the temp dir just for this test.

temp_path = os.path.join(self.temp_dir, "test_map.html")
m.to_html(temp_path)
self.assertTrue(os.path.exists(temp_path))

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please assert something about the content for the file that is written.

class FoliumapTestCase(unittest.TestCase):

@classmethod
def setUpClass(cls) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove all the setup and teardown methods

with mock.patch("geemap.coreutils.ee_initialize"):
with mock.patch("geemap.common.is_arcpy", return_value=False):
m = self.foliumap.Map(ee_initialize=False)
self.assertEqual(m.setCenter, m.set_center)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to test this. It's just an alias.

def test_zoom_to_bounds_valid_bounds(self) -> None:
with mock.patch("geemap.coreutils.ee_initialize"):
m = self.foliumap.Map(ee_initialize=False)
m.zoom_to_bounds([-122.5, 37.5, -122.0, 38.0])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert something about the m instance that changed from the call.

tiles="https://tile.example.com/{z}/{x}/{y}.png",
name="Test Layer",
attribution="Test",
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert something for the change in m. Same for all the other tests the do a change. They all have to assert something about the change.

def test_setcontrolvisibility_alias(self) -> None:
with mock.patch("geemap.coreutils.ee_initialize"):
m = self.foliumap.Map(ee_initialize=False)
self.assertEqual(m.setControlVisibility, m.set_control_visibility)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also remove this alias check

@schwehr
Copy link
Collaborator

schwehr commented Feb 12, 2026

How about picking another test and doing the same style cleanup? Maybe test_osm.py?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

Comments