Skip to content

Commit e979752

Browse files
fix(tests): Refactor mocks in test_add_smart_campaign.py
This commit implements a detailed refactoring of mocks within `test_add_smart_campaign.py` to address persistent "malformed KeywordTheme" and "argument of type 'MagicMock' is not iterable" errors. Key changes include: - Centralized definitions of mock instances for objects created via `client.get_type()` at the beginning of each test function. This ensures consistency. - `mock_google_ads_client.get_type` is reset to a fresh `MagicMock()` at the start of each test, and its `side_effect` is configured to return these pre-defined mock instances. - `SmartCampaignSuggestionInfo` mock instances are now guaranteed to have `location_list.locations` and `ad_schedules` attributes initialized as Python lists (`[]`), resolving the "not iterable" error. - A factory function (`create_mock_keyword_theme_instance`) is used to create all `KeywordTheme` mock instances. This factory ensures that the mocks are `spec`ed with `['keyword_theme_constant', 'free_form_keyword_theme']` and these attributes are initialized to `None`. This standardized creation is applied to `KeywordTheme` mocks returned by `client.get_type("SuggestKeywordThemesResponse").KeywordTheme` and also to those returned by the `SmartCampaignSuggestService.suggest_keyword_themes` mock. This is intended to fix the "malformed KeywordTheme" error by ensuring the `in` operator checks in the script work as expected.
1 parent 39f43eb commit e979752

File tree

1 file changed

+98
-73
lines changed

1 file changed

+98
-73
lines changed

examples/advanced_operations/tests/test_add_smart_campaign.py

Lines changed: 98 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -74,69 +74,77 @@ 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-
# --- Start of client.get_type mocking ---
7877
mock_google_ads_client.get_type = MagicMock() # Reset get_type
7978

80-
def create_mock_keyword_theme_instance_func(): # Renamed to avoid clash if tests run in same scope
79+
# --- Definitions for mock instances returned by get_type ---
80+
def create_mock_keyword_theme_instance(): # Factory for KeywordTheme
8181
instance = MagicMock(spec=['keyword_theme_constant', 'free_form_keyword_theme'])
8282
instance.keyword_theme_constant = None
8383
instance.free_form_keyword_theme = None
8484
return instance
8585

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):
86+
mock_keyword_theme_constructor = MagicMock(side_effect=create_mock_keyword_theme_instance)
87+
mock_suggest_themes_response_type_obj = MagicMock()
88+
mock_suggest_themes_response_type_obj.KeywordTheme = mock_keyword_theme_constructor
89+
90+
mock_sc_suggestion_info_obj = MagicMock()
91+
mock_sc_suggestion_info_obj.location_list = MagicMock()
92+
mock_sc_suggestion_info_obj.location_list.locations = []
93+
mock_sc_suggestion_info_obj.ad_schedules = []
94+
mock_sc_suggestion_info_obj.final_url = None
95+
mock_sc_suggestion_info_obj.language_code = None
96+
mock_sc_suggestion_info_obj.business_profile_location = None
97+
mock_sc_suggestion_info_obj.business_context = MagicMock()
98+
mock_sc_suggestion_info_obj.business_context.business_name = None
99+
mock_sc_suggestion_info_obj.keyword_themes = []
100+
101+
mock_location_info_obj = MagicMock()
102+
mock_ad_schedule_info_obj = MagicMock()
103+
mock_campaign_budget_operation_obj = MagicMock()
104+
mock_campaign_operation_obj = MagicMock()
105+
mock_smart_campaign_setting_operation_obj = MagicMock()
106+
mock_smart_campaign_setting_operation_obj.update = MagicMock()
107+
mock_campaign_criterion_operation_obj = MagicMock()
108+
mock_ad_group_operation_obj = MagicMock()
109+
mock_ad_group_ad_operation_obj = MagicMock()
110+
mock_ad_text_asset_obj = MagicMock()
111+
# --- End definitions for mock instances ---
112+
113+
def new_get_type_side_effect(type_name):
96114
if type_name == "SuggestKeywordThemesResponse":
97-
return mock_suggest_response_type_main
115+
return mock_suggest_themes_response_type_obj
98116
elif type_name == "SmartCampaignSuggestionInfo":
99-
return mock_si_instance_main
117+
return mock_sc_suggestion_info_obj
100118
elif type_name == "LocationInfo":
101-
return MagicMock()
119+
return mock_location_info_obj # Or MagicMock() if new instance needed each time
102120
elif type_name == "AdScheduleInfo":
121+
return mock_ad_schedule_info_obj # Or MagicMock()
122+
elif type_name == "CampaignBudgetOperation": return mock_campaign_budget_operation_obj
123+
elif type_name == "CampaignOperation": return mock_campaign_operation_obj
124+
elif type_name == "SmartCampaignSettingOperation": return mock_smart_campaign_setting_operation_obj
125+
elif type_name == "CampaignCriterionOperation": return mock_campaign_criterion_operation_obj
126+
elif type_name == "AdGroupOperation": return mock_ad_group_operation_obj
127+
elif type_name == "AdGroupAdOperation": return mock_ad_group_ad_operation_obj
128+
elif type_name == "AdTextAsset": return mock_ad_text_asset_obj
129+
else:
130+
# print(f"DEBUG: client.get_type called for unhandled type in test_main_runs_successfully: {type_name}")
103131
return MagicMock()
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 ---
132+
mock_google_ads_client.get_type.side_effect = new_get_type_side_effect
116133

