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

求助:单文件正常但Python包中Mock urllib.request.urlopen失效

Fixing Mock Failure for urllib.request.urlopen in Package Structures

I've run into this exact issue before—mock works fine in a single file, but breaks when moving code into a package with imports in __init__.py. Let's break down why this happens and how to fix it.

The Root Cause

The core problem boils down to how unittest.mock.patch works: you have to patch the object in the namespace where it's being used, not where it's defined.

When you import fetch_data into your package's __init__.py, the function still references the urlopen variable from its original module (mypackage.module). If your test patches the wrong path (like a local relative path instead of the full package module path), the mock won't attach to the urlopen instance that fetch_data actually uses. This leads to the real urlopen being called, causing your "test is not a valid URL" error.

Reproducing the Scenario

First, let's confirm the package structure we're working with:

mypackage/
├── __init__.py
├── module.py
└── module_test.py

module.py

from urllib.request import urlopen

def fetch_data(url):
    response = urlopen(url)
    return response.read()

init.py

from .module import fetch_data

The Broken Test (Your test_not_working)

This fails because the patch path is incorrect:

def test_not_working(self):
    # Wrong: using a relative path instead of the full package module path
    with patch('module.urlopen') as mock_urlopen:
        mock_response = Mock()
        mock_response.read.return_value = b'test data'
        mock_urlopen.return_value = mock_response
        result = fetch_data('http://example.com')  # Uses real urlopen!

The Fix

Correct Test Code

Use the full package module path to patch the urlopen instance that fetch_data actually uses. Here's the working version:

import unittest
from unittest.mock import patch, Mock
from mypackage import fetch_data  # Import from the package

class TestFetchData(unittest.TestCase):
    def test_working_mock(self):
        # Correct: patch the urlopen in the module where fetch_data lives
        with patch("mypackage.module.urlopen") as mock_urlopen:
            # Set up the mock response
            mock_response = Mock()
            mock_response.read.return_value = b"Mocked response content"
            mock_urlopen.return_value = mock_response

            # Run the function under test
            result = fetch_data("http://example.com")

            # Verify everything worked
            mock_urlopen.assert_called_once_with("http://example.com")
            self.assertEqual(result, b"Mocked response content")

    def test_global_mock_alternative(self):
        # Optional: Patch the global urllib.request.urlopen (affects all uses)
        with patch("urllib.request.urlopen") as mock_urlopen:
            mock_response = Mock()
            mock_response.read.return_value = b"Global mock content"
            mock_urlopen.return_value = mock_response

            result = fetch_data("http://example.com")
            self.assertEqual(result, b"Global mock content")

if __name__ == "__main__":
    unittest.main()

Why This Works

  • When you import fetch_data from mypackage, it still executes code from mypackage.module. The urlopen it uses is the one bound to the mypackage.module namespace.
  • By patching "mypackage.module.urlopen", we're replacing exactly that bound instance, so fetch_data uses our mock instead of the real urlopen.

Best Practices

  1. Always use full module paths for patches, especially in package structures. Relative paths lead to inconsistent behavior.
  2. Remember the patch rule: Patch where the object is used, not where it's defined. If a function in module A uses an object imported from module B, patch A.object, not B.object (unless you want a global mock).
  3. Avoid ambiguous imports: Be explicit about where your functions and objects come from to make patch paths easier to get right.

内容的提问来源于stack exchange,提问作者j-maas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 03:43:10