Python 3 urlopen上下文管理器模拟

15

我是测试新手,需要帮忙。

假设有这个方法:

from urllib.request import urlopen

def get_posts():
    with urlopen('some url here') as data:
        return json.loads(data.read().decode('utf-8'))

问题是如何测试这个方法(如果可能的话,使用mock.patch装饰器)?

现在我有的是:

@mock.patch('mymodule.urlopen')
def test_get_post(self, mocked_urlopen):
    mocked_urlopen.__enter__ = Mock(return_value=self.test_data)
    mocked_urlopen.__exit__ = Mock(return_value=False)
    ...

但它似乎没有起作用。

P.S. 是否有便捷的方式在测试中使用数据变量(类型为HTTPResponse)以便它可以只是一个简单的字符串?


我不熟悉mock库,但是也许你可以查看unittest.mock文档以获取更多细节。 - Eric
self.test_data 必须是一个具有 read() 方法的对象,而不是字符串。 - jfs
4个回答

30

我也曾经和这个问题斗争,最后找到了方法。(Python 3语法):

我也遇到过类似的问题,最终解决了。(Python 3语法):

import urllib.request
import unittest
from unittest.mock import patch, MagicMock

class TestUrlopen(unittest.TestCase):
    @patch('urllib.request.urlopen')
    def test_cm(self, mock_urlopen):
        cm = MagicMock()
        cm.getcode.return_value = 200
        cm.read.return_value = 'contents'
        cm.__enter__.return_value = cm
        mock_urlopen.return_value = cm

        with urllib.request.urlopen('http://foo') as response:
            self.assertEqual(response.getcode(), 200)
            self.assertEqual(response.read(), 'contents')

    @patch('urllib.request.urlopen')
    def test_no_cm(self, mock_urlopen):
        cm = MagicMock()
        cm.getcode.return_value = 200
        cm.read.return_value = 'contents'
        mock_urlopen.return_value = cm

        response = urllib.request.urlopen('http://foo')
        self.assertEqual(response.getcode(), 200)
        self.assertEqual(response.read(), 'contents')
        response.close()

我可以问一下no_cm和cm之间有什么区别吗? - EralpB
1
@EralpB cm 意味着他展示了一个使用 urlopen 作为上下文管理器的示例,no_cm 则是一个不使用它作为上下文管理器的示例。 - Meori Lehr

0

with urlopen('some url here') as data 是一个上下文管理器

另外,文件也可以用作上下文管理器,因此在这里更好的方法是使用io.StringIO

import io
import json
import urllib.request
from unittest.mock import patch


def get_posts():
    with urllib.request.urlopen('some url here') as data:
        return json.load(data)


def test_get_posts():
    data = io.StringIO('{"id": 123}')
    with patch.object(urllib.request, 'urlopen', return_value=data):
        assert get_posts() == {"id": 123}

0
这是我的看法。
from urllib.request import urlopen 
from unittest.mock import patch

class Mock():
    def __init__(self, request, context):
        return None

    def read(self):
        return self

    def decode(self, arg):
        return ''

    def __iter__(self):
        return self

    def __next__(self):
        raise StopIteration


 with patch('urllib.request.urlopen',  Mock):
    # do whatever over here

-1

好的,我编写了一个简单的类来模拟上下文管理器。

class PatchContextManager:

    def __init__(self, method, enter_return, exit_return=False):
        self._patched = patch(method)
        self._enter_return = enter_return
        self._exit_return = exit_return

    def __enter__(self):
        res = self._patched.__enter__()
        res.context = MagicMock()
        res.context.__enter__.return_value = self._enter_return
        res.context.__exit__.return_value = self._exit_return
        res.return_value = res.context
        return res

    def __exit__(self, type, value, tb):
        return self._patched.__exit__()

使用方法:

with PatchContextManager('mymodule.method', 'return_string') as mocked:
    a = mymodule.method(47) # a == 'return_string'
    mocked.assert_called_with(47)
    ... 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接