547 lines
15 KiB
Python
547 lines
15 KiB
Python
import os
|
|
import platform
|
|
import sys
|
|
from contextlib import contextmanager
|
|
|
|
import py.code
|
|
import pytest
|
|
|
|
pytest_plugins = 'pytester'
|
|
|
|
# could not make some of the tests work on PyPy, patches are welcome!
|
|
skip_pypy = pytest.mark.skipif(platform.python_implementation() == 'PyPy',
|
|
reason='could not make work on pypy')
|
|
|
|
|
|
class UnixFS(object):
|
|
"""
|
|
Wrapper to os functions to simulate a Unix file system, used for testing
|
|
the mock fixture.
|
|
"""
|
|
|
|
@classmethod
|
|
def rm(cls, filename):
|
|
os.remove(filename)
|
|
|
|
@classmethod
|
|
def ls(cls, path):
|
|
return os.listdir(path)
|
|
|
|
|
|
@pytest.fixture
|
|
def check_unix_fs_mocked(tmpdir, mocker):
|
|
"""
|
|
performs a standard test in a UnixFS, assuming that both `os.remove` and
|
|
`os.listdir` have been mocked previously.
|
|
"""
|
|
|
|
def check(mocked_rm, mocked_ls):
|
|
assert mocked_rm is os.remove
|
|
assert mocked_ls is os.listdir
|
|
|
|
file_name = tmpdir / 'foo.txt'
|
|
file_name.ensure()
|
|
|
|
UnixFS.rm(str(file_name))
|
|
mocked_rm.assert_called_once_with(str(file_name))
|
|
assert os.path.isfile(str(file_name))
|
|
|
|
mocked_ls.return_value = ['bar.txt']
|
|
assert UnixFS.ls(str(tmpdir)) == ['bar.txt']
|
|
mocked_ls.assert_called_once_with(str(tmpdir))
|
|
|
|
mocker.stopall()
|
|
|
|
assert UnixFS.ls(str(tmpdir)) == ['foo.txt']
|
|
UnixFS.rm(str(file_name))
|
|
assert not os.path.isfile(str(file_name))
|
|
|
|
return check
|
|
|
|
|
|
def mock_using_patch_object(mocker):
|
|
return mocker.patch.object(os, 'remove'), mocker.patch.object(os, 'listdir')
|
|
|
|
|
|
def mock_using_patch(mocker):
|
|
return mocker.patch('os.remove'), mocker.patch('os.listdir')
|
|
|
|
|
|
def mock_using_patch_multiple(mocker):
|
|
r = mocker.patch.multiple('os', remove=mocker.DEFAULT,
|
|
listdir=mocker.DEFAULT)
|
|
return r['remove'], r['listdir']
|
|
|
|
|
|
@pytest.mark.parametrize('mock_fs', [mock_using_patch_object, mock_using_patch,
|
|
mock_using_patch_multiple],
|
|
)
|
|
def test_mock_patches(mock_fs, mocker, check_unix_fs_mocked):
|
|
"""
|
|
Installs mocks into `os` functions and performs a standard testing of
|
|
mock functionality. We parametrize different mock methods to ensure
|
|
all (intended, at least) mock API is covered.
|
|
"""
|
|
# mock it twice on purpose to ensure we unmock it correctly later
|
|
mock_fs(mocker)
|
|
mocked_rm, mocked_ls = mock_fs(mocker)
|
|
check_unix_fs_mocked(mocked_rm, mocked_ls)
|
|
mocker.resetall()
|
|
mocker.stopall()
|
|
|
|
|
|
def test_mock_patch_dict(mocker):
|
|
"""
|
|
Testing
|
|
:param mock:
|
|
"""
|
|
x = {'original': 1}
|
|
mocker.patch.dict(x, values=[('new', 10)], clear=True)
|
|
assert x == {'new': 10}
|
|
mocker.stopall()
|
|
assert x == {'original': 1}
|
|
|
|
|
|
def test_mock_patch_dict_resetall(mocker):
|
|
"""
|
|
We can call resetall after patching a dict.
|
|
:param mock:
|
|
"""
|
|
x = {'original': 1}
|
|
mocker.patch.dict(x, values=[('new', 10)], clear=True)
|
|
assert x == {'new': 10}
|
|
mocker.resetall()
|
|
assert x == {'new': 10}
|
|
|
|
|
|
def test_deprecated_mock(mock, tmpdir):
|
|
"""
|
|
Use backward-compatibility-only mock fixture to ensure complete coverage.
|
|
"""
|
|
mock.patch('os.listdir', return_value=['mocked'])
|
|
assert os.listdir(str(tmpdir)) == ['mocked']
|
|
mock.stopall()
|
|
assert os.listdir(str(tmpdir)) == []
|
|
|
|
|
|
@pytest.mark.parametrize('name', ['MagicMock', 'PropertyMock', 'Mock', 'call', 'ANY', 'sentinel', 'mock_open'])
|
|
def test_mocker_aliases(name, pytestconfig):
|
|
from pytest_mock import _get_mock_module, MockFixture
|
|
|
|
mock_module = _get_mock_module(pytestconfig)
|
|
|
|
mocker = MockFixture(pytestconfig)
|
|
assert getattr(mocker, name) is getattr(mock_module, name)
|
|
|
|
|
|
def test_mocker_resetall(mocker):
|
|
listdir = mocker.patch('os.listdir')
|
|
open = mocker.patch('os.open')
|
|
|
|
listdir("/tmp")
|
|
open("/tmp/foo.txt")
|
|
listdir.assert_called_once_with("/tmp")
|
|
open.assert_called_once_with("/tmp/foo.txt")
|
|
|
|
mocker.resetall()
|
|
|
|
assert not listdir.called
|
|
assert not open.called
|
|
|
|
|
|
class TestMockerStub:
|
|
def test_call(self, mocker):
|
|
stub = mocker.stub()
|
|
stub('foo', 'bar')
|
|
stub.assert_called_once_with('foo', 'bar')
|
|
|
|
def test_repr_with_no_name(self, mocker):
|
|
stub = mocker.stub()
|
|
assert not 'name' in repr(stub)
|
|
|
|
def test_repr_with_name(self, mocker):
|
|
test_name = 'funny walk'
|
|
stub = mocker.stub(name=test_name)
|
|
assert "name={0!r}".format(test_name) in repr(stub)
|
|
|
|
def __test_failure_message(self, mocker, **kwargs):
|
|
expected_name = kwargs.get('name') or 'mock'
|
|
expected_message = 'Expected call: {0}()\nNot called'.format(expected_name)
|
|
stub = mocker.stub(**kwargs)
|
|
with pytest.raises(AssertionError) as exc_info:
|
|
stub.assert_called_with()
|
|
assert str(exc_info.value) == expected_message
|
|
|
|
def test_failure_message_with_no_name(self, mocker):
|
|
self.__test_failure_message(mocker)
|
|
|
|
@pytest.mark.parametrize('name', (None, '', 'f', 'The Castle of aaarrrrggh'))
|
|
def test_failure_message_with_name(self, mocker, name):
|
|
self.__test_failure_message(mocker, name=name)
|
|
|
|
|
|
def test_instance_method_spy(mocker):
|
|
class Foo(object):
|
|
|
|
def bar(self, arg):
|
|
return arg * 2
|
|
|
|
foo = Foo()
|
|
other = Foo()
|
|
spy = mocker.spy(foo, 'bar')
|
|
assert foo.bar(arg=10) == 20
|
|
assert other.bar(arg=10) == 20
|
|
foo.bar.assert_called_once_with(arg=10)
|
|
spy.assert_called_once_with(arg=10)
|
|
|
|
|
|
@skip_pypy
|
|
def test_instance_method_by_class_spy(mocker):
|
|
class Foo(object):
|
|
|
|
def bar(self, arg):
|
|
return arg * 2
|
|
|
|
spy = mocker.spy(Foo, 'bar')
|
|
foo = Foo()
|
|
other = Foo()
|
|
assert foo.bar(arg=10) == 20
|
|
assert other.bar(arg=10) == 20
|
|
calls = [mocker.call(foo, arg=10), mocker.call(other, arg=10)]
|
|
assert spy.call_args_list == calls
|
|
|
|
|
|
@skip_pypy
|
|
def test_instance_method_by_subclass_spy(mocker):
|
|
|
|
class Base(object):
|
|
|
|
def bar(self, arg):
|
|
return arg * 2
|
|
|
|
class Foo(Base):
|
|
pass
|
|
|
|
spy = mocker.spy(Foo, 'bar')
|
|
foo = Foo()
|
|
other = Foo()
|
|
assert foo.bar(arg=10) == 20
|
|
assert other.bar(arg=10) == 20
|
|
calls = [mocker.call(foo, arg=10), mocker.call(other, arg=10)]
|
|
assert spy.call_args_list == calls
|
|
|
|
|
|
@skip_pypy
|
|
def test_class_method_spy(mocker):
|
|
class Foo(object):
|
|
|
|
@classmethod
|
|
def bar(cls, arg):
|
|
return arg * 2
|
|
|
|
spy = mocker.spy(Foo, 'bar')
|
|
assert Foo.bar(arg=10) == 20
|
|
Foo.bar.assert_called_once_with(arg=10)
|
|
spy.assert_called_once_with(arg=10)
|
|
|
|
|
|
@skip_pypy
|
|
@pytest.mark.xfail(sys.version_info[0] == 2, reason='does not work on Python 2')
|
|
def test_class_method_subclass_spy(mocker):
|
|
class Base(object):
|
|
|
|
@classmethod
|
|
def bar(self, arg):
|
|
return arg * 2
|
|
|
|
class Foo(Base):
|
|
pass
|
|
|
|
spy = mocker.spy(Foo, 'bar')
|
|
assert Foo.bar(arg=10) == 20
|
|
Foo.bar.assert_called_once_with(arg=10)
|
|
spy.assert_called_once_with(arg=10)
|
|
|
|
|
|
@skip_pypy
|
|
def test_class_method_with_metaclass_spy(mocker):
|
|
class MetaFoo(type):
|
|
pass
|
|
|
|
class Foo(object):
|
|
|
|
__metaclass__ = MetaFoo
|
|
|
|
@classmethod
|
|
def bar(cls, arg):
|
|
return arg * 2
|
|
|
|
spy = mocker.spy(Foo, 'bar')
|
|
assert Foo.bar(arg=10) == 20
|
|
Foo.bar.assert_called_once_with(arg=10)
|
|
spy.assert_called_once_with(arg=10)
|
|
|
|
|
|
@skip_pypy
|
|
def test_static_method_spy(mocker):
|
|
class Foo(object):
|
|
|
|
@staticmethod
|
|
def bar(arg):
|
|
return arg * 2
|
|
|
|
spy = mocker.spy(Foo, 'bar')
|
|
assert Foo.bar(arg=10) == 20
|
|
Foo.bar.assert_called_once_with(arg=10)
|
|
spy.assert_called_once_with(arg=10)
|
|
|
|
|
|
@skip_pypy
|
|
@pytest.mark.xfail(sys.version_info[0] == 2, reason='does not work on Python 2')
|
|
def test_static_method_subclass_spy(mocker):
|
|
class Base(object):
|
|
|
|
@staticmethod
|
|
def bar(arg):
|
|
return arg * 2
|
|
|
|
class Foo(Base):
|
|
pass
|
|
|
|
spy = mocker.spy(Foo, 'bar')
|
|
assert Foo.bar(arg=10) == 20
|
|
Foo.bar.assert_called_once_with(arg=10)
|
|
spy.assert_called_once_with(arg=10)
|
|
|
|
|
|
@contextmanager
|
|
def assert_traceback():
|
|
"""
|
|
Assert that this file is at the top of the filtered traceback
|
|
"""
|
|
try:
|
|
yield
|
|
except AssertionError:
|
|
traceback = py.code.ExceptionInfo().traceback
|
|
crashentry = traceback.getcrashentry()
|
|
assert crashentry.path == __file__
|
|
else:
|
|
raise AssertionError("DID NOT RAISE")
|
|
|
|
|
|
@contextmanager
|
|
def assert_argument_introspection(left, right):
|
|
"""
|
|
Assert detailed argument introspection is used
|
|
"""
|
|
try:
|
|
yield
|
|
except AssertionError as e:
|
|
# this may be a bit too assuming, but seems nicer then hard-coding
|
|
import _pytest.assertion.util as util
|
|
# NOTE: we assert with either verbose or not, depending on how our own
|
|
# test was run by examining sys.argv
|
|
verbose = any(a.startswith('-v') for a in sys.argv)
|
|
expected = '\n '.join(util._compare_eq_iterable(left, right, verbose))
|
|
assert expected in str(e)
|
|
else:
|
|
raise AssertionError("DID NOT RAISE")
|
|
|
|
|
|
@pytest.mark.skipif(sys.version_info[:2] in [(3, 3), (3, 4)],
|
|
reason="assert_not_called not available in python 3.3 and 3.4")
|
|
def test_assert_not_called_wrapper(mocker):
|
|
stub = mocker.stub()
|
|
stub.assert_not_called()
|
|
stub()
|
|
with assert_traceback():
|
|
stub.assert_not_called()
|
|
|
|
|
|
def test_assert_called_with_wrapper(mocker):
|
|
stub = mocker.stub()
|
|
stub("foo")
|
|
stub.assert_called_with("foo")
|
|
with assert_traceback():
|
|
stub.assert_called_with("bar")
|
|
|
|
|
|
def test_assert_called_once_with_wrapper(mocker):
|
|
stub = mocker.stub()
|
|
stub("foo")
|
|
stub.assert_called_once_with("foo")
|
|
stub("foo")
|
|
with assert_traceback():
|
|
stub.assert_called_once_with("foo")
|
|
|
|
|
|
def test_assert_called_args_with_introspection(mocker):
|
|
stub = mocker.stub()
|
|
|
|
complex_args = ('a', 1, set(['test']))
|
|
wrong_args = ('b', 2, set(['jest']))
|
|
|
|
stub(*complex_args)
|
|
stub.assert_called_with(*complex_args)
|
|
stub.assert_called_once_with(*complex_args)
|
|
|
|
with assert_argument_introspection(complex_args, wrong_args):
|
|
stub.assert_called_with(*wrong_args)
|
|
stub.assert_called_once_with(*wrong_args)
|
|
|
|
|
|
def test_assert_called_kwargs_with_introspection(mocker):
|
|
stub = mocker.stub()
|
|
|
|
complex_kwargs = dict(foo={'bar': 1, 'baz': 'spam'})
|
|
wrong_kwargs = dict(foo={'goo': 1, 'baz': 'bran'})
|
|
|
|
stub(**complex_kwargs)
|
|
stub.assert_called_with(**complex_kwargs)
|
|
stub.assert_called_once_with(**complex_kwargs)
|
|
|
|
with assert_argument_introspection(complex_kwargs, wrong_kwargs):
|
|
stub.assert_called_with(**wrong_kwargs)
|
|
stub.assert_called_once_with(**wrong_kwargs)
|
|
|
|
|
|
def test_assert_any_call_wrapper(mocker):
|
|
stub = mocker.stub()
|
|
stub("foo")
|
|
stub("foo")
|
|
stub.assert_any_call("foo")
|
|
with assert_traceback():
|
|
stub.assert_any_call("bar")
|
|
|
|
|
|
def test_assert_has_calls(mocker):
|
|
stub = mocker.stub()
|
|
stub("foo")
|
|
stub.assert_has_calls([mocker.call("foo")])
|
|
with assert_traceback():
|
|
stub.assert_has_calls([mocker.call("bar")])
|
|
|
|
|
|
def test_monkeypatch_ini(mocker, testdir):
|
|
# Make sure the following function actually tests something
|
|
stub = mocker.stub()
|
|
assert stub.assert_called_with.__module__ != stub.__module__
|
|
|
|
testdir.makepyfile("""
|
|
import py.code
|
|
def test_foo(mocker):
|
|
stub = mocker.stub()
|
|
assert stub.assert_called_with.__module__ == stub.__module__
|
|
""")
|
|
testdir.makeini("""
|
|
[pytest]
|
|
mock_traceback_monkeypatch = false
|
|
""")
|
|
result = runpytest_subprocess(testdir)
|
|
assert result.ret == 0
|
|
|
|
|
|
def test_parse_ini_boolean():
|
|
import pytest_mock
|
|
assert pytest_mock.parse_ini_boolean('True') is True
|
|
assert pytest_mock.parse_ini_boolean('false') is False
|
|
with pytest.raises(ValueError):
|
|
pytest_mock.parse_ini_boolean('foo')
|
|
|
|
|
|
def test_patched_method_parameter_name(mocker):
|
|
"""Test that our internal code uses uncommon names when wrapping other
|
|
"mock" methods to avoid conflicts with user code (#31).
|
|
"""
|
|
|
|
class Request:
|
|
@classmethod
|
|
def request(cls, method, args):
|
|
pass
|
|
|
|
m = mocker.patch.object(Request, 'request')
|
|
Request.request(method='get', args={'type': 'application/json'})
|
|
m.assert_called_once_with(method='get', args={'type': 'application/json'})
|
|
|
|
|
|
def test_monkeypatch_native(testdir):
|
|
"""Automatically disable monkeypatching when --tb=native.
|
|
"""
|
|
testdir.makepyfile("""
|
|
def test_foo(mocker):
|
|
stub = mocker.stub()
|
|
stub(1, greet='hello')
|
|
stub.assert_called_once_with(1, greet='hey')
|
|
""")
|
|
result = runpytest_subprocess(testdir, '--tb=native')
|
|
assert result.ret == 1
|
|
assert 'During handling of the above exception' not in result.stdout.str()
|
|
assert 'Differing items:' not in result.stdout.str()
|
|
traceback_lines = [x for x in result.stdout.str().splitlines()
|
|
if 'Traceback (most recent call last)' in x]
|
|
assert len(traceback_lines) == 1 # make sure there are no duplicated tracebacks (#44)
|
|
|
|
|
|
@pytest.mark.skipif(sys.version_info[0] < 3, reason='Py3 only')
|
|
def test_standalone_mock(testdir):
|
|
"""Check that the "mock_use_standalone" is being used.
|
|
"""
|
|
testdir.makepyfile("""
|
|
def test_foo(mocker):
|
|
pass
|
|
""")
|
|
testdir.makeini("""
|
|
[pytest]
|
|
mock_use_standalone_module = true
|
|
""")
|
|
result = runpytest_subprocess(testdir)
|
|
assert result.ret == 3
|
|
result.stderr.fnmatch_lines([
|
|
"*No module named 'mock'*",
|
|
])
|
|
|
|
|
|
def runpytest_subprocess(testdir, *args):
|
|
"""Testdir.runpytest_subprocess only available in pytest-2.8+"""
|
|
if hasattr(testdir, 'runpytest_subprocess'):
|
|
return testdir.runpytest_subprocess(*args)
|
|
else:
|
|
# pytest 2.7.X
|
|
return testdir.runpytest(*args)
|
|
|
|
|
|
def test_detailed_introspection(testdir):
|
|
"""Check that the "mock_use_standalone" is being used.
|
|
"""
|
|
testdir.makepyfile("""
|
|
def test(mocker):
|
|
m = mocker.Mock()
|
|
m('fo')
|
|
m.assert_called_once_with('', bar=4)
|
|
""")
|
|
result = testdir.runpytest('-s')
|
|
result.stdout.fnmatch_lines([
|
|
"*AssertionError: Expected call: mock('', bar=4)*",
|
|
"*Actual call: mock('fo')*",
|
|
"*pytest introspection follows:*",
|
|
'*Args:',
|
|
"*assert ('fo',) == ('',)",
|
|
"*At index 0 diff: 'fo' != ''*",
|
|
"*Use -v to get the full diff*",
|
|
"*Kwargs:*",
|
|
"*assert {} == {'bar': 4}*",
|
|
"*Right contains more items:*",
|
|
"*{'bar': 4}*",
|
|
"*Use -v to get the full diff*",
|
|
])
|
|
|
|
|
|
def test_assert_called_with_unicode_arguments(mocker):
|
|
"""Test bug in assert_call_with called with non-ascii unicode string (#91)"""
|
|
stub = mocker.stub()
|
|
stub(b'l\xc3\xb6k'.decode('UTF-8'))
|
|
|
|
with pytest.raises(AssertionError):
|
|
stub.assert_called_with(u'lak')
|
|
|