使用Pytest模拟Bing API方法响应失败问题排查
问题:测试geocode_postcode函数时模拟Bing API返回MagicMock而非预期值
问题背景
测试geocode_postcode函数时,为避免调用真实Bing API,使用unittest.mock.patch和自定义StubResponse类模拟响应,但测试返回的是MagicMock对象而非预期的(0,0),导致断言失败。
待测试函数
def geocode_postcode(postcode: str) -> tuple: """ Turn the postcode into a longitude and latitude point Inputs postocde: postocde for the UK Outputs point: longitude and latitude as a tuple (longitude, latitude) else None """ try: if not postcode or not isinstance(postcode, str): raise ValueError("Invalid postcode format") with Bing(api_key=CONFIG.BING_API_KEY) as geolocator: point_location = geolocator.geocode( query={"postalCode":postcode, "countryRegion":"UK"}, exactly_one=True ) if point_location: point = (point_location.longitude, point_location.latitude) else: point = None return point except (GeocoderTimedOut, GeocoderServiceError) as e: print(f"Error geocoding postcode {postcode}: {e}") return None except ValueError as e: print(e) return None
测试代码
from petrol_locations.helpers import geocode_postcode from unittest.mock import MagicMock, patch import pytest class StubResponse(): def __init__(self, longitude, latitude): self.longitude = longitude self.latitude = latitude @patch("petrol_locations.helpers.Bing") def test_geocode_postcode_success(mock_geocode): mock_response = StubResponse(0,0) mock_geocode.return_value.geocode.return_value = mock_response response = geocode_postcode("SW1A 2AA") print(response) assert response == (0, 0)
错误信息
========================================================================================================= FAILURES ========================================================================================================== _______________________________________________________________________________________________ test_geocode_postcode_success _______________________________________________________________________________________________ mock_geocode = <MagicMock name='Bing' id='139699539770480'> @patch("petrol_locations.helpers.Bing") def test_geocode_postcode_success(mock_geocode): mock_response = StubResponse(0,0) mock_geocode.return_value.geocode.return_value = mock_response response = geocode_postcode("SW1A 2AA") print(response) > assert response == (0, 0) E AssertionError: assert (<MagicMock n...99539985984'>) == (0, 0) E At index 0 diff: <MagicMock name='Bing().__enter__().geocode().longitude' id='139699539973984'> != 0 E Use -v to get more diff tests/test_helpers.py:34: AssertionError
返回结果
(<MagicMock name='Bing().__enter__().geocode().longitude' id='139699539973984'>, <MagicMock name='Bing().__enter__().geocode().latitude' id='139699539985984'>)
解决方案
问题核心是Bing类作为上下文管理器使用,你只mock了Bing.return_value.geocode,但忽略了上下文管理器的__enter__方法。with Bing(...) as geolocator中,geolocator是Bing().__enter__()返回的对象,而非直接的Bing()实例,所以需要给__enter__的返回值设置mock。
修改后的测试代码:
@patch("petrol_locations.helpers.Bing") def test_geocode_postcode_success(mock_geocode): mock_response = StubResponse(0,0) # 创建上下文管理器返回的mock对象 mock_context = MagicMock() mock_context.geocode.return_value = mock_response # 让Bing实例的__enter__方法返回这个mock对象 mock_geocode.return_value.__enter__.return_value = mock_context response = geocode_postcode("SW1A 2AA") assert response == (0, 0)
说明
mock_geocode.return_value对应Bing()实例__enter__.return_value指定了with语句中geolocator变量的实际指向- 给这个上下文对象的
geocode方法设置返回值为StubResponse实例,这样point_location就是自定义的响应对象,其longitude和latitude就是预期的0和0,而非MagicMock对象。
内容的提问来源于stack exchange,提问作者Philip09
相关产品推荐
相关产品推荐

