Ishan Bhatt
Ishan Bhatt

Reputation: 10269

How to mock object attributes and complex fields and methods?

I have a following function which needs to be unit tested.

def read_all_fields(all_fields_sheet):
    entries = []

    for row_index in xrange(2, all_fields_sheet.nrows):
        d = {'size' : all_fields_sheet.cell(row_index,0).value,\
             'type' : all_fields_sheet.cell(row_index,1).value,\
             'hotslide' : all_fields_sheet.cell(row_index,3).value}
        entries.append((all_fields_sheet.cell(row_index,2).value,d))

    return entries

Now, my all_fields_sheet is a sheet returned by xlrd module(Used to read Excel file).

So, basically I need to mock for following attributes nrows cell

How should I go abput it?

Upvotes: 1

Views: 3565

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1123810

Just mock the calls and attributes directly on a mock object; adjust to cover your test needs:

mock_sheet = MagicMock()
mock_sheet.nrows = 3  # loop once
cells = [
    MagicMock(value=42),     # row_index, 0
    MagicMock(value='foo'),  # row_index, 1
    MagicMock(value='bar'),  # row_index, 3
    MagicMock(value='spam'), # row_index, 2
]
mock_sheet.cell.side_effect = cells

By assigning a list to Mock.side_effect you can control, in order, what calls to .cell() return.

Afterwards, you can test if the right calls have been made with the various assertion methods. You could use the mock.call() object to give precise expectations:

result = read_all_fields(mock_sheet)
self.assertEqual(
    result, 
    [('spam', {'size': 42, 'type': 'foo', 'hotslide': 'bar'})]
)

self.assertEqual(
    mock_sheet.cell.call_args_list,
    [call(2, 0), call(2, 1), call(2, 3), call(2, 2)])

I used Mock.call_args_list here to match an exact number of calls, directly to mock_sheet.cell alone.

Demo, assuming that your read_all_fields() function is already defined:

>>> from unittest.mock import MagicMock, call
>>> mock_sheet = MagicMock()
>>> mock_sheet.nrows = 3  # loop once
>>> cells = [
...     MagicMock(value=42),     # row_index, 0
...     MagicMock(value='foo'),  # row_index, 1
...     MagicMock(value='bar'),  # row_index, 3
...     MagicMock(value='spam'), # row_index, 2
... ]
>>> mock_sheet.cell.side_effect = cells
>>> result = read_all_fields(mock_sheet)
>>> result == [('spam', {'size': 42, 'type': 'foo', 'hotslide': 'bar'})]
True
>>> mock_sheet.cell.call_args_list == [call(2, 0), call(2, 1), call(2, 3), call(2, 2)]
True

Alternatively, you could create a function for the mock_sheet.cell.side_effect attribute, to return values from a 'sheet' you set up up front:

cells = [[42, 'foo', 'spam', 'bar']]  # 1 row
def mock_cells(row, cell):
    return MagicMock(value=cells[row - 2][cell])
mock_sheet.cell.side_effect = mock_cells

When side_effect is a function, it is called whenever mock_sheet.cell() is called, with the same arguments.

Upvotes: 5

Related Questions