117134
# --- Mock services ---
118135
mock_suggest_service = mock_google_ads_client.get_service("SmartCampaignSuggestService")
119-
mock_ktc_service = mock_google_ads_client.get_service("KeywordThemeConstantService") # Define mock_ktc_service
136+
mock_ktc_service = mock_google_ads_client.get_service("KeywordThemeConstantService")
120137
mock_geo_service = mock_google_ads_client.get_service("GeoTargetConstantService")
121138
mock_googleads_service = mock_google_ads_client.get_service("GoogleAdsService")
122139

123-
# Configure mock_suggest_service for the call it receives in this test
124-
# The script's get_keyword_theme_suggestions will call this with suggestion_info
125-
# that includes the free_form_keyword_text.
140+
# Configure mock_suggest_service
126141
mock_response_for_suggest_themes = MagicMock()
127-
128-
# Use the already defined factory create_mock_keyword_theme_instance_func
129-
# (defined as part of the get_type mock setup)
130-
# to ensure the KeywordTheme object is correctly spec'ed and initialized.
131-
free_form_theme_for_test = create_mock_keyword_theme_instance_func()
132-
free_form_theme_for_test.free_form_keyword_theme = mock_free_form_keyword_text # Use the variable passed to main
133-
free_form_theme_for_test.keyword_theme_constant = None # Explicitly ensure other oneof field is None
134-
142+
free_form_theme_for_test = create_mock_keyword_theme_instance()
143+
free_form_theme_for_test.free_form_keyword_theme = mock_free_form_keyword_text
135144
mock_response_for_suggest_themes.keyword_themes = [free_form_theme_for_test]
136145
mock_suggest_service.suggest_keyword_themes.return_value = mock_response_for_suggest_themes
137-
146+
138147
# Configure mock_ktc_service
139-
# This is called by _get_keyword_theme_auto_suggestions for keyword_text
140148
mock_ktc_response_main = MagicMock()
141149
ktc_proto_mock_main = MagicMock() # Simulates KeywordThemeConstant proto
142150
ktc_proto_mock_main.resource_name = "keywordThemeConstants/from_text_search_auto1"
@@ -295,57 +303,74 @@ def test_main_with_business_location_runs_successfully(mock_google_ads_client: M
295303
mock_empty_kw_theme_response.keyword_themes = [] # No themes from this service for this test path
296304
mock_suggest_service.suggest_keyword_themes.return_value = mock_empty_kw_theme_response
297305

298-
# --- Start of client.get_type mocking for biz test ---
299306
mock_google_ads_client.get_type = MagicMock() # Reset get_type
300307

301-
def create_mock_keyword_theme_instance_func_biz(): # Unique name
308+
# --- Definitions for mock instances returned by get_type (biz test) ---
309+
# (Using same factory function name as it's local to test scope)
310+
def create_mock_keyword_theme_instance():
302311
instance = MagicMock(spec=['keyword_theme_constant', 'free_form_keyword_theme'])
303312
instance.keyword_theme_constant = None
304313
instance.free_form_keyword_theme = None
305314
return instance
306315

307-
mock_keyword_theme_constructor_biz = MagicMock(side_effect=create_mock_keyword_theme_instance_func_biz)
308-
mock_suggest_response_type_biz = MagicMock()
309-
mock_suggest_response_type_biz.KeywordTheme = mock_keyword_theme_constructor_biz
310-
311-
mock_si_instance_biz = MagicMock() # SmartCampaignSuggestionInfo instance
312-
mock_si_instance_biz.location_list = MagicMock()
313-
mock_si_instance_biz.location_list.locations = []
314-
mock_si_instance_biz.ad_schedules = []
315-
316-
def new_get_type_side_effect_biz_func(type_name): # Unique name
316+
mock_keyword_theme_constructor_biz = MagicMock(side_effect=create_mock_keyword_theme_instance)
317+
mock_suggest_themes_response_type_obj_biz = MagicMock()
318+
mock_suggest_themes_response_type_obj_biz.KeywordTheme = mock_keyword_theme_constructor_biz
319+
320+
mock_sc_suggestion_info_obj_biz = MagicMock()
321+
mock_sc_suggestion_info_obj_biz.location_list = MagicMock()
322+
mock_sc_suggestion_info_obj_biz.location_list.locations = []
323+
mock_sc_suggestion_info_obj_biz.ad_schedules = []
324+
mock_sc_suggestion_info_obj_biz.final_url = None
325+
mock_sc_suggestion_info_obj_biz.language_code = None
326+
mock_sc_suggestion_info_obj_biz.business_profile_location = None # Will be set by script for this test
327+
mock_sc_suggestion_info_obj_biz.business_context = MagicMock()
328+
mock_sc_suggestion_info_obj_biz.business_context.business_name = None
329+
mock_sc_suggestion_info_obj_biz.keyword_themes = []
330+
331+
mock_location_info_obj_biz = MagicMock()
332+
mock_ad_schedule_info_obj_biz = MagicMock()
333+
mock_campaign_budget_operation_obj_biz = MagicMock()
334+
mock_campaign_operation_obj_biz = MagicMock()
335+
mock_smart_campaign_setting_operation_obj_biz = MagicMock()
336+
mock_smart_campaign_setting_operation_obj_biz.update = MagicMock()
337+
mock_campaign_criterion_operation_obj_biz = MagicMock()
338+
mock_ad_group_operation_obj_biz = MagicMock()
339+
mock_ad_group_ad_operation_obj_biz = MagicMock()
340+
mock_ad_text_asset_obj_biz = MagicMock()
341+
# --- End definitions for mock instances (biz test) ---
342+
343+
def new_get_type_side_effect_biz(type_name): # Renamed for clarity
317344
if type_name == "SuggestKeywordThemesResponse":
318-
return mock_suggest_response_type_biz
345+
return mock_suggest_themes_response_type_obj_biz
319346
elif type_name == "SmartCampaignSuggestionInfo":
320-
return mock_si_instance_biz
347+
return mock_sc_suggestion_info_obj_biz
321348
elif type_name == "LocationInfo":
322-
return MagicMock()
349+
return mock_location_info_obj_biz
323350
elif type_name == "AdScheduleInfo":
351+
return mock_ad_schedule_info_obj_biz
352+
elif type_name == "CampaignBudgetOperation": return mock_campaign_budget_operation_obj_biz
353+
elif type_name == "CampaignOperation": return mock_campaign_operation_obj_biz
354+
elif type_name == "SmartCampaignSettingOperation": return mock_smart_campaign_setting_operation_obj_biz
355+
elif type_name == "CampaignCriterionOperation": return mock_campaign_criterion_operation_obj_biz
356+
elif type_name == "AdGroupOperation": return mock_ad_group_operation_obj_biz
357+
elif type_name == "AdGroupAdOperation": return mock_ad_group_ad_operation_obj_biz
358+
elif type_name == "AdTextAsset": return mock_ad_text_asset_obj_biz
359+
else:
360+
# print(f"DEBUG: client.get_type called for unhandled type in test_main_with_business_location: {type_name}")
324361
return MagicMock()
325-
elif type_name == "CampaignBudgetOperation": return MagicMock()
326-
elif type_name == "CampaignOperation": return MagicMock()
327-
elif type_name == "SmartCampaignSettingOperation":
328-
op_mock = MagicMock(); op_mock.update = MagicMock(); return op_mock
329-
elif type_name == "CampaignCriterionOperation": return MagicMock()
330-
elif type_name == "AdGroupOperation": return MagicMock()
331-
elif type_name == "AdGroupAdOperation": return MagicMock()
332-
elif type_name == "AdTextAsset": return MagicMock()
333-
else: return MagicMock()
334-
335-
mock_google_ads_client.get_type.side_effect = new_get_type_side_effect_biz_func
336-
# --- End of client.get_type mocking for biz test ---
362+
mock_google_ads_client.get_type.side_effect = new_get_type_side_effect_biz
337363

338364
# --- Mock services for biz test ---
339365
mock_suggest_service = mock_google_ads_client.get_service("SmartCampaignSuggestService")
340-
mock_ktc_service = mock_google_ads_client.get_service("KeywordThemeConstantService") # Define mock_ktc_service
366+
mock_ktc_service = mock_google_ads_client.get_service("KeywordThemeConstantService")
341367
mock_geo_service = mock_google_ads_client.get_service("GeoTargetConstantService")
342368
mock_googleads_service = mock_google_ads_client.get_service("GoogleAdsService")
343369

344370
# Configure mock_suggest_service for biz test
345-
# free_form_keyword_text is None, so _get_keyword_theme_infos won't call suggest_keyword_themes for it.
346-
mock_suggest_themes_response_biz = MagicMock()
347-
mock_suggest_themes_response_biz.keyword_themes = [] # No themes from this call path
348-
mock_suggest_service.suggest_keyword_themes.return_value = mock_suggest_themes_response_biz
371+
mock_suggest_themes_response_for_biz_test = MagicMock()
372+
mock_suggest_themes_response_for_biz_test.keyword_themes = []
373+
mock_suggest_service.suggest_keyword_themes.return_value = mock_suggest_themes_response_for_biz_test
349374

350375
# Configure mock_ktc_service for biz test
351376
mock_ktc_response_biz = MagicMock()

0 commit comments

Comments
 (0)