Skip to content

Commit 4fa20c7

Browse files
fix: Further refinement of mocks in test_add_smart_campaign.py
This commit applies more detailed mocking strategies to `test_add_smart_campaign.py` to address persistent "malformed KeywordTheme" and "argument of type 'MagicMock' is not iterable" errors. Key changes: - Ensured `mock_google_ads_client.get_type` is reset to a fresh `MagicMock()` at the start of each test function within `test_add_smart_campaign.py`. - Implemented a new `get_type.side_effect` in both test functions: - Mocks for `SuggestKeywordThemesResponse().KeywordTheme` now return a factory function. This factory produces `MagicMock` instances that are `spec`ed with `['keyword_theme_constant', 'free_form_keyword_theme']` and have these attributes initialized to `None` to correctly simulate `oneof` behavior for `in` operator checks. - Mocks for `SmartCampaignSuggestionInfo` now ensure that the `location_list.locations` and `ad_schedules` attributes are initialized as actual Python lists (`[]`), which should prevent the "not iterable" error when the script attempts to append or iterate these fields. - Mocks for `LocationInfo`, `AdScheduleInfo`, and various Google Ads operation types (e.g., `CampaignOperation`) are returned when `client.get_type` is called for them. - The side_effect includes a fallback to return a new `MagicMock()` for any unhandled type names to prevent recursion and ensure stability.
1 parent eb7ef52 commit 4fa20c7

File tree

1 file changed

+102
-71
lines changed

1 file changed

+102
-71
lines changed

examples/advanced_operations/tests/test_add_smart_campaign.py

Lines changed: 102 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -74,56 +74,68 @@ def suggest_keyword_themes_side_effect(request):
7474

7575
mock_suggest_service.suggest_keyword_themes.side_effect = suggest_keyword_themes_side_effect # Keep existing side effect logic for now
7676

