如何模拟psycopg2游标对象?

时间:2016-02-02 00:34:58

标签: python python-2.7 unit-testing mocking psycopg2

我在Python2中有这段代码:

def super_cool_method():
    con = psycopg2.connect(**connection_stuff)
    cur = con.cursor(cursor_factory=DictCursor)
    cur.execute("Super duper SQL query")
    rows = cur.fetchall()

    for row in rows:
        # do some data manipulation on row
    return rows

我想写一些单元测试。我想知道如何使用mock.patch来修补游标和连接变量,以便它们返回一组假数据?我为我的单元测试尝试了以下代码段,但无济于事:

@mock.patch("psycopg2.connect")
@mock.patch("psycopg2.extensions.cursor.fetchall")
def test_super_awesome_stuff(self, a, b):
    testing = super_cool_method()

但我似乎得到以下错误:

TypeError: can't set attributes of built-in/extension type 'psycopg2.extensions.cursor'

3 个答案:

答案 0 :(得分:23)

您有一系列链式调用,每个调用都返回一个新对象。如果您模拟只是 psycopg2.connect()调用,您可以通过.return_value属性跟踪该调用链(每个生成模拟对象),这些属性引用返回的模拟进行此类调用:< / p>

@mock.patch("psycopg2.connect")
def test_super_awesome_stuff(self, mock_connect):
    expected = [['fake', 'row', 1], ['fake', 'row', 2]]

    mock_con = mock_connect.return_value  # result of psycopg2.connect(**connection_stuff)
    mock_cur = mock_con.cursor.return_value  # result of con.cursor(cursor_factory=DictCursor)
    mock_cur.fetchall.return_value = expected  # return this when calling cur.fetchall()

    result = super_cool_method()
    self.assertEqual(result, expected)

因为你持有模拟connect函数的引用,以及模拟连接和游标对象,你可以断言它们是否被正确调用:

mock_connect.assert_called_with(**connection_stuff)
mock_con.cursor.called_with(cursor_factory=DictCursor)
mock_cur.execute.called_with("Super duper SQL query")

如果您不需要测试这些内容,您可以将return_value引用链接起来,直接转到cursor()调用连接对象的结果:

@mock.patch("psycopg2.connect")
def test_super_awesome_stuff(self, mock_connect):
    expected = [['fake', 'row', 1], ['fake', 'row' 2]]
    mock_connect.return_value.cursor.return_value.fetchall.return_value = expected

    result = super_cool_method()
    self.assertEqual(result, expected)

请注意,如果您将连接用作context manager to automatically commit the transaction ,则使用as__enter__()返回的对象绑定到新名称(所以with psycopg2.connect(...) as conn: # ...)然后您需要在调用链中注入额外的__enter__.return_value

mock_con_cm = mock_connect.return_value  # result of psycopg2.connect(**connection_stuff)
mock_con = mock_con_cm.__enter__.return_value  # object assigned to con in with ... as con    
mock_cur = mock_con.cursor.return_value  # result of con.cursor(cursor_factory=DictCursor)
mock_cur.fetchall.return_value = expected  # return this when calling cur.fetchall()

同样适用于with conn.cursor() as cursor:的结果,conn.cursor.return_value.__enter__.return_value对象已分配给as目标。

答案 1 :(得分:7)

由于游标是con.cursor的返回值,您只需要模拟连接,然后正确配置它。例如,

query_result = [("field1a", "field2a"), ("field1b", "field2b")]
with mock.patch('psycopg2.connect') as mock_connect:
    mock_connect.cursor.return_value.execute.fetch_all = query_result
    super_cool_method()

答案 2 :(得分:1)

以下答案是以上答案的变体。 我正在使用django.db.connections光标对象。

所以下面的代码对我有用

@patch('django.db.connections')
def test_supercool_method(self, mock_connections):
    query_result = [("field1a", "field2a"), ("field1b", "field2b")]
    mock_connections.__getitem__.return_value.cursor.return_value.__enter__.return_value.fetchall.return_value = query_result

    result = supercool_method()
    self.assertIsInstance(result, list)