11# pylint: disable=unused-argument,assignment-from-no-return
22import os
3- import urllib .error # pylint: disable=unused-import
3+ import urllib .error
44from unittest .mock import MagicMock , patch
55
66import matplotlib .pyplot as plt
7+ import numpy as np
78import pytest
8- from PIL import UnidentifiedImageError # pylint: disable=unused-import
9+ from PIL import UnidentifiedImageError
910
1011from rocketpy .plots .monte_carlo_plots import _MonteCarloPlots
1112from rocketpy .simulation import MonteCarlo
13+ from rocketpy .tools import import_optional_dependency
1214
1315plt .rcParams .update ({"figure.max_open_warning" : 0 })
1416
@@ -66,100 +68,53 @@ def __init__(self, latitude=32.990254, longitude=-106.974998):
6668 self .longitude = longitude
6769
6870
71+ @pytest .mark .parametrize (
72+ "background_type" ,
73+ [None , "satellite" , "street" , "terrain" , "CartoDB.Positron" ],
74+ )
6975@patch ("matplotlib.pyplot.show" )
70- def test_ellipses_background_none (mock_show ):
71- """Test default behavior when background=None (no background map displayed).
72-
73- Parameters
74- ----------
75- mock_show :
76- Mocks the matplotlib.pyplot.show() function to avoid showing the plots.
77- """
78- mock_monte_carlo = MockMonteCarlo (environment = SimpleEnvironment ())
79- # Test that background=None does not raise an error
80- result = mock_monte_carlo .plots .ellipses (background = None )
81- assert result is None
76+ def test_ellipses_background_types_display_successfully (mock_show , background_type ):
77+ """Test that different background map types display without errors.
8278
83-
84- @patch ("matplotlib.pyplot.show" )
85- def test_ellipses_background_satellite (mock_show ):
86- """Test using satellite map when background="satellite".
79+ This parameterized test verifies that the ellipses method works with:
80+ - None (no background map)
81+ - "satellite" (Esri.WorldImagery)
82+ - "street" (OpenStreetMap.Mapnik)
83+ - "terrain" (Esri.WorldTopoMap)
84+ - Custom provider (e.g., CartoDB.Positron)
8785
8886 Parameters
8987 ----------
90- mock_show :
91- Mocks the matplotlib.pyplot.show() function to avoid showing the plots.
88+ mock_show : unittest.mock.MagicMock
89+ Mocks the matplotlib.pyplot.show() function to avoid displaying plots.
90+ background_type : str or None
91+ The background map type to test.
9292 """
9393 mock_monte_carlo = MockMonteCarlo (environment = SimpleEnvironment ())
94- # Test that background="satellite" does not raise an error
95- result = mock_monte_carlo .plots .ellipses (background = "satellite" )
96- assert result is None
97-
9894
99- @patch ("matplotlib.pyplot.show" )
100- def test_ellipses_background_street (mock_show ):
101- """Test using street map when background="street".
95+ result = mock_monte_carlo .plots .ellipses (background = background_type )
10296
103- Parameters
104- ----------
105- mock_show :
106- Mocks the matplotlib.pyplot.show() function to avoid showing the plots.
107- """
108- mock_monte_carlo = MockMonteCarlo (environment = SimpleEnvironment ())
109- # Test that background="street" does not raise an error
110- result = mock_monte_carlo .plots .ellipses (background = "street" )
11197 assert result is None
11298
11399
114100@patch ("matplotlib.pyplot.show" )
115- def test_ellipses_background_terrain (mock_show ):
116- """Test using terrain map when background="terrain".
117-
118- Parameters
119- ----------
120- mock_show :
121- Mocks the matplotlib.pyplot.show() function to avoid showing the plots.
122- """
123- mock_monte_carlo = MockMonteCarlo (environment = SimpleEnvironment ())
124- # Test that background="terrain" does not raise an error
125- result = mock_monte_carlo .plots .ellipses (background = "terrain" )
126- assert result is None
127-
101+ def test_ellipses_image_takes_precedence_over_background (mock_show , tmp_path ):
102+ """Test that image parameter takes precedence over background parameter.
128103
129- @patch ("matplotlib.pyplot.show" )
130- def test_ellipses_background_custom_provider (mock_show ):
131- """Test using custom contextily provider for background.
104+ When both image and background are provided, the image should be used
105+ and the background map should not be downloaded.
132106
133107 Parameters
134108 ----------
135- mock_show :
136- Mocks the matplotlib.pyplot.show() function to avoid showing the plots.
109+ mock_show : unittest.mock.MagicMock
110+ Mocks the matplotlib.pyplot.show() function to avoid displaying plots.
111+ tmp_path : pathlib.Path
112+ Pytest fixture providing a temporary directory.
137113 """
138- mock_monte_carlo = MockMonteCarlo (environment = SimpleEnvironment ())
139- # Test that custom provider does not raise an error
140- result = mock_monte_carlo .plots .ellipses (background = "CartoDB.Positron" )
141- assert result is None
142-
143-
144- @patch ("matplotlib.pyplot.show" )
145- def test_ellipses_image_takes_precedence_over_background (mock_show , tmp_path ):
146- """Test that image takes precedence when both image and background are provided.
147114
148- Parameters
149- ----------
150- mock_show :
151- Mocks the matplotlib.pyplot.show() function to avoid showing the plots.
152- tmp_path :
153- pytest fixture providing a temporary directory.
154- """
155115 mock_monte_carlo = MockMonteCarlo (environment = SimpleEnvironment ())
156116 dummy_image_path = tmp_path / "dummy_image.png"
157117 dummy_image_path .write_bytes (b"dummy" )
158-
159- # Test that when both image and background are provided, image takes precedence
160- # This should not attempt to download background map
161- import numpy as np # pylint: disable=import-outside-toplevel
162-
163118 mock_image = np .zeros ((100 , 100 , 3 ), dtype = np .uint8 ) # RGB image
164119
165120 with patch ("imageio.imread" ) as mock_imread :
@@ -172,64 +127,67 @@ def test_ellipses_image_takes_precedence_over_background(mock_show, tmp_path):
172127
173128
174129@patch ("matplotlib.pyplot.show" )
175- def test_ellipses_background_no_environment (mock_show ):
176- """Test that ValueError is raised when MonteCarlo object has no environment attribute.
130+ def test_ellipses_background_raises_error_when_no_environment (mock_show ):
131+ """Test that ValueError is raised when environment attribute is missing .
177132
178- This test creates a MonteCarlo object without an environment attribute.
179- The function should raise ValueError when trying to fetch background map.
133+ Parameters
134+ ----------
135+ mock_show : unittest.mock.MagicMock
136+ Mocks the matplotlib.pyplot.show() function to avoid displaying plots.
180137 """
138+
181139 mock_monte_carlo = MockMonteCarlo (environment = None )
182140
183141 with pytest .raises (ValueError ) as exc_info :
184142 mock_monte_carlo .plots .ellipses (background = "satellite" )
185- assert "environment" in str (exc_info .value ).lower ()
186- assert "automatically fetching the background map" in str (exc_info .value )
143+
144+ error_message = str (exc_info .value ).lower ()
145+ assert "environment" in error_message
146+ assert "automatically fetching the background map" in error_message
187147
188148
189149@patch ("matplotlib.pyplot.show" )
190- def test_ellipses_background_no_latitude_longitude (mock_show ):
191- """Test that ValueError is raised when environment has no latitude or longitude attributes .
150+ def test_ellipses_background_raises_error_when_missing_coordinates (mock_show ):
151+ """Test that ValueError is raised when environment lacks latitude or longitude.
192152
193- This test creates a mock environment without latitude and longitude attributes.
194- The function should raise ValueError when trying to fetch background map.
153+ Parameters
154+ ----------
155+ mock_show : unittest.mock.MagicMock
156+ Mocks the matplotlib.pyplot.show() function to avoid displaying plots.
195157 """
196158
197- # Create a simple environment object without latitude and longitude
198159 class EmptyEnvironment :
199160 """Empty environment object without latitude and longitude attributes."""
200161
201- def __init__ (self ):
202- pass
203-
204162 mock_environment = EmptyEnvironment ()
205163 mock_monte_carlo = MockMonteCarlo (environment = mock_environment )
206164
207165 with pytest .raises (ValueError ) as exc_info :
208166 mock_monte_carlo .plots .ellipses (background = "satellite" )
209- assert "latitude" in str (exc_info .value ).lower ()
210- assert "longitude" in str (exc_info .value ).lower ()
211- assert "automatically fetching the background map" in str (exc_info .value )
167+
168+ error_message = str (exc_info .value ).lower ()
169+ assert "latitude" in error_message
170+ assert "longitude" in error_message
171+ assert "automatically fetching the background map" in error_message
212172
213173
214174@patch ("matplotlib.pyplot.show" )
215- def test_ellipses_background_contextily_not_installed (mock_show ):
175+ def test_ellipses_background_raises_error_when_contextily_not_installed (mock_show ):
216176 """Test that ImportError is raised when contextily is not installed.
217177
218178 Parameters
219179 ----------
220- mock_show :
221- Mocks the matplotlib.pyplot.show() function to avoid showing the plots.
180+ mock_show : unittest.mock.MagicMock
181+ Mocks the matplotlib.pyplot.show() function to avoid displaying plots.
222182 """
183+
223184 mock_monte_carlo = MockMonteCarlo (environment = SimpleEnvironment ())
224- from rocketpy .tools import (
225- import_optional_dependency as original_import , # pylint: disable=import-outside-toplevel
226- )
227185
228- # Create a mock function that only raises exception when importing contextily
229186 def mock_import_optional_dependency (name ):
187+ """Mock function that raises ImportError for contextily."""
230188 if name == "contextily" :
231189 raise ImportError ("No module named 'contextily'" )
232- return original_import (name )
190+ return import_optional_dependency (name )
233191
234192 with patch (
235193 "rocketpy.plots.monte_carlo_plots.import_optional_dependency" ,
@@ -241,70 +199,73 @@ def mock_import_optional_dependency(name):
241199
242200
243201@patch ("matplotlib.pyplot.show" )
244- def test_ellipses_background_with_custom_xlim_ylim (mock_show ):
245- """Test using background with custom xlim and ylim .
202+ def test_ellipses_background_works_with_custom_limits (mock_show ):
203+ """Test that background maps work with custom axis limits .
246204
247205 Parameters
248206 ----------
249- mock_show :
250- Mocks the matplotlib.pyplot.show() function to avoid showing the plots.
207+ mock_show : unittest.mock.MagicMock
208+ Mocks the matplotlib.pyplot.show() function to avoid displaying plots.
251209 """
210+
252211 mock_monte_carlo = MockMonteCarlo (environment = SimpleEnvironment ())
253- # Test using custom xlim and ylim
212+
254213 result = mock_monte_carlo .plots .ellipses (
255214 background = "satellite" ,
256215 xlim = (- 5000 , 5000 ),
257216 ylim = (- 5000 , 5000 ),
258217 )
218+
259219 assert result is None
260220
261221
262222@patch ("matplotlib.pyplot.show" )
263- def test_ellipses_background_save (mock_show ):
264- """Test using background with save=True .
223+ def test_ellipses_background_saves_file_successfully (mock_show ):
224+ """Test that plots with background maps can be saved to file .
265225
266226 Parameters
267227 ----------
268- mock_show :
269- Mocks the matplotlib.pyplot.show() function to avoid showing the plots.
228+ mock_show : unittest.mock.MagicMock
229+ Mocks the matplotlib.pyplot.show() function to avoid displaying plots.
270230 """
231+
271232 filename = "monte_carlo_test.png"
233+ mock_monte_carlo = MockMonteCarlo (
234+ environment = SimpleEnvironment (), filename = "monte_carlo_test"
235+ )
236+
272237 try :
273- mock_monte_carlo = MockMonteCarlo (
274- environment = SimpleEnvironment (), filename = "monte_carlo_test"
275- )
276- # Test save functionality
277238 result = mock_monte_carlo .plots .ellipses (background = "satellite" , save = True )
278239 assert result is None
279- # Verify file was created
280240 assert os .path .exists (filename )
281241 finally :
282242 if os .path .exists (filename ):
283243 os .remove (filename )
284244
285245
286246@patch ("matplotlib.pyplot.show" )
287- def test_ellipses_background_invalid_provider (mock_show ):
288- """Test that ValueError is raised when an invalid map provider is specified .
247+ def test_ellipses_background_raises_error_for_invalid_provider (mock_show ):
248+ """Test that ValueError is raised for invalid map provider names .
289249
290250 Parameters
291251 ----------
292- mock_show :
293- Mocks the matplotlib.pyplot.show() function to avoid showing the plots.
252+ mock_show : unittest.mock.MagicMock
253+ Mocks the matplotlib.pyplot.show() function to avoid displaying plots.
294254 """
255+
295256 mock_monte_carlo = MockMonteCarlo (environment = SimpleEnvironment ())
257+ invalid_provider = "Invalid.Provider.Name"
258+
296259 with pytest .raises (ValueError ) as exc_info :
297- mock_monte_carlo .plots .ellipses (background = "Invalid.Provider.Name" )
298- assert "Invalid map provider" in str (exc_info .value )
299- assert "Invalid.Provider.Name" in str (exc_info .value )
300- assert (
301- "satellite" in str (exc_info .value )
302- or "street" in str (exc_info .value )
303- or "terrain" in str (exc_info .value )
304- )
260+ mock_monte_carlo .plots .ellipses (background = invalid_provider )
261+
262+ error_message = str (exc_info .value )
263+ assert "Invalid map provider" in error_message
264+ assert invalid_provider in error_message
265+ # Check that error message includes built-in options
266+ assert any (option in error_message for option in ["satellite" , "street" , "terrain" ])
305267
306268
307- @patch ("matplotlib.pyplot.show" )
308269@pytest .mark .parametrize (
309270 "exception_factory,expected_exception,expected_messages" ,
310271 [
@@ -373,45 +334,48 @@ def test_ellipses_background_invalid_provider(mock_show):
373334 ),
374335 ],
375336)
376- def test_ellipses_background_bounds2img_failure (
337+ @patch ("matplotlib.pyplot.show" )
338+ def test_ellipses_background_handles_bounds2img_failures (
377339 mock_show , exception_factory , expected_exception , expected_messages
378340):
379341 """Test that appropriate exceptions are raised when bounds2img fails.
380342
381- This is a parameterized test that covers all exception types handled in
382- the _fetch_background_map method :
383- - ValueError: invalid coordinates or zoom level
384- - ConnectionError: network errors (URLError, HTTPError, TimeoutError)
385- - RuntimeError: UnidentifiedImageError (invalid image data)
386- - RuntimeError: other unexpected exceptions
343+ This parameterized test verifies error handling for all exception types
344+ that can occur during background map fetching :
345+ - ValueError: Invalid coordinates or zoom level
346+ - ConnectionError: Network errors (URLError, HTTPError, TimeoutError)
347+ - RuntimeError: Invalid image data (UnidentifiedImageError )
348+ - RuntimeError: Other unexpected exceptions
387349
388350 Parameters
389351 ----------
390- mock_show :
391- Mocks the matplotlib.pyplot.show() function to avoid showing the plots.
352+ mock_show : unittest.mock.MagicMock
353+ Mocks the matplotlib.pyplot.show() function to avoid displaying plots.
392354 exception_factory : callable
393355 A function that returns the exception to raise in mock_bounds2img.
394356 expected_exception : type
395357 The expected exception type to be raised.
396- expected_messages : list[ str]
358+ expected_messages : list of str
397359 List of expected message substrings in the raised exception.
398360 """
399- mock_monte_carlo = MockMonteCarlo (environment = SimpleEnvironment ())
400361
362+ mock_monte_carlo = MockMonteCarlo (environment = SimpleEnvironment ())
401363 contextily = pytest .importorskip ("contextily" )
402364
403365 mock_contextily = MagicMock ()
404366 mock_contextily .providers = contextily .providers
405367
406368 def mock_bounds2img (* args , ** kwargs ):
369+ """Mock bounds2img that raises the specified exception."""
407370 raise exception_factory ()
408371
409372 mock_contextily .bounds2img = mock_bounds2img
410373
411374 def mock_import_optional_dependency (name ):
375+ """Mock import function that returns mock contextily."""
412376 if name == "contextily" :
413377 return mock_contextily
414- return original_import (name )
378+ return import_optional_dependency (name )
415379
416380 with patch (
417381 "rocketpy.plots.monte_carlo_plots.import_optional_dependency" ,
0 commit comments