77-
# --- Mocking client.get_type for various types used in the script ---
78-
mock_google_ads_client.get_type = MagicMock() # Reset to a simple mock first
79-
80-
# Mock for KeywordTheme objects produced by KeywordTheme()
81-
# This instance will be returned when KeywordTheme() is called in the script.
82-
mock_keyword_theme_instance = MagicMock(spec=['keyword_theme_constant', 'free_form_keyword_theme'])
83-
mock_keyword_theme_instance.keyword_theme_constant = None
84-
mock_keyword_theme_instance.free_form_keyword_theme = None
85-
# This is the mock for the KeywordTheme "class" or "constructor"
86-
mock_keyword_theme_constructor = MagicMock(return_value=mock_keyword_theme_instance)
87-
88-
# This is the mock for the type that *contains* KeywordTheme
89-
# (i.e., what client.get_type("SuggestKeywordThemesResponse") returns)
90-
mock_suggest_keyword_themes_response_type = MagicMock()
91-
mock_suggest_keyword_themes_response_type.KeywordTheme = mock_keyword_theme_constructor
92-
93-
# Mock for SmartCampaignSuggestionInfo
94-
mock_smart_campaign_suggestion_info_instance = MagicMock()
95-
mock_smart_campaign_suggestion_info_instance.location_list = MagicMock()
96-
mock_smart_campaign_suggestion_info_instance.location_list.locations = [] # Must be a list for .append()
97-
mock_smart_campaign_suggestion_info_instance.ad_schedules = [] # Must be a list for .append()
98-
99-
# Mock for LocationInfo and AdScheduleInfo (simple mocks are fine)
100-
mock_location_info_instance = MagicMock()
101-
mock_ad_schedule_info_instance = MagicMock()
102-
103-
def new_get_type_side_effect(type_name):
77+
# --- Start of client.get_type mocking ---
78+
mock_google_ads_client.get_type = MagicMock() # Reset get_type
79+
80+
def create_mock_keyword_theme_instance_func(): # Renamed to avoid clash if tests run in same scope
81+
instance = MagicMock(spec=['keyword_theme_constant', 'free_form_keyword_theme'])
82+
instance.keyword_theme_constant = None
83+
instance.free_form_keyword_theme = None
84+
return instance
85+
86+
mock_keyword_theme_constructor_main = MagicMock(side_effect=create_mock_keyword_theme_instance_func)
87+
mock_suggest_response_type_main = MagicMock()
88+
mock_suggest_response_type_main.KeywordTheme = mock_keyword_theme_constructor_main
89+
90+
mock_si_instance_main = MagicMock() # SmartCampaignSuggestionInfo instance
91+
mock_si_instance_main.location_list = MagicMock()
92+
mock_si_instance_main.location_list.locations = []
93+
mock_si_instance_main.ad_schedules = []
94+
95+
def new_get_type_side_effect_main(type_name):
10496
if type_name == "SuggestKeywordThemesResponse":
105-
return mock_suggest_keyword_themes_response_type
97+
return mock_suggest_response_type_main
10698
elif type_name == "SmartCampaignSuggestionInfo":
107-
# Important: return the instance directly if the script does client.get_type("SmartCampaignSuggestionInfo")
108-
# and expects to set attributes on it. If it calls it like a constructor, then this should return a mock constructor.
109-
# The script does: suggestion_info = client.get_type("SmartCampaignSuggestionInfo")
110-
# then suggestion_info.location_list = ...
111-
# So, returning the instance directly is correct.
112-
return mock_smart_campaign_suggestion_info_instance
99+
return mock_si_instance_main
113100
elif type_name == "LocationInfo":
114-
# Script does: location = client.get_type("LocationInfo")
115-
return mock_location_info_instance
101+
return MagicMock()
116102
elif type_name == "AdScheduleInfo":
117-
# Script does: ad_schedule = client.get_type("AdScheduleInfo")
118-
return mock_ad_schedule_info_instance
119-
else:
120-
# Fallback for any other type
121103
return MagicMock()
122-
123-
mock_google_ads_client.get_type.side_effect = new_get_type_side_effect
124-
# --- End of get_type mocking ---
125-
126-
# Suggest Budget Options
104+
elif type_name == "CampaignBudgetOperation": return MagicMock()
105+
elif type_name == "CampaignOperation": return MagicMock()
106+
elif type_name == "SmartCampaignSettingOperation":
107+
op_mock = MagicMock(); op_mock.update = MagicMock(); return op_mock
108+
elif type_name == "CampaignCriterionOperation": return MagicMock()
109+
elif type_name == "AdGroupOperation": return MagicMock()
110+
elif type_name == "AdGroupAdOperation": return MagicMock()
111+
elif type_name == "AdTextAsset": return MagicMock()
112+
else: return MagicMock()
113+
114+
mock_google_ads_client.get_type.side_effect = new_get_type_side_effect_main
115+
# --- End of client.get_type mocking ---
116+
117+
# Refined SmartCampaignSuggestService.suggest_keyword_themes mock
118+
# This is called by _get_keyword_theme_infos for free_form_keyword_text
119+
mock_suggest_themes_response_main = MagicMock()
120+
free_form_theme_instance = create_mock_keyword_theme_instance_func()
121+
free_form_theme_instance.free_form_keyword_theme = "mocked free form text" # From free_form_keyword_text
122+
mock_suggest_themes_response_main.keyword_themes = [free_form_theme_instance]
123+
# The side_effect for suggest_keyword_themes needs to handle different request types if script calls it multiple ways.
124+
# For free_form_text path:
125+
mock_suggest_service.suggest_keyword_themes.return_value = mock_suggest_themes_response_main
126+
# If suggest_keyword_themes was also called for keyword_seed (it's not, suggest_keyword_theme_constants is),
127+
# the side_effect would need to distinguish. Since it's only for free_form_text:
128+
# Simplified: if called, it's for free_form_text.
129+
130+
# Refined KeywordThemeConstantService.suggest_keyword_theme_constants mock
131+
# This is called by _get_keyword_theme_auto_suggestions for keyword_text
132+
mock_ktc_response_main = MagicMock()
133+
ktc_proto_mock_main = MagicMock() # Simulates KeywordThemeConstant proto
134+
ktc_proto_mock_main.resource_name = "keywordThemeConstants/from_text_search_auto1"
135+
mock_ktc_response_main.keyword_theme_constants = [ktc_proto_mock_main]
136+
mock_ktc_service.suggest_keyword_theme_constants.return_value = mock_ktc_response_main
137+
138+
# Suggest Budget Options (remains largely the same)
127139
mock_budget_options_response = MagicMock()
128140
mock_recommended_budget = MagicMock()
129141
mock_recommended_budget.daily_amount_micros = 50000000 # 50 units of currency
@@ -251,41 +263,60 @@ def test_main_with_business_location_runs_successfully(mock_google_ads_client: M
251263
mock_empty_kw_theme_response.keyword_themes = [] # No themes from this service for this test path
252264
mock_suggest_service.suggest_keyword_themes.return_value = mock_empty_kw_theme_response
253265

254-
# --- Mocking client.get_type for various types (similar to the first test) ---
255-
mock_google_ads_client.get_type = MagicMock() # Reset to a simple mock
266+
# --- Start of client.get_type mocking for biz test ---
267+
mock_google_ads_client.get_type = MagicMock() # Reset get_type
256268

257-
mock_keyword_theme_instance_biz = MagicMock(spec=['keyword_theme_constant', 'free_form_keyword_theme'])
258-
mock_keyword_theme_instance_biz.keyword_theme_constant = None
259-
mock_keyword_theme_instance_biz.free_form_keyword_theme = None
260-
mock_keyword_theme_constructor_biz = MagicMock(return_value=mock_keyword_theme_instance_biz)
269+
def create_mock_keyword_theme_instance_func_biz(): # Unique name
270+
instance = MagicMock(spec=['keyword_theme_constant', 'free_form_keyword_theme'])
271+
instance.keyword_theme_constant = None
272+
instance.free_form_keyword_theme = None
273+
return instance
261274

262-
mock_suggest_keyword_themes_response_type_biz = MagicMock()
263-
mock_suggest_keyword_themes_response_type_biz.KeywordTheme = mock_keyword_theme_constructor_biz
275+
mock_keyword_theme_constructor_biz = MagicMock(side_effect=create_mock_keyword_theme_instance_func_biz)
276+
mock_suggest_response_type_biz = MagicMock()
277+
mock_suggest_response_type_biz.KeywordTheme = mock_keyword_theme_constructor_biz
264278

265-
mock_smart_campaign_suggestion_info_instance_biz = MagicMock()
266-
mock_smart_campaign_suggestion_info_instance_biz.location_list = MagicMock()
267-
mock_smart_campaign_suggestion_info_instance_biz.location_list.locations = []
268-
mock_smart_campaign_suggestion_info_instance_biz.ad_schedules = []
269-
270-
mock_location_info_instance_biz = MagicMock()
271-
mock_ad_schedule_info_instance_biz = MagicMock()
279+
mock_si_instance_biz = MagicMock() # SmartCampaignSuggestionInfo instance
280+
mock_si_instance_biz.location_list = MagicMock()
281+
mock_si_instance_biz.location_list.locations = []
282+
mock_si_instance_biz.ad_schedules = []
272283

273-
def new_get_type_side_effect_biz(type_name):
284+
def new_get_type_side_effect_biz_func(type_name): # Unique name
274285
if type_name == "SuggestKeywordThemesResponse":
275-
return mock_suggest_keyword_themes_response_type_biz
286+
return mock_suggest_response_type_biz
276287
elif type_name == "SmartCampaignSuggestionInfo":
277-
return mock_smart_campaign_suggestion_info_instance_biz
288+
return mock_si_instance_biz
278289
elif type_name == "LocationInfo":
279-
return mock_location_info_instance_biz
290+
return MagicMock()
280291
elif type_name == "AdScheduleInfo":
281-
return mock_ad_schedule_info_instance_biz
282-
else:
283292
return MagicMock()
284-
285-
mock_google_ads_client.get_type.side_effect = new_get_type_side_effect_biz
286-
# --- End of get_type mocking for biz test ---
287-
288-
mock_budget_options_response = MagicMock()
293+
elif type_name == "CampaignBudgetOperation": return MagicMock()
294+
elif type_name == "CampaignOperation": return MagicMock()
295+
elif type_name == "SmartCampaignSettingOperation":
296+
op_mock = MagicMock(); op_mock.update = MagicMock(); return op_mock
297+
elif type_name == "CampaignCriterionOperation": return MagicMock()
298+
elif type_name == "AdGroupOperation": return MagicMock()
299+
elif type_name == "AdGroupAdOperation": return MagicMock()
300+
elif type_name == "AdTextAsset": return MagicMock()
301+
else: return MagicMock()
302+
303+
mock_google_ads_client.get_type.side_effect = new_get_type_side_effect_biz_func
304+
# --- End of client.get_type mocking for biz test ---
305+
306+
# Refined SmartCampaignSuggestService.suggest_keyword_themes mock for biz test
307+
# free_form_keyword_text is None, so _get_keyword_theme_infos won't call suggest_keyword_themes for it.
308+
mock_suggest_themes_response_biz = MagicMock()
309+
mock_suggest_themes_response_biz.keyword_themes = [] # No themes from this call path
310+
mock_suggest_service.suggest_keyword_themes.return_value = mock_suggest_themes_response_biz
311+
312+
# Refined KeywordThemeConstantService.suggest_keyword_theme_constants mock for biz test
313+
mock_ktc_response_biz = MagicMock()
314+
ktc_proto_mock_biz = MagicMock()
315+
ktc_proto_mock_biz.resource_name = "keywordThemeConstants/from_text_search_auto_biz"
316+
mock_ktc_response_biz.keyword_theme_constants = [ktc_proto_mock_biz]
317+
mock_ktc_service.suggest_keyword_theme_constants.return_value = mock_ktc_response_biz
318+
319+
# Suggest Budget Options (remains largely the same)
289320
mock_recommended_budget = MagicMock()
290321
mock_recommended_budget.daily_amount_micros = 55000000
291322
mock_budget_options_response.recommended_daily_budget_options.high.daily_amount_micros = 65000000

0 commit comments

Comments
 (0)