pythonunit-testingtestingmockingnose

Mocking two functions with patch for a unit test


I have a function I want to unit test contains calls two other functions. I am unsure how can I mock both functions at the same time properly using patch. I have provided an example of what I mean below. When I run nosetests, the tests pass but I feel that there must be a cleaner way to do this and I do not really Understand the piece regarding f.close()...

The directory structure looks like this:

program/
  program/
    data.py
  tests/
    data_test.py

data.py:

import cPickle

def write_out(file_path, data):
    f = open(file_path, 'wb')
    cPickle.dump(data, f)
    f.close()

data_test.py:

from mock import MagicMock, patch

def test_write_out():
    path = '~/collection'
    mock_open = MagicMock()
    mock_pickle = MagicMock()
    f_mock = MagicMock()
    with patch('__builtin__.open', mock_open):
        f = mock_open.return_value
        f.method.return_value = path
        with patch('cPickle.dump', mock_pickle):
            write_out(path, 'data')
            mock_open.assert_called_once_with('~/collection', 'wb')
            f.close.assert_any_call()
            mock_pickle.assert_called_once_with('data', f)

Results:

$ nosetests
.
----------------------------------------------------------------------
Ran 1 test in 0.008s
OK

Solution

  • You can simplify your test by using the patch decorator and nesting them like so (they are MagicMock objects by default):

    from unittest.mock import patch
    
    @patch('cPickle.dump')
    @patch('__builtin__.open')
    def test_write_out(mock_open, mock_pickle):
        path = '~/collection'
        f = mock_open.return_value
        f.method.return_value = path
        
        write_out(path, 'data')
        
        mock_open.assert_called_once_with('~/collection', 'wb')
        mock_pickle.assert_called_once_with('data', f)
        f.close.assert_any_call()
    

    Note that, as shown in the example above, the decorators are applied bottom-up in the order of the parameters. See the quick guide from the docs for official documentation of this.

    Calls to a MagicMock instance return a new MagicMock instance, so you can check that the returned value was called just like any other mocked object. In this case f is a MagicMock named 'open()' (try printing f).