Mocking

The unittest.mock module provides mocking functionality for Python testing.

The patch function actually swaps the targeted thing for the mock object. The first argument is the thing that should be mocked and the second is optionally the thing that will replace it. We can use a Mock or MagicMock object.

There are a few patterns. We can use patch as a context manager and the scoped context object can be used to assert:

 
with patch('some.function') as mock_fn:
	do_thing() # a function that calls some.function
	mock_fn.assert_called_once()

We can set up complex mocks ahead of time and pass them into the context manager. This means that our asserts can be done outside of the with scope too. This setup is better for Arrange Act Assert style testing.

#Arrange
 
mock_fn = MagicMock()
 
#Act
with patch('some.function') as mock_fn:
	do_thing() # a function that calls some.function
 
#Assert
mock_fn.assert_called_once()

We can also use the @mock.patch decorator to replace a scoped function for the duration of a test method:

@mock.patch('some.function')
def test_some_function(mock_fn):
	do_thing()
	mock_fn.assert_called_once()

If we need to stack multiple @mock.patch annotations they are added as input arguments in reverse order because of how python decorators work.

(in the following example mock_fn3 is the mocked version of some.function3 and so on)

@mock.patch('some.function1')
@mock.patch('some.function2')
@mock.patch('some.function3')
def test_some_function(mock_fn3, mock_fn2, mock_fn1):
	do_thing()
	mock_fn.assert_called_once()

Mocking File Opens

As per: this SO post, patch builtins.open and use mock_open to wrap the calls:

```python
from unittest.mock import patch, mock_open
with patch("builtins.open", mock_open(read_data="data")) as mock_file:
    assert open("path/to/open").read() == "data"
mock_file.assert_called_with("path/to/open")