Python Mocking with unitest.mock: Difference between revisions
Line 174: | Line 174: | ||
mock.call_args_list[0].kwargs['some_arg'] | mock.call_args_list[0].kwargs['some_arg'] | ||
</syntaxhighlight> | </syntaxhighlight> | ||
====<tt>method_calls</tt>==== | ====<tt>method_calls</tt>==== | ||
====<tt>mock_calls</tt>==== | ====<tt>mock_calls</tt>==== |
Revision as of 20:36, 24 September 2022
External
Internal
Overview
The unittest.mock
library allows replacing parts of the system under test with mock objects and make assertion about how they are accessed and used.
Mock
By default, a Mock
accept any invocations into it.
MagicMock
Difference between Mock
and MagicMock
.
patch() and @patch
Sentinel
TODEPLETE
https://realpython.com/python-mock-library/
Mocking
To mock a class and a method of that class:
from unittest.mock import Mock
class SomeClass:
def __init__(self, state):
self.state = state
def some_method(self):
return self.state
sc = SomeClass('A')
assert 'A' == sc.some_method()
sc_mock = Mock(SomeClass)
sc_mock.some_method = Mock(return_value='blah')
assert 'blah' == sc_mock.some_method()
How to simulate different return values for a mocked function depending on an argument value?
Mocking a Property
Tested to work:
from unittest.mock import patch, PropertyMock
class A:
@property
def property_a(self):
return "pa"
with patch('__main__.A.property_a', new_callable=PropertyMock) as mock_property:
mock_property.return_value = 'mocked pa'
a = A()
assert a.property_a == 'mocked pa'
Mocking a Method
from unittest.mock import patch
class A:
def method_a(self):
return "ma"
with patch.object(A, 'method_a', return_value='mocked ma') as mock_method:
a = A()
assert a.method_a() == 'mocked ma'
Mocking a Regular Module Function
Use patch()
as a context manager. Inside the with
statement, the `target` (the first argument of patch()
) is patched with a `new` object. When the with
statement exits, the patch is undone. If the 'new' is committed, the target is replaced with a MagicMock, and the created mock is returned by the context manager.
import subprocess
from unittest.mock import patch, Mock
class MockCompletedProcess:
@property
def stdout(self):
return "mock stdout"
with patch('subprocess.run', new=Mock(return_value=MockCompletedProcess())) as run_interceptor:
result = subprocess.run('ls', capture_output=True, check=True, shell=True)
assert result.stdout == 'mock stdout'
result = subprocess.run('echo', capture_output=False, check=False, shell=True)
assert result.stdout == 'mock stdout'
assert run_interceptor.call_count == 2
assert run_interceptor.call_args_list[0].args[0] == 'ls'
assert run_interceptor.call_args_list[0].kwargs['capture_output'] is True
assert run_interceptor.call_args_list[0].kwargs['check'] is True
assert run_interceptor.call_args_list[0].kwargs['shell'] is True
assert run_interceptor.call_args_list[1].args[0] == 'echo'
assert run_interceptor.call_args_list[1].kwargs['capture_output'] is False
assert run_interceptor.call_args_list[1].kwargs['check'] is False
assert run_interceptor.call_args_list[1].kwargs['shell'] is True
Alternative setting where we intercept the arguments and control output depending on arguments:
class MockCompletedProcess:
def __init__(self, *args, **kwargs):
self._stdout = None
command = args[0]
if command.startswith('spin account get'):
self._stdout = '{"name": "test"}'
elif command.startswith('spin pt save') or command.startswith('spin pipeline save'):
self._stdout = 'Pipeline save succeeded'
else:
self._stdout = 'Generic output'
@property
def stdout(self):
return self._stdout.encode('utf-8')
def test_something():
with patch('subprocess.run', new=Mock(wraps=MockCompletedProcess)) as run_interceptor:
# to testing with 'subprocess.run' mocked ...
Asserting Invocations on Mock
mock.assert_called_once_with(
"some concrete arg 1",
unittest.mock.ANY)
TO further document: https://docs.python.org/3/library/unittest.mock.html#unittest.mock.ANY
mock = ...
mock.call_count # return the number of times the mock was called into, as an int
call_args_list
mock = ...
mock.call_args_list
call_args_list
is a list of call arguments for all calls that were made on the mock. The list length is equal with mock.call_count
Call arguments for each call can be obtained with the index operator []:
args_for_first_call = mock.call_args_list[0]
args_for_second_call = mock.call_args_list[1]
...
Positional arguments are maintained in a tuple and can be obtained with the args
property. It returns a tuple, which may be empty:
mock.call_args_list[0].args
Named arguments are maintained in a dictionary, which can be obtained with the kwargs
property. It returns a dictionary, which may be empty:
mock.call_args_list[0].kwargs
mock.call_args_list[0].kwargs['some_arg']