Python单元测试:如何Mock YouTube Build对象及搜索结果?报错排查
问题:Mock YouTube API调用时遇到的下标访问错误
问题描述
为Python项目添加单元测试时,Mock YouTube的build对象出现异常:app.py中results变量被赋值为Mock对象,执行video_id = results['items'][0]['id']['videoId']时报错:TypeError: 'Mock' object is not subscriptable。
尝试过两种解决方案均未解决:
- 使用
@patch('app.youtube.search.list.execute'),报错:AttributeError: 'function' object has no attribute 'list' - 设置
mock_youtube_search.return_value.list.execute.return_value = response_data,无报错但页面渲染后video_id值异常,iframe标签中显示为<MagicMock name='search().list().execute().__getitem__().__getitem__().__getitem__().__getitem__()' id='2662986714704'>
app.py 中index路由的POST处理代码
elif request.method == "POST": # Get movie info from movie_id that was submitted in the form movie = db.execute("SELECT * FROM movies WHERE id = ?", request.form.get("movie_id"))[0] # Search youtube build object and get back a list, passing in movie title the user clicked and set number of results = 1, set parameter part to snippet results = youtube.search().list(part='snippet', q=f'{movie["title"]} trailer', maxResults=1).execute() # Find video ID of the results query video_id = results['items'][0]['id']['videoId'] # Now that we have YouTube video ID, use embed URL to link to the video in the iFrame tag movie_link = f'https://www.youtube.com/embed/{video_id}' return render_template("description.html", movie = movie, URL = large_poster_path_URL, movie_link = movie_link)
app_test.py 中的测试代码
@patch('app.youtube.search') @patch('app.db.execute') def test_index_route_POST(self, mock_db_execute, mock_youtube_search): # Make sure user is logged in with self.app.session_transaction() as session: session['user_id'] = 1 session['logged_in'] = True # Mock database SELECT query mock_db_execute.return_value = [ {'id': 2, 'title': 'Teenage Mutant Ninja Turtles: Mutant Mayhem', 'overview': "After years of being sheltered from the human world, the Turtle brothers set out to win the hearts of New Yorkers and be accepted as normal teenagers through heroic acts. Their new friend April O'Neil helps them take on a mysterious crime syndicate, but they soon get in over their heads when an army of mutants is unleashed upon them.", 'release_date': '2023-07-31', 'poster_path': '/poster_path_3.jpg'} ] # Mock the YouTube API response using custom mock response_data = Mock(return_value={"items": [{"id": {"kind":"youtube#video","videoId":"IHvzw4Ibuho"}}]}) print("This is the response data: ", response_data()) mock_youtube_search.return_value = response_data # POST request to index route response = self.app.post('/', follow_redirects=True) print(response.data) # Debugging # Assert that the response status code is 200 (OK) self.assertEqual(response.status_code, 200) # Assert that index.html is showing correct database information soup = BeautifulSoup(response.data, 'html.parser') movie_title = soup.find('p') movie_title_text = movie_title.text.strip() self.assertEqual(movie_title_text, 'Movie title: Teenage Mutant Ninja Turtles: Mutant Mayhem')
问题原因&修复方案
核心问题
原代码中youtube.search().list(...).execute()是链式调用,测试中Mock的层级未匹配调用链,且错误地用Mock对象包装了响应数据,导致下标访问失败或返回MagicMock占位符。
具体修复步骤
修正Mock层级,匹配链式调用
不需要用Mock包装响应数据,直接使用真实字典,逐层设置每个链式调用的返回值:# 定义真实的响应数据字典(无需用Mock包裹) response_data = {"items": [{"id": {"kind":"youtube#video","videoId":"IHvzw4Ibuho"}}]} # 构建链式调用的Mock链:search() → list() → execute() mock_search_instance = Mock() mock_list_instance = Mock() # 让execute()返回真实字典,而非Mock mock_list_instance.execute.return_value = response_data # 让search()返回的对象拥有list方法,且list()返回mock_list_instance mock_search_instance.list.return_value = mock_list_instance # 最终让youtube.search()返回mock_search_instance mock_youtube_search.return_value = mock_search_instance修复POST请求参数缺失问题
原测试中POST请求未传递movie_id参数,而app.py中需要该参数查询数据库,需补充:response = self.app.post('/', data={"movie_id": 2}, follow_redirects=True)解释之前方案的错误
- 方案1错误:
@patch('app.youtube.search.list.execute')路径无效,因为search是方法,调用后才返回拥有list方法的对象,直接patchsearch.list.execute找不到对应属性。 - 方案2错误:用Mock包装了
response_data,导致execute()返回的是Mock对象而非真实字典,下标访问时生成MagicMock占位符,最终渲染异常。
- 方案1错误:
内容的提问来源于stack exchange,提问作者Meliodas
相关产品推荐
相关产品推荐

