You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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占位符。

具体修复步骤

  1. 修正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
    
  2. 修复POST请求参数缺失问题
    原测试中POST请求未传递movie_id参数,而app.py中需要该参数查询数据库,需补充:

    response = self.app.post('/', data={"movie_id": 2}, follow_redirects=True)
    
  3. 解释之前方案的错误

    • 方案1错误:@patch('app.youtube.search.list.execute')路径无效,因为search是方法,调用后才返回拥有list方法的对象,直接patchsearch.list.execute找不到对应属性。
    • 方案2错误:用Mock包装了response_data,导致execute()返回的是Mock对象而非真实字典,下标访问时生成MagicMock占位符,最终渲染异常。

内容的提问来源于stack exchange,提问作者Meliodas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 17:03:23