Code Monkey home page Code Monkey logo

pytest-mock's Introduction

pytest-mock

This plugin provides a mocker fixture which is a thin-wrapper around the patching API provided by the mock package:

import os

class UnixFS:

    @staticmethod
    def rm(filename):
        os.remove(filename)

def test_unix_fs(mocker):
    mocker.patch('os.remove')
    UnixFS.rm('file')
    os.remove.assert_called_once_with('file')

Besides undoing the mocking automatically after the end of the test, it also provides other nice utilities such as spy and stub, and uses pytest introspection when comparing calls.

python version anaconda docs ci coverage black pre-commit

Professionally supported pytest-mock is available.

Documentation

For full documentation, please see https://pytest-mock.readthedocs.io/en/latest.

License

Distributed under the terms of the MIT license.

pytest-mock's People

Contributors

alexgascon avatar asfaltboy avatar attomos avatar blueyed avatar cclauss avatar fogo avatar graingert avatar hugovk avatar inderpreet99 avatar jhermann avatar jurko-gospodnetic avatar kjwilcox avatar mgorny avatar mkoeppe avatar niccolum avatar nicoddemus avatar perchunpak avatar plannigan avatar pre-commit-ci[bot] avatar ronnypfannschmidt avatar rouge8 avatar scorphus avatar sgaist avatar shadycuz avatar srittau avatar the-compiler avatar tigarmo avatar tirkarthi avatar webknjaz avatar yesthesoup avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pytest-mock's Issues

Helper to assert (complete) list of calls in any order?

With assert_has_calls there's no method to ensure that the list is the complete list of calls: it will only check if the expected list is in the list of calls.

This can be combined with call_count then:

from unittest.mock import call

expected_calls = [call(1), call(2)]
mock.assert_has_calls(expected_calls, any_order=True)
assert mock.call_count == len(expected_calls)

This could be shortened to e.g.

mock.assert_calls(expected_calls, any_order=True)

any_order might default to True for this wrapper then even:

mock.assert_calls(expected_calls)

btw: while there is mock_calls, it does not have the any_order=True feature, and
call / _Call are not hashable and cannot be sorted.

On the other hand, it might be simpler to just use assert_any_call multiple times (for each call), and then assert call_count, too. This makes it easier to see what call is missing (where the diff might be hard to read):

mock.assert_any_call(1)
mock.assert_any_call(2)
assert mock.call_count == 2

So there's probably not enough reason for adding this to pytest-mock, and if so, it should be (proposed to get) added to unittest directly then?

fixture 'mocker' not found

I haven't looked too closely into this bug yet, but the error itself is strange, given that it explicitly lists 'mocker' as one of the available fixtures.

I had run tox -r previously and everything passed. Then I upgraded

  • pytest (2.9.2) -> 3.0.3
  • pytest-mock (1.2) -> 1.4.0

And started seeing this failure.

  @pytest.fixture
  def request(mocker):
E       fixture 'mocker' not found
>       available fixtures: cache, capfd, capsys, doctest_namespace, mock, mocker, monkeypatch, pytestconfig, record_xml_property, recwarn, request, requests_get, tmpdir, tmpdir_factory
>       use 'pytest --fixtures [testpath]' for help on them.

Code looks something like this:

import pytest
from mock import Mock, sentinel
from requests import codes

@pytest.fixture
def request(mocker):
    return Mock((), url='https://test-url.com:8000', cookies=None, headers={})

distutils import is problematic (causes error with frozen tests)

distutils is a somewhat weird biest, and it's used in #27. I thought a simple util method would be okay while reviewing - turns out I was wrong.

I get this when trying to run the tests frozen via cx_Freeze:

C:\projects\qutebrowser\.tox\unittests-frozen\build\library.zip\distutils\__init__.py:14: UserWarning: The virtualenv distutils package at %s appears to be in the same location as the system distutils?
Traceback (most recent call last):
  File "C:\projects\qutebrowser\.tox\unittests-frozen\lib\site-packages\cx_Freeze\initscripts\Console.py", line 27, in <module>
    exec(code, m.__dict__)
  File "scripts/dev/run_frozen_tests.py", line 27, in <module>
    import pytest_mock
  File "c:\python\32-bit\3.4\lib\importlib\_bootstrap.py", line 2237, in _find_and_load
  File "c:\python\32-bit\3.4\lib\importlib\_bootstrap.py", line 2226, in _find_and_load_unlocked
  File "c:\python\32-bit\3.4\lib\importlib\_bootstrap.py", line 1191, in _load_unlocked
  File "c:\python\32-bit\3.4\lib\importlib\_bootstrap.py", line 1161, in _load_backward_compatible
  File "C:\projects\qutebrowser\.tox\unittests-frozen\lib\site-packages\pytest_mock.py", line 5, in <module>
    from distutils.util import strtobool
  File "c:\python\32-bit\3.4\lib\importlib\_bootstrap.py", line 2237, in _find_and_load
  File "c:\python\32-bit\3.4\lib\importlib\_bootstrap.py", line 2212, in _find_and_load_unlocked
  File "c:\python\32-bit\3.4\lib\importlib\_bootstrap.py", line 321, in _call_with_frames_removed
  File "c:\python\32-bit\3.4\lib\importlib\_bootstrap.py", line 2237, in _find_and_load
  File "c:\python\32-bit\3.4\lib\importlib\_bootstrap.py", line 2226, in _find_and_load_unlocked
  File "c:\python\32-bit\3.4\lib\importlib\_bootstrap.py", line 1191, in _load_unlocked
  File "c:\python\32-bit\3.4\lib\importlib\_bootstrap.py", line 1161, in _load_backward_compatible
  File "C:\projects\qutebrowser\.tox\unittests-frozen\lib\distutils\__init__.py", line 25, in <module>
    from distutils import dist, sysconfig
ImportError: cannot import name 'dist'

I guess it'd be better to just do {'false': False, 'true': True}[val.lower()] or so.

cc @Chronial

TypeError: super() argument 1 must be type, not MagicMock

My test:

class TestController:

    @pytest.fixture(autouse=True)
    def mock_external_classes(self, mocker):
        mocker.patch('zulipterminal.ui.View')

    def test_initialize_controller(self):
        Controller(config_file, theme)

Controller:

class Controller:
    def __init__(self):
        self.view = View()

View:

Class View(urwid.WidgetWrap):
    def __init__(self):
        ...
        super(View, self).__init__(self.main_window())
...

This is the error:

====================================================== test session starts =======================================================
platform linux -- Python 3.5.2, pytest-3.4.2, py-1.5.3, pluggy-0.6.0
rootdir: /home/a/zulip-terminal, inifile:
plugins: pep8-1.0.6, mock-1.7.1, cov-2.5.1
collected 3 items

tests/core/test_core.py F                                                                                                  [ 33%]
tests/helper/test_helper.py ..                                                                                             [100%]

============================================================ FAILURES ============================================================
___________________________________________ TestController.test_initialize_controller ____________________________________________

self = <tests.core.test_core.TestController object at 0x7fe78c304ac8>

    def test_initialize_controller(self):
        config_file = 'path/to/zuliprc'
        theme = 'default'
>       self.controller = Controller(config_file, theme)

tests/core/test_core.py:18:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
zulipterminal/core.py:22: in __init__
    self.view = View(self)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <[AttributeError("'View' object has no attribute '_wrapped_widget'") raised in repr()] View object at 0x7fe78c3ccef0>
controller = <zulipterminal.core.Controller object at 0x7fe78c433400>

    def __init__(self, controller: Any) -> None:
        self.controller = controller
        self.model = controller.model
        self.client = controller.client
        self.users = self.model.users
        self.streams = self.model.streams
        self.write_box = WriteBox(self)
>       super(View, self).__init__(self.main_window())
E       TypeError: super() argument 1 must be type, not MagicMock

zulipterminal/ui.py:63: TypeError
=============================================== 1 failed, 2 passed in 0.19 seconds ===============================================
a@a:~/zulip-terminal$ pytest
====================================================== test session starts =======================================================
platform linux -- Python 3.5.2, pytest-3.4.2, py-1.5.3, pluggy-0.6.0
rootdir: /home/a/zulip-terminal, inifile:
plugins: pep8-1.0.6, mock-1.7.1, cov-2.5.1
collected 3 items

tests/core/test_core.py E                                                                                                  [ 33%]
tests/helper/test_helper.py ..                                                                                             [100%]

============================================================= ERRORS =============================================================
__________________________________ ERROR at setup of TestController.test_initialize_controller ___________________________________

self = <tests.core.test_core.TestController object at 0x7f4b4ff8ab00>, mocker = <pytest_mock.MockFixture object at 0x7f4b4ff8ada0>

    @pytest.fixture(autouse=True)
    def mock_external_classes(self, mocker):
        self.client = mocker.patch('zulip.Client')
        self.model = mocker.patch('zulipterminal.model.Model')
>       self.view = mocker.patch.object('zulipterminal.ui.View')

tests/core/test_core.py:13:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../.local/lib/python3.5/site-packages/pytest_mock.py:141: in object
    return self._start_patch(self.mock_module.patch.object, *args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <pytest_mock.MockFixture._Patcher object at 0x7f4b4ff8add8>, mock_func = <function _patch_object at 0x7f4b50263488>
args = ('zulipterminal.ui.View',), kwargs = {}

    def _start_patch(self, mock_func, *args, **kwargs):
        """Patches something by calling the given function from the mock
                module, registering the patch to stop it later and returns the
                mock object resulting from the mock call.
                """
>       p = mock_func(*args, **kwargs)
E       TypeError: _patch_object() missing 1 required positional argument: 'attribute'

../.local/lib/python3.5/site-packages/pytest_mock.py:132: TypeError
=============================================== 2 passed, 1 error in 0.19 seconds ================================================
a@a:~/zulip-terminal$ pytest
====================================================== test session starts =======================================================
platform linux -- Python 3.5.2, pytest-3.4.2, py-1.5.3, pluggy-0.6.0
rootdir: /home/a/zulip-terminal, inifile:
plugins: pep8-1.0.6, mock-1.7.1, cov-2.5.1
collected 3 items

tests/core/test_core.py F                                                                                                  [ 33%]
tests/helper/test_helper.py ..                                                                                             [100%]

============================================================ FAILURES ============================================================
___________________________________________ TestController.test_initialize_controller ____________________________________________

self = <tests.core.test_core.TestController object at 0x7f902b688a90>

    def test_initialize_controller(self):
        config_file = 'path/to/zuliprc'
        theme = 'default'
>       self.controller = Controller(config_file, theme)

tests/core/test_core.py:18:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
zulipterminal/core.py:22: in __init__
    self.view = View(self)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <[AttributeError("'View' object has no attribute '_wrapped_widget'") raised in repr()] View object at 0x7f902b749e80>
controller = <zulipterminal.core.Controller object at 0x7f902b7b0390>

    def __init__(self, controller: Any) -> None:
        self.controller = controller
        self.model = controller.model
        self.client = controller.client
        self.users = self.model.users
        self.streams = self.model.streams
        self.write_box = WriteBox(self)
>       super(View, self).__init__(self.main_window())
E       TypeError: super() argument 1 must be type, not MagicMock

zulipterminal/ui.py:63: TypeError
=============================================== 1 failed, 2 passed in 0.19 seconds ===============================================

I can't find solution anywhere for this.

Undo mocks on debugging hooks?

I wrote a series of test cases which patch builtin.open() to return a StringIO/BytesIO. pytest-mock handles these well, but when the test fails, pytest's exception handler uses open to print info about source files. PDB is also broken in test cases that patch open(). Errors like the following will occur when trying to use pdb, either directly or via --pdb:

ValueError: I/O operation on closed file.

pytest has a few hooks related to debugging, including pytest_enter_pdb and pytest_exception_interact - both seem promising. It would be nice to be able to patch open() in a way that would get undone if the test fails and the exception handler is entered.

mocker.patch on function

Hi,
When I want to mock a function in a module, the mock not work if I use this syntax :

from mypkg.mymodule import myfunction

  • myfunction()

but work with this one :

from mypkg import mymodule
+mymodule.myfunction()

Any idea ?

Can't mock sys.argv

I'm trying to mock sys.argv using mocker.patch('sys.argv', ['script.py', 'all']) but I get AttributeError: __enter__. Am I missing something? When I use unittest.mock.patch the call works without any problem.

The test:

def it_outputs_answers_based_on_an_argument(when, mocker):
    with mocker.patch('sys.argv', ['cli.py', 'all']):
        when('app.cli.ApiClient').search_answers('all').thenReturn([
            {
                'id': 1,
                'question': 'A?',
                'content': 'B'
            }
        ])

        with mocker.patch('sys.stdout', StringIO()) as stdout:
            main()
            output = stdout.getvalue().strip()
            expect(output).to(equal('A?\n--\nB'))

0.8.1 incompatible with pytest-qt + pytest-cov on travis

When both pytest-qt and pytest-cov are installed, none of the py test plugins are picked up by py.test --fixtures .

Only happens if all three plugins are loaded. Unclear if this is pytest-mock's fault, pytest-cov, pytest-qt or py.test's fault.

Minimally failing travis build here: https://travis-ci.org/FAForever/server/builds/88285397

Log output:

    $ python --version
    Python 3.5.0
    $ pip --version
    pip 7.1.2 from /home/travis/virtualenv/python3.5.0/lib/python3.5/site-packages (python 3.5)
    install
    4.04s$ pip install --use-wheel pytest-mock pytest-qt pytest-cov
    Collecting pytest-mock
      Downloading pytest_mock-0.8.1-py2.py3-none-any.whl
    Collecting pytest-qt
      Downloading pytest_qt-1.8.0-py2.py3-none-any.whl
    Collecting pytest-cov
      Downloading pytest_cov-2.2.0-py2.py3-none-any.whl
    Requirement already satisfied (use --upgrade to upgrade): pytest>=2.4 in /home/travis/virtualenv/python3.5.0/lib/python3.5/site-packages (from pytest-mock)
    Collecting coverage>=3.7.1 (from pytest-cov)
      Downloading coverage-4.0.1.tar.gz (349kB)
        100% |████████████████████████████████| 352kB 756kB/s
    Requirement already satisfied (use --upgrade to upgrade): py>=1.4.29 in /home/travis/virtualenv/python3.5.0/lib/python3.5/site-packages (from pytest>=2.4->pytest-mock)
    Building wheels for collected packages: coverage
      Running setup.py bdist_wheel for coverage
      Stored in directory: /home/travis/.cache/pip/wheels/3d/2d/5f/5e812a155cc0752a0f99157ee14aebbcd87b8d425083f463f5
    Successfully built coverage
    Installing collected packages: pytest-mock, pytest-qt, coverage, pytest-cov
    Successfully installed coverage-4.0.1 pytest-cov-2.2.0 pytest-mock-0.8.1 pytest-qt-1.8.0
    0.55s$ py.test --fixtures . | grep mock
    The command "py.test --fixtures . | grep mock" exited with 1.
    Done. Your build exited with 1.

Confirmed working with pytest-mock==0.7

Can you make the license on this MIT?

I can't use any form of GPL at work (including LGPL). Even if it was possible, the handling considerations wouldn't be worth it for a plugin. A few months ago I had a remote worker try to borrow code from an LGPL plugin for a project, thinking "hey it's open source, and freedom". Its not easy to convey the ramification of creating a derivative work.

pytest itself is MIT.

Make spy a context manager

Capture return value

NOTE: already implemented in 1.11

class SomeClass:
    def method(self, a):
        return a * 2

    def facade(self):
        self.method(4)
        return 'Done'


def test_some_class(mocker):
    spy = mocker.spy(SomeClass, 'method')
    o = SomeClass()
    o.facade()
    assert spy.called
    assert spy.call_count == 1

    print(spy.mock_calls[0])
    # nicely outputs method arguments
    # call(<m.SomeClass object at 0x7febfed74518>, 4)

    print(spy.return_value)
    # just tells us we have mock object here
    # <MagicMock name='method()' id='140651569552856'>
    
    # it would be much better to have there
    # assert spy.return_value == 8
    # or, rather
    # assert spy.mock_calls[0].return_value == 8

Spy as context manager

That's probably kind of misusing mocker object, but in my case there's a quite long test and I want to restrict fragment of code where particular spy object is applied.

def test_context_mgr(mocker):
    o = SomeClass()
    with mocker.spy(SomeClass, 'method') as spy:  # AttributeError: __enter__ here
        o.facade()
        assert spy.call_count == 1
    o.facade()

Currently I can only find arguments

Test failures in test_assert_called_args_with_introspection

Whilst attempting to integrate pytest-mock to the Debian CI infrastructure, I encountered the following issue when running the tests against the installed version from the Debian package:

=================================== FAILURES ===================================
__________________ test_assert_called_args_with_introspection __________________

mocker = <pytest_mock.MockFixture object at 0x7f46460fce10>

    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)

test_pytest_mock.py:390: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python2.7/contextlib.py:35: in __exit__
    self.gen.throw(type, value, traceback)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

left = ('a', 1, set(['test'])), right = ('b', 2, set(['jest']))

    @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)
E           assert "Full diff:\n  - ('a', 1, set(['test']))\n  ?   ^   ^        ^\n  + ('b', 2, set(['jest']))\n  ?   ^   ^        ^" in "Expected call: mock('b', 2, set(['jest']))\nActual call: mock('a', 1, set(['test']))\n\npytest introspection follows:\n\nArgs:\n"
E            +  where "Expected call: mock('b', 2, set(['jest']))\nActual call: mock('a', 1, set(['test']))\n\npytest introspection follows:\n\nArgs:\n" = str(AssertionError(u"Expected call: mock('b', 2, set(['jest']))\nActual call: mock('a', 1, set(['test']))\n\npytest introspection follows:\n\nArgs:\n",))

test_pytest_mock.py:346: AssertionError
_________________ test_assert_called_kwargs_with_introspection _________________

mocker = <pytest_mock.MockFixture object at 0x7f464627c150>

    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)

test_pytest_mock.py:405: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python2.7/contextlib.py:35: in __exit__
    self.gen.throw(type, value, traceback)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

left = {'foo': {'bar': 1, 'baz': 'spam'}}
right = {'foo': {'baz': 'bran', 'goo': 1}}

    @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)
E           assert "Full diff:\n  - {'foo': {'bar': 1, 'baz': 'spam'}}\n  + {'foo': {'baz': 'bran', 'goo': 1}}" in "Expected call: mock(foo={'goo': 1, 'baz': 'bran'})\nActual call: mock(foo={'baz': 'spam', 'bar': 1})\n\npytest introspection follows:\n\nKwargs:\n"
E            +  where "Expected call: mock(foo={'goo': 1, 'baz': 'bran'})\nActual call: mock(foo={'baz': 'spam', 'bar': 1})\n\npytest introspection follows:\n\nKwargs:\n" = str(AssertionError(u"Expected call: mock(foo={'goo': 1, 'baz': 'bran'})\nActual ca...foo={'baz': 'spam', 'bar': 1})\n\npytest introspection follows:\n\nKwargs:\n",))

test_pytest_mock.py:346: AssertionError
_________________________ test_detailed_introspection __________________________

testdir = <Testdir local('/tmp/pytest-of-root/pytest-0/testdir/test_detailed_introspection0')>

    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*",
        ])
E       Failed: nomatch: "*AssertionError: Expected call: mock('', bar=4)*"
E           and: u'============================= test session starts =============================='
E           and: u'platform linux2 -- Python 2.7.14+, pytest-3.2.5, py-1.4.34, pluggy-0.4.0'
E           and: u'rootdir: /tmp/pytest-of-root/pytest-0/testdir/test_detailed_introspection0, inifile:'
E           and: u'plugins: mock-1.6.3'
E           and: u'collected 1 item'
E           and: u''
E           and: u'test_detailed_introspection.py F'
E           and: u''
E           and: u'=================================== FAILURES ==================================='
E           and: u'_____________________________________ test _____________________________________'
E           and: u''
E           and: u'mocker = <pytest_mock.MockFixture object at 0x7f4646277c90>'
E           and: u''
E           and: u'    def test(mocker):'
E           and: u'        m = mocker.Mock()'
E           and: u"        m('fo')"
E           and: u">       m.assert_called_once_with('', bar=4)"
E       fnmatch: "*AssertionError: Expected call: mock('', bar=4)*"
E          with: u"E       AssertionError: Expected call: mock('', bar=4)"
E       fnmatch: "*Actual call: mock('fo')*"
E          with: u"E       Actual call: mock('fo')"
E       nomatch: '*pytest introspection follows:*'
E           and: u'E       '
E       fnmatch: '*pytest introspection follows:*'
E          with: u'E       pytest introspection follows:'
E       nomatch: '*Args:'
E           and: u'E       '
E       fnmatch: '*Args:'
E          with: u'E       Args:'
E       nomatch: "*assert ('fo',) == ('',)"
E           and: u'E       '
E           and: u'E       Kwargs:'
E           and: u''
E           and: u'test_detailed_introspection.py:4: AssertionError'
E           and: u'=========================== 1 failed in 0.01 seconds ==========================='
E           and: u''
E       remains unmatched: "*assert ('fo',) == ('',)"

/tmp/autopkgtest.31sHEi/autopkgtest_tmp/test_pytest_mock.py:535: Failed
----------------------------- Captured stdout call -----------------------------
============================= test session starts ==============================
platform linux2 -- Python 2.7.14+, pytest-3.2.5, py-1.4.34, pluggy-0.4.0
rootdir: /tmp/pytest-of-root/pytest-0/testdir/test_detailed_introspection0, inifile:
plugins: mock-1.6.3
collected 1 item

test_detailed_introspection.py F

=================================== FAILURES ===================================
_____________________________________ test _____________________________________

mocker = <pytest_mock.MockFixture object at 0x7f4646277c90>

    def test(mocker):
        m = mocker.Mock()
        m('fo')
>       m.assert_called_once_with('', bar=4)
E       AssertionError: Expected call: mock('', bar=4)
E       Actual call: mock('fo')
E       
E       pytest introspection follows:
E       
E       Args:
E       
E       Kwargs:

test_detailed_introspection.py:4: AssertionError
=========================== 1 failed in 0.01 seconds ===========================
========== 3 failed, 38 passed, 1 skipped, 2 xfailed in 0.69 seconds ===========

The plugin seems to be correctly registered:

============================= test session starts ==============================
platform linux2 -- Python 2.7.14+, pytest-3.2.5, py-1.4.34, pluggy-0.4.0 -- /usr/bin/python2.7
cachedir: .cache
rootdir: /tmp/autopkgtest.31sHEi/autopkgtest_tmp, inifile:
plugins: mock-1.6.3
collecting ... collected 44 items

The same thing happens for both Python 2 and Python 3.

Error for Python 3.6 nightly

Hi,

our cookiecutter build breaks for Python 3.6 nightly due to some error in pytest-mock.

    class MockFixture(object):
TypeError: '_SentinelObject' object is not callable

Figured I'll raise this here although it's certainly not a problem at this point as Python 3.6 isn't out just yet.

Two tracebacks with assert_wrapper / wrap_assert_has_calls

When using assert_has_calls I am seeing two tracebacks, but the first one should not be there?!

Traceback (most recent call last):
  File "…/pytest-mock/pytest_mock.py", line 155, in assert_wrapper
    __wrapped_mock_method__(*args, **kwargs)
  File "/usr/lib64/python3.5/unittest/mock.py", line 824, in assert_has_calls
    ) from cause
AssertionError: Calls not found.
Expected: […]
Actual: […]

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "…/app/tests/test_metrics.py", line 495, in test_handle_missing_values
    expected_call(end, 2, None),
  File "…/pytest-mock/pytest_mock.py", line 181, in wrap_assert_has_calls
    *args, **kwargs)
  File "…/pytest-mock/pytest_mock.py", line 157, in assert_wrapper
    raise AssertionError(*e.args)
AssertionError: Calls not found.
Expected: […]
Actual: […]

This is with pytest-2.9.2.dev1 and Python 3.5.1.

It looks like the __tracebackhide__ = True trick is not working?!

With mock_traceback_monkeypatch=false I only get the original traceback as expected.

Patched objects created with mocker don't work with a mock manager

It seems like when I try to attach patched objects from pytest-mock to a manager mock with attach_mock, calls to the patched object are not propagated to the manager's mock_calls.

For example,

from mock import Mock, patch, call
import os


@patch('os.listdir')
def test_decorator(mock_listdir):
    mgr = Mock()
    mgr.attach_mock(mock_listdir, "listdir")

    os.listdir()

    assert mgr.mock_calls == [call.listdir()]


def test_mocker(mocker):
    mock_listdir = mocker.patch("os.listdir")
    mgr = Mock()
    mgr.attach_mock(mock_listdir, "listdir")

    os.listdir()

    assert mgr.mock_calls == [call.listdir()] 

The first test passes but the second fails with:

E       assert [] == [call.listdir()]
E         Right contains more items, first extra item: call.listdir()
E         Use -v to get the full diff

I'm guessing this is probably an issue with mock_attach but thought I'd start here first to see if anyone had any ideas.

I'm running 3.6.1 and

pbr==3.0.0
py==1.4.33
pytest==3.0.7
pytest-mock==1.6.0
six==1.10.0```

Provide a fixture that's not named "mock"

I really like this plugin, but I find calling the fixture "mock" conflicts with the actual mock module. It also results in code that looks wrong to the casual observer if they're familiar with the mock module but aren't aware of this plugin.

Would it be possible to additionally provide this fixture under a synonym like "patcher"?

Singleton instance mocking

I'd like to present you with the following problem: I have a singleton instance defined by

class Singleton(type):
  _instances = {}
  def __call__(cls, *args, **kwargs):
    if cls not in cls._instances:
      cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
    return cls._instances[cls]

class KeyHandler(metaclass=Singleton):
  def __init__(self, some_var):
    self.keys = self._retrieve_keys(some_var)
    <create some weird data structure using the keys variable, like cyclical rotation of keys>

Where the _retrieve_keys calls some external API do get some data. My idea is to mock out this method when testing the KeyHandler class. Easily done with:

@pytest.fixture
def key_response():
  return {'key1': 'value1', 'key2': 'value2'}

def test_keys_initialize(key_response, mocker):
  mocker.patch.object(credentials.KeyHandler, '_retrieve_keys', return_value=key_response)
  key_handler = credentials.KeyHandler('some_key')

This works, at least if these are your only tests. If however you have another test file that calls a module which instantiates the singleton then the tests will fail (the singleton will have been instantiated with an actual API call and not mocked).

I'm a bit lost to how to proceed with this module. My first hunch would be to instantiate the singleton in conftest.py with the mock API response, such that it can be used in all test files, thus:

@pytest.fixture(scope='module')
def key_response():
  return {'key1': 'value1', 'key2': 'value2'}

@pytest.fixture(scope='module')
def setup_key_handler(key_response, mocker):
  mocker.patch.object(credentials.KeyHandler, '_retrieve_parameter', return_value=key_response)
  return credentials.KeyHandler('some_key')

However, mocker is function scoped, causing a scope mismatch. Is there a way to proceed? How would you deal with this situation?

Thank you

Provide `mocker.call` name

In order to use the assert_has_calls check, you need the call name. I think it should be part of the set of 'convenience' names available via mocker.

The end goal is to make this work::

get_pwd = mocker.patch('keyring.get_password')
…
get_pwd.assert_has_calls(
    mocker.call(Credentials.KEYRING_SERVICE_DEFAULT, '[email protected]'),
    mocker.call(Credentials.KEYRING_SERVICE_DEFAULT, 'keyring.example.com'))

If you agree it should be available, I can do a PR.

How to mock methods of 3rd-party library?

Hi,

I'm trying to use pytest-mock for a small project and faced issue with mocking of methods of a 3rd-party library. I'm new to unit testing in Python so maybe my approach, in general, is wrong. Please, help :)

Folder structure

app/
    main.py
    tests/
        __init__.py (empty)
        test_main.py

app/main.py

class BoxClient:
    def __init__(self, client_id, client_secret, enterprise_id, jwt_key_id,
                 rsa_private_key_file_sys_path):
        self.client_id = client_id
        self.client_secret = client_secret
        self.enterprise_id = str(enterprise_id)
        self.jwt_key_id = jwt_key_id
        self.rsa_private_key_file_sys_path = rsa_private_key_file_sys_path

        auth = JWTAuth(
            client_id=self.client_id,
            client_secret=self.client_secret,
            enterprise_id=self.enterprise_id,
            jwt_key_id=self.jwt_key_id,
            rsa_private_key_file_sys_path=self.rsa_private_key_file_sys_path)

        self.access_token = auth.authenticate_instance()
        self.client = Client(auth)

    def get_subfolders(self, folder_id):
        subfolders = {}
        limit = 100
        offset = 0

        root_folder = self.client.folder(folder_id=folder_id)
        while True:
            search = root_folder.get_items(
                limit=limit, offset=offset, fields=["id", "name"])
            if search:
                for item in search:
                    subfolders[item["id"]] = item["name"]
                offset += limit
                continue
            else:
                return subfolders

app/tests/test_main.py

#! /usr/bin/env python3

import pytest
import boxsdk
import main

@pytest.fixture
def mock_client(mocker):
    mocker.patch("main.JWTAuth")
    box = main.BoxClient(None, None, None, None, None)

    box.client.return_value = mocker.Mock(boxsdk.client.Client)
    box.client.return_value.folder.return_value = mocker.Mock(
        boxsdk.folder.Folder)
    box.client.return_value.folder.return_value.get_items.return_value = {
        "0": "name"
    }
    return box


def test_get_subfolders(mock_client):
    assert mock_client.get_subfolders(0) == {"0": "name"}

Current pytest output

====================================================================== test session starts ======================================================================
platform darwin -- Python 3.6.2, pytest-3.3.2, py-1.5.2, pluggy-0.6.0
rootdir: /Users/user/app/tests, inifile:
plugins: mock-1.6.3
collected 1 item

test_main.py F                                                                                                                                            [100%]

=========================================================================== FAILURES ============================================================================
______________________________________________________________________ test_get_subfolders ______________________________________________________________________

mock_client = <main.BoxClient object at 0x10218d588>

    def test_get_subfolders(mock_client):
        expected = {"0": "name"}

>       assert mock_client.get_subfolders(0) == expected

test_main.py:25:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../main.py:33: in get_subfolders
    limit=limit, offset=offset, fields=["id", "name"])
../.venv/lib/python3.6/site-packages/boxsdk/object/folder.py:153: in get_items
    box_response = self._session.get(url, params=params)
../.venv/lib/python3.6/site-packages/boxsdk/session/box_session.py:395: in get
    response = self._prepare_and_send_request('GET', url, **kwargs)
../.venv/lib/python3.6/site-packages/boxsdk/session/box_session.py:299: in _prepare_and_send_request
    **kwargs
../.venv/lib/python3.6/site-packages/boxsdk/session/box_session.py:380: in _make_request
    **kwargs
../.venv/lib/python3.6/site-packages/boxsdk/session/box_session.py:185: in _retry_request_if_necessary
    return self._make_request(*args, **kwargs)
../.venv/lib/python3.6/site-packages/boxsdk/session/box_session.py:383: in _make_request
    self._raise_on_unsuccessful_request(network_response, expect_json_response, method, url)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <boxsdk.session.box_session.BoxSession object at 0x102198b38>
network_response = <boxsdk.network.default_network.DefaultNetworkResponse object at 0x1021ace80>, expect_json_response = True, method = 'GET'
url = 'https://api.box.com/2.0/folders/0/items'

    def _raise_on_unsuccessful_request(self, network_response, expect_json_response, method, url):
        """
            Raise an exception if the request was unsuccessful.

            :param network_response:
                The network response which is being tested for success.
            :type network_response:
                :class:`NetworkResponse`
            :param expect_json_response:
                Whether or not the response content should be json.
            :type expect_json_response:
                `bool`
            :param method:
                The HTTP verb used to make the request.
            :type method:
                `unicode`
            :param url:
                The request URL.
            :type url:
                `unicode`
            """
        if not network_response.ok:
            response_json = {}
            try:
                response_json = network_response.json()
            except ValueError:
                pass
            raise BoxAPIException(
                status=network_response.status_code,
                headers=network_response.headers,
                code=response_json.get('code', None),
                message=response_json.get('message', None),
                request_id=response_json.get('request_id', None),
                url=url,
                method=method,
>               context_info=response_json.get('context_info', None),
            )
E           boxsdk.exception.BoxAPIException:
E           Message: None
E           Status: 401
E           Code: None
E           Request id: None
E           Headers: {'Date': 'Fri, 26 Jan 2018 14:02:26 GMT', 'Content-Length': '0', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', 'WWW-Authenticate': 'Bearer realm="Service", error="invalid_token", error_description="The access token provided is invalid."', 'Age': '0', 'Connection': 'keep-alive'}
E           URL: https://api.box.com/2.0/folders/0/items
E           Method: GET
E           Context info: None

../.venv/lib/python3.6/site-packages/boxsdk/session/box_session.py:238: BoxAPIException
----------------------------------------------------------------------- Captured log call -----------------------------------------------------------------------
connectionpool.py          824 DEBUG    Starting new HTTPS connection (1): api.box.com
connectionpool.py          396 DEBUG    https://api.box.com:443 "GET /2.0/folders/0/items?limit=100&offset=0&fields=id%2Cname HTTP/1.1" 401 0
connectionpool.py          396 DEBUG    https://api.box.com:443 "GET /2.0/folders/0/items?limit=100&offset=0&fields=id%2Cname HTTP/1.1" 401 0
=================================================================== 1 failed in 1.61 seconds ====================================================================

My goal is to test behaviour of BoxClient.get_subfolders(). As far as I understand, I need to mock get_items() but my current attempt failed. Could anyone explain how to do it properly, please?

Confusing requirement on mock for Python 2

The README explicitly list mock as a requirement for Python 2, whereas it is listed in extras_require in the setup script. Please clarify whether mock is an optional or required dependency for pytest-mock with Python 2. If optional, please adjust the wording of the README, if required (which I suspect), please list mock under install_requires for Python 2.

pytest-mock-1.6.0: test failure with python-3.x

When running the tests for pytest-mock-1.6.0 (pypi distfile) on NetBSD
with python-3.6.1, there is one test failure:

============================================================================= test session starts =============================================================================
platform netbsd7 -- Python 3.6.1, pytest-3.0.7, py-1.4.30, pluggy-0.4.0
rootdir: .../pytest-mock-1.6.0, inifile:
plugins: mock-1.6.0
collected 43 items 

test_pytest_mock.py .........................................F.

================================================================================== FAILURES ===================================================================================
____________________________________________________________________________ test_standalone_mock _____________________________________________________________________________

testdir = <Testdir local('/tmp/pytest-of-pbulk/pytest-3/testdir/test_standalone_mock0')>

    @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
E       assert 0 == 3
E        +  where 0 = <_pytest.pytester.RunResult object at 0x799b317012d8>.ret

.../pytest-mock-1.6.0/test_pytest_mock.py:503: AssertionError
---------------------------------------------------------------------------- Captured stdout call -----------------------------------------------------------------------------
running: /usr/pkg/bin/python3.6 /usr/pkg/lib/python3.6/site-packages/pytest.py --basetemp=/tmp/pytest-of-pbulk/pytest-3/testdir/test_standalone_mock0/runpytest-0
     in: /tmp/pytest-of-pbulk/pytest-3/testdir/test_standalone_mock0
============================= test session starts ==============================
platform netbsd7 -- Python 3.6.1, pytest-3.0.7, py-1.4.30, pluggy-0.4.0
rootdir: /tmp/pytest-of-pbulk/pytest-3/testdir/test_standalone_mock0, inifile: tox.ini
plugins: mock-1.6.0
collected 1 items

test_standalone_mock.py .

=========================== 1 passed in 0.01 seconds ===========================
===================================================================== 1 failed, 42 passed in 1.72 seconds =====================================================================
*** Error code 1

Provide a helper to copy args

mock will store references in call_args and call_args_list (see https://docs.python.org/3/library/unittest.mock-examples.html#coping-with-mutable-arguments).

I think that pytest-mock could provide a helper based on the example from the doc:

from copy import deepcopy
>>> class CopyingMock(MagicMock):
...     def __call__(self, *args, **kwargs):
...         args = deepcopy(args)
...         kwargs = deepcopy(kwargs)
...         return super(CopyingMock, self).__call__(*args, **kwargs)

The following works (by extending the pytest-mock mocker fixture).

@pytest.fixture
def mocker(mocker):
    from copy import deepcopy
    from mock import MagicMock

    class CopyingMock(MagicMock):
        def __call__(self, *args, **kwargs):
            args = deepcopy(args)
            kwargs = deepcopy(kwargs)
            return super(CopyingMock, self).__call__(*args, **kwargs)

    mocker.CopyingMock = CopyingMock
    return mocker
    patched = mocker.patch('foo.bar', new_callable=mocker.CopyingMock)

Not sure if that's helpful enough and/or if there could be a mocker_copy fixture instead, which would handle new_callable not only for patch().

stub.assert_called_once_with has distracting reporting

assert_called_once_with and the other assert_ functions show in the traceback, which is very unhelpful. __tracebackhide__ would help, but this is is a system lib :).

Test

def test_stub(mocker):
    stub = mocker.stub()
    stub.assert_called_once_with('foo', 'baz')

Output:

================================================================================== FAILURES ==================================================================================
_________________________________________________________________________________ test_stub __________________________________________________________________________________

mocker = <pytest_mock.MockFixture object at 0x7f52cb320cc0>

    def test_stub(mocker):
        stub = mocker.stub()
>       stub.assert_called_once_with('foo', 'baz')

mocker     = <pytest_mock.MockFixture object at 0x7f52cb320cc0>
stub       = <MagicMock spec='function' id='139993573101520'>

tests/ston/test_utils.py:21:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

_mock_self = <MagicMock spec='function' id='139993573101520'>, args = ('foo', 'baz'), kwargs = {}, self = <MagicMock spec='function' id='139993573101520'>
msg = "Expected 'mock' to be called once. Called 0 times."

    def assert_called_once_with(_mock_self, *args, **kwargs):
        """assert that the mock was called exactly once and with the specified
            arguments."""
        self = _mock_self
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times." %
                   (self._mock_name or 'mock', self.call_count))
>           raise AssertionError(msg)
E           AssertionError: Expected 'mock' to be called once. Called 0 times.

_mock_self = <MagicMock spec='function' id='139993573101520'>
args       = ('foo', 'baz')
kwargs     = {}
msg        = "Expected 'mock' to be called once. Called 0 times."
self       = <MagicMock spec='function' id='139993573101520'>

/usr/lib/python3.4/unittest/mock.py:781: AssertionError

Expected Output:

================================================================================== FAILURES ==================================================================================
_________________________________________________________________________________ test_stub __________________________________________________________________________________

mocker = <pytest_mock.MockFixture object at 0x7f07b55ed128>

    def test_stub(mocker):
        stub = mocker.stub()
>       stub.assert_called_once_with('foo', 'baz')
E       AssertionError: Expected 'mock' to be called once. Called 0 times.

mocker     = <pytest_mock.MockFixture object at 0x7f07b55ed128>
stub       = <MagicMock spec='function' id='139671084389320'>

tests/ston/test_utils.py:21: AssertionError

Warn when no specs are used

Mock without specs is so forgiving that people often write tests whose asserts do nothing

This is mainly because the apu of mock is severely flawed and error-prone

Spyder Not starting

My spyder just started and close automatically. It shows the following messages..
<spyder.otherplugins._ModuleMock object at 0x7fcb757a5780>: '_ModuleMock' object has no attribute 'PLUGIN_CLASS'
Traceback (most recent call last):
File "/home/rusa/anaconda3/lib/python3.6/site-packages/spyder/app/mainwindow.py", line 1086, in setup
plugin = mod.PLUGIN_CLASS(self)
AttributeError: '_ModuleMock' object has no attribute 'PLUGIN_CLASS'
Segmentation fault (core dumped)

Patches not stopped between tests.

Hi,

My patches seem to be leeking from one test to the other. Can you suggest why this is?

Tests below:

import asyncio
import logging
import sys
import time

import pytest
import websockets
from asynctest import CoroutineMock, MagicMock

from pyskyq.status import Status

from .asynccontextmanagermock import AsyncContextManagerMock
from .mock_constants import WS_STATUS_MOCK

logformat = "[%(asctime)s] %(levelname)s:%(name)s:%(message)s"
logging.basicConfig(level=logging.WARNING, stream=sys.stdout,
                    format=logformat)  # datefmt="%Y-%m-%d %H:%M:%S"


# THIS TEST WORKS FINE
def test_status(mocker):
    a = mocker.patch('websockets.connect', new_callable=AsyncContextManagerMock)
    a.return_value.__aenter__.return_value.recv = CoroutineMock(return_value=WS_STATUS_MOCK)

    stat = Status('some_host')
    stat.create_event_listener()

    time.sleep(1) # allow time for awaiting, etc.
    assert stat.standby is True

    mocker.stopall()

def wait_beyond_timeout_then_serve_json():
    time.sleep(3)
    raise websockets.exceptions.ConnectionClosed
    #return WS_STATUS_MOCK

# THIS TEST HAS THE MOCK FROM THE PREVIOUS TEST STILL IN PLACE
def test_status_timeout(mocker):
    mocker.stopall()
    b = mocker.patch('websockets.connect', new_callable=AsyncContextManagerMock)
    b.return_value.__aenter__.return_value.recv = CoroutineMock(side_effect=wait_beyond_timeout_then_serve_json)
    b.start()
    stat = Status('timeout_host', ws_timeout=2)

    logging.getLogger().setLevel(logging.DEBUG)

    time.sleep(1)
    with pytest.raises(asyncio.TimeoutError):
        stat.create_event_listener()
    b.stop()

Alias _Call class?

I am/was using code like the following to detect if some object is a _Call instance (to stop recursion in this case):

    from pytest_mock import mock_module

    try:
        _Call = mock_module.mock._Call
    except AttributeError:
        # unittest.mock
        _Call = mock_module._Call
    if isinstance(exp, _Call):
        ...

Since mock_module has been moved in a4cfee7, I wonder what is the best way to fix / address this.

Random failure of returning the desired value for a patch.object

Description

I observed a strange behaviour when using the mocker fixture on python 3.3,
that is not present under python 2.7. I can reliably reproduce it on my
machine, but it seems random: it will fail roughly half of the time.

When having two mocker.patch.object statements one after the other, the
second one will sometimes be set to the wrong value.

I realise this is a sub-optimal bug report, and there is a possibility that the
problem do not even happen on your machine, but I can see no reason behind it
that could be link to my particular setup.

How to reproduce it

this code, when run through python 3.3.2, pytest 2.6.3, and pytest-mock x.x.x, fails half of the time on my machine.

from PySide.QtGui import QMessageBox, QFrame

class Simple(QFrame):

    def query(self, notebook):
        reply = QMessageBox.question(
            self, 'Message', 'Deleting %s, are you sure?' % notebook,
            QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.answer = 'yes'
        else:
            self.answer = 'no'

def test_Qt(qtbot, mocker):
    simple = Simple()
    qtbot.addWidget(simple)

    mocker.patch.object(QMessageBox, 'question',
                        return_value=QMessageBox.No)
    simple.query('toto')
    assert simple.answer == 'no'

    mocker.patch.object(QMessageBox, 'question',
                        return_value=QMessageBox.Yes)
    simple.query('toto')
    assert simple.answer == 'yes'

When run through python 2.7, on the other hand, everything works 100% of the
time. Hence my question:

Is it somehow forbidden to use two mock.patch.object statements in a row,
without doing some sort of cleaning? And that it is pure luck that this works
under 2.7? It seemed to me that the original mock module uses a Python
with ...: syntax, probably for house cleaning. There is no such thing in this
module, right?

Or is it something due to the fact that now mock is integrated in py.test,
since Python 3.3?

Thanks in advance for any help you could provide.

How to use this with a class of tests?

I'm new to this library. I'm using a class of tests with a setUp method. When I try add mocker as a parameter to a test method, I get TypeError: test_solution_draft_update_modified_admin() takes exactly 2 arguments (1 given). What's the best way to use the mocker in a class of tests?

class ViewTests(APITestCase):
    def setUp(self):
        # do set up

    def test_view(self, mocker):
        # test logic

TypeError when assert_called_with is used

I get an odd behavior during assert_called_with when using mocker.

TypeError: assert_wrapper() got multiple values for keyword argument

Also there is no issue when mock itself is used.

UnicodeEncodeError in detailed introspection of assert_called_with

Comparing called arguments with assert_called_with fails with UnicodeEncodeError when one of the arguments (either on the left or right) is a unicode string, non ascii.

Python 2.7.13

Below are two test cases that looks like they should work:

def test_assert_called_with_unicode_wrong_argument(mocker):
    stub = mocker.stub()
    stub('l\xc3\xb6k'.decode('UTF-8'))

    with pytest.raises(AssertionError):
        stub.assert_called_with(u'lak')


def test_assert_called_with_unicode_correct_argument(mocker):
    stub = mocker.stub()
    stub('l\xc3\xb6k'.decode('UTF-8'))

    stub.assert_called_with('l\xc3\xb6k'.decode('UTF-8'))

Result:

test_pytest_mock.py::test_assert_called_with_unicode_wrong_argument FAILED
test_pytest_mock.py::test_assert_called_with_unicode_correct_argument PASSED

==== FAILURES ====
______test_assert_called_with_unicode_wrong_argument _______

mocker = <pytest_mock.MockFixture object at 0x104868f90>

    def test_assert_called_with_unicode_wrong_argument(mocker):
        stub = mocker.stub()
        stub('l\xc3\xb6k'.decode('UTF-8'))

        with pytest.raises(AssertionError):
>           stub.assert_called_with(u'lak')
E           UnicodeEncodeError: 'ascii' codec can't encode character u'\xf6' in position 10: ordinal not in range(128)

test_pytest_mock.py:544: UnicodeEncodeError

Truncated traceback:

> pytest-mock/test_pytest_mock.py(544)test_assert_called_with_unicode_wrong_argument()
-> stub.assert_called_with(u'lak')
  pytest-mock/pytest_mock.py(211)wrap_assert_called_with()
-> *args, **kwargs)
  pytest-mock/pytest_mock.py(192)assert_wrapper()
-> msg += '\nArgs:\n' + str(e)

Test run crashes with ValueError unknown configuration value: 'mock_use_standalone_module'

When using fixture mocker in one of mine test functions py.test will crash/stop the execution of my test run with a ValueError. See error output:

pytestconfig = <_pytest.config.Config object at 0x2438ad0>

    @pytest.yield_fixture
    def mocker(pytestconfig):
        """
        return an object that has the same interface to the `mock` module, but
        takes care of automatically undoing all patches after each test method.
        """
>       result = MockFixture(pytestconfig)

/usr/local/lib/python2.7/dist-packages/pytest_mock.py:155:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/lib/python2.7/dist-packages/pytest_mock.py:37: in __init__
    self.mock_module = mock_module = _get_mock_module(config)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

config = <_pytest.config.Config object at 0x2438ad0>

    def _get_mock_module(config):
        """
        Import and return the actual "mock" module. By default this is "mock" for Python 2 and
        "unittest.mock" for Python 3, but the user can force to always use "mock" on Python 3 using
        the mock_use_standalone_module ini option.
        """
        if not hasattr(_get_mock_module, '_module'):
>           use_standalone_module = parse_ini_boolean(config.getini('mock_use_standalone_module'))
E           ValueError: unknown configuration value: 'mock_use_standalone_module'

/usr/local/lib/python2.7/dist-packages/pytest_mock.py:17: ValueError

python version

Python 2.7.3

py.test version

This is pytest version 3.0.5, imported from /usr/local/lib/python2.7/dist-packages/pytest.pyc

pytest-mock version:

pytest-mock (1.5.0)

Comparing sqla.types

Comparing sqla.types seems to be causing some problems. Output (-vv):

to_sql_mock.assert_called_once_with(table, mysql_connection.engine, if_exists='append', dtype=dtypes)
E AssertionError: Expected call: to_sql('table', 'engine', dtype={'var_name': VARCHAR(length=255)}, if_exists='append')
E Actual call: to_sql('table', 'engine', dtype={'var_name': VARCHAR(length=255)}, if_exists='append')
E
E pytest introspection follows:
E
E Kwargs:
E assert {'dtype': {'v...ts': 'append'} == {'dtype': {'va...ts': 'append'}
E Common items:
E {'if_exists': 'append'}
E Differing items:
E {'dtype': {'var_name': VARCHAR(length=255)}} != {'dtype': {'var_name': VARCHAR(length=255)}}
E Full diff:
E {'dtype': {'var_name': VARCHAR(length=255)}, 'if_exists': 'append'}

Replacing the VARCHAR type with any other basic type fixes the problem. The type VARCHAR is obtained as follows:

import sqlalchemy as sqla
sqla.types.VARCHAR(length=255)

Do not fallback when importing "unittest.mock"

try:
    import mock as mock_module
except ImportError:
    import unittest.mock as mock_module

Use this instead:

if sys.version_info.major == 2:
    import mock as mock_module
else:
    import unittest.mock as mock_module

This way we don't "swallow" the first exception in Python 2 in case something goes wrong when importing mock.

EDIT: Just for the record, the first import mock as mock_module was raising an ImportError but not because mock was missing, one of its dependencies was not installed (it was a frozen application). This made this problem much harder to debug.

cc @gqmelo

Fix tests for pytest 3

Hey!

Many tests are failing when running with pytest 3.0.1:

_____________________________ TestMockerStub.test_failure_message_with_no_name _____________________________

self = <test_pytest_mock.TestMockerStub instance at 0x7f0b7f55c758>
mocker = <pytest_mock.MockFixture object at 0x7f0b7f51add0>

    def test_failure_message_with_no_name(self, mocker):
>       self.__test_failure_message(mocker)

test_pytest_mock.py:182:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <test_pytest_mock.TestMockerStub instance at 0x7f0b7f55c758>
mocker = <pytest_mock.MockFixture object at 0x7f0b7f51add0>, kwargs = {}, expected_name = 'mock'
expected_message = 'Expected call: mock()\nNot called'
stub = <MagicMock spec='function' id='139687357426384'>, exc_info = <ExceptionInfo AssertionError tblen=3>
@py_assert1 = AssertionError('Expected call: mock()\nNot called',)

    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 exc_info.value.msg == expected_message
E       AttributeError: 'exceptions.AssertionError' object has no attribute 'msg'

test_pytest_mock.py:179: AttributeError

pytest-mock 0.10.0 fails with an attribute error

The new version fails with such problem:

Traceback (most recent call last):
  File "/home/ash/git/fuel-octane/.tox/py27/local/lib/python2.7/site-packages/_pytest/main.py", line 80, in wrap_session
    config.do_configure()
  File "/home/ash/git/fuel-octane/.tox/py27/local/lib/python2.7/site-packages/_pytest/config.py", line 621, in do_configure
    self.hook.pytest_configure(config=self)
  File "/home/ash/git/fuel-octane/.tox/py27/local/lib/python2.7/site-packages/_pytest/core.py", line 521, in __call__
    return self._docall(self.methods, kwargs)
  File "/home/ash/git/fuel-octane/.tox/py27/local/lib/python2.7/site-packages/_pytest/core.py", line 528, in _docall
    firstresult=self.firstresult).execute()
  File "/home/ash/git/fuel-octane/.tox/py27/local/lib/python2.7/site-packages/_pytest/core.py", line 394, in execute
    res = method(*args)
  File "/home/ash/git/fuel-octane/.tox/py27/local/lib/python2.7/site-packages/pytest_mock.py", line 235, in pytest_configure
    wrap_assert_methods(config)
  File "/home/ash/git/fuel-octane/.tox/py27/local/lib/python2.7/site-packages/pytest_mock.py", line 210, in wrap_assert_methods
    config.add_cleanup(unwrap_assert_methods)
AttributeError: 'Config' object has no attribute 'add_cleanup'

pip freeze:

Babel==2.2.0
cliff==1.9.0
cmd2==0.6.8
coverage==4.0.3
ecdsa==0.13
flake8==2.2.4
funcsigs==0.4
hacking==0.10.2
iso8601==0.1.11
mccabe==0.2.1
mock==1.3.0
msgpack-python==0.4.7
netaddr==0.7.18
netifaces==0.10.4
oslo.config==1.9.3
oslo.i18n==1.5.0
oslo.serialization==1.4.0
oslo.utils==1.4.0
paramiko==1.13.0
pbr==0.11.1
pep8==1.5.7
prettytable==0.7.2
py==1.4.31
pycrypto==2.6.1
pyflakes==0.8.1
pyparsing==2.0.7
pytest==2.7.3
pytest-cov==2.2.0
pytest-mock==0.10.0
python-fuelclient==7.0.0
python-keystoneclient==1.3.4
pytz==2015.7
PyYAML==3.10
pyzabbix==0.7.3
requests==2.2.1
six==1.9.0
stevedore==1.3.0
wheel==0.24.0

Spying on properties

I've tried to use spy on object properties, but it fails with AttributeError: can't set attribute in unittest/mock.py.

I've started looking into adding support for (through PropertyMock), but failed to make it work without accessing/calling the property method during setup of the spy - it would be better to only call it when it's accessed from the test.

I would appreciate any feedback / help in this regard.

Add mocker.resetall()

Having a mocker.resetall() method, similar to mocker.stopall(), would be very handy. Most of the time if I mock things, it's because I'm testing a side-effecting function which cannot be blackboxed effectively, and will want to walk through multiple code paths while checking that various callees were/were not called as appropriate. This commonly involves resetting the mocks, so not having to reset them individually would greatly increase the usefulness of mocker.

Python3 + pytest + pytest-mock: Mocks leaking into other test functions breaking assertions?

Hi guys! First of all, thank you for everything that you do! It is really appreciated!

My Question

Apologies in advance if this issue is blatant, but i've been wrestling with it for several days now. Hopefully someone can shed some new light.

I'm in the process of converting unit tests for my personal project from unittest -> pytest. Previously I was using the built-in unittest.mock module, but now i'm trying to use the pytest-mock plugin instead.

I have a sneaking feeling that my tests are leaking mock objects into one another.

Here's why:
NOTE: All Details about my setup (python version, modules etc) listed at bottom of question.

High-level details:

# Python version
Python 3.5.2

# Pytest version ( and plugins )
pytest==3.0.7
pytest-benchmark==3.1.0a2
pytest-catchlog==1.2.2
pytest-cov==2.4.0
pytest-ipdb==0.1.dev2
pytest-leaks==0.2.2
pytest-mock==1.6.0
pytest-rerunfailures==2.1.0
pytest-sugar==0.8.0
pytest-timeout==1.2.0
python-dateutil==2.6.0
python-dbusmock==0.16.7

When I run my tests using the following command:

py.test --pdb --showlocals -v -R : -k test_subprocess.py

Everything is fine till we get to test_subprocess_check_command_type. At which point I get the following error:

            # Set mock return types
            # mock_map_type_to_command.return_value = int
    
            # action
            with pytest.raises(TypeError) as excinfo:
                scarlett_os.subprocess.Subprocess(test_command,
                                                  name=test_name,
                                                  fork=test_fork,
    >                                             run_check_command=True)
    E           Failed: DID NOT RAISE <class 'TypeError'>
    
    excinfo    = <[AttributeError("'ExceptionInfo' object has no attribute 'typename'") raised in repr()] ExceptionInfo object at 0x7f8c380f9dc0>
    mock_fork  = <Mock name='mock_fork' id='140240122195184'>
    mock_logging_debug = <Mock name='mock_logging_debug' id='140240128747640'>
    mock_map_type_to_command = <Mock name='mock_map_type_to_command' id='140240122785112'>
    mocker     = <pytest_mock.MockFixture object at 0x7f8c329f07a8>
    monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f8c329f0810>
    self       = <tests.test_subprocess.TestScarlettSubprocess object at 0x7f8c32aaac20>
    test_command = ['who', '-b']
    test_fork  = False
    test_name  = 'test_who'
    
    tests/test_subprocess.py:267: Failed
    
     tests/test_subprocess.py::TestScarlettSubprocess.test_subprocess_check_command_type ⨯                                                           100% ██████████

BUT!

If I filter out all of the other tests except for the problematic one then I get:

via py.test --pdb --showlocals -v -R : -k test_subprocess_check_command_type

    pi@0728af726f1f:~/dev/bossjones-github/scarlett_os$ py.test --pdb --showlocals -v -R : -k test_subprocess_check_command_type
    /usr/local/lib/python3.5/site-packages/_pdbpp_path_hack/pdb.py:4: ResourceWarning: unclosed file <_io.TextIOWrapper name='/usr/local/lib/python3.5/site-packages/pdb.py' mode='r' encoding='UTF-8'>
      os.path.dirname(os.path.dirname(__file__)), 'pdb.py')).read(), os.path.join(
    Test session starts (platform: linux, Python 3.5.2, pytest 3.0.7, pytest-sugar 0.8.0)
    cachedir: .cache
    benchmark: 3.1.0a2 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
    rootdir: /home/pi/dev/bossjones-github/scarlett_os, inifile: setup.cfg
    plugins: timeout-1.2.0, sugar-0.8.0, rerunfailures-2.1.0, mock-1.6.0, leaks-0.2.2, ipdb-0.1.dev2, cov-2.4.0, catchlog-1.2.2, benchmark-3.1.0a2
    timeout: 60.0s method: signal
    NOTE: DBUS_SESSION_BUS_ADDRESS environment var not found!
    [DBUS_SESSION_BUS_ADDRESS]: unix:path=/tmp/dbus_proxy_outside_socket
    
     tests/test_subprocess.py::TestScarlettSubprocess.test_subprocess_check_command_type ✓                                                                                                                                                                           100% ██████████
    
    Results (8.39s):
           1 passed
         190 deselected
    pi@0728af726f1f:~/dev/bossjones-github/scarlett_os$

I also tried manually commenting out the following 2 tests and they allowed me to successfully run all the tests again:

  • test_subprocess_init
  • test_subprocess_map_type_to_command

Can anyone see anything blatently wrong with my setup? I've read several blog posts on "where to mock", and looked at the docs themselves several times, not sure what i'm missing. https://docs.python.org/3/library/unittest.mock.html

My Setup Details

Here is everything that might be required to solve this. Let me know if I need to provide any more information!

Also ... please excuse how messy my code looks and all of the comment blocks. I'm a big note taker when i'm learning something new ... I'll make everything more pythonic and cleaner in the near future :)

My code:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    """Scarlett Dbus Service. Implemented via MPRIS D-Bus Interface Specification."""

    from __future__ import with_statement, division, absolute_import

    import os
    import sys
    from scarlett_os.exceptions import SubProcessError
    from scarlett_os.exceptions import TimeOutError
    import logging
    from scarlett_os.internal.gi import GObject
    from scarlett_os.internal.gi import GLib

    logger = logging.getLogger(__name__)


    def check_pid(pid):
        """Check For the existence of a unix pid."""
        try:
            os.kill(pid, 0)
        except OSError:
            return False
        else:
            return True


    class Subprocess(GObject.GObject):
        """
        GObject API for handling child processes.

        :param command: The command to be run as a subprocess.
        :param fork: If `True` this process will be detached from its parent and
                     run independent. This means that no excited-signal will be emited.

        :type command: `list`
        :type fork: `bool`
        """

        __gtype_name__ = 'Subprocess'
        __gsignals__ = {
            'exited': (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_INT, GObject.TYPE_INT))
        }

        def __init__(self, command, name=None, fork=False, run_check_command=True):
            """Create instance of Subprocess."""

            GObject.GObject.__init__(self)

            self.process = None
            self.pid = None

            if not fork:
                self.stdout = True
                self.stderr = True
            else:
                self.stdout = False
                self.stderr = False

            self.forked = fork

            # Verify that command is properly formatted 
            # and each argument is of type str
            if run_check_command:
                self.check_command_type(command)

            self.command = command
            self.name = name

            logger.debug("command: {}".format(self.command))
            logger.debug("name: {}".format(self.name))
            logger.debug("forked: {}".format(self.forked))
            logger.debug("process: {}".format(self.process))
            logger.debug("pid: {}".format(self.pid))

            if fork:
                self.fork()

        # TODO: Add these arguments so we can toggle stdout
        # def spawn_command(self, standard_input=False, standard_output=False, standard_error=False):
        def spawn_command(self):
            # DO_NOT_REAP_CHILD
            # Don't reap process automatically so it is possible to detect when it is closed.
            return GLib.spawn_async(self.command,
                                    flags=GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD
                                    )

        def map_type_to_command(self, command):
            """Return: Map after applying type to several objects in an array"""
            # NOTE: In python3, many processes that iterate over iterables return iterators themselves. 
            # In most cases, this ends up saving memory, and should make things go faster.
            # cause of that, we need to call list() over the map object
            return list(map(type, command))

        def check_command_type(self, command):

            types = self.map_type_to_command(command)

            if type(types) is not list:
                raise TypeError("Variable types should return a list in python3. Got: {}".format(types))

            # NOTE: str is a built-in function (actually a class) which converts its argument to a string. 
            # string is a module which provides common string operations.
            # source: http://stackoverflow.com/questions/2026038/relationship-between-string-module-and-str
            for t in types:
                if t is not str:
                    raise TypeError("Executables and arguments must be str objects. types: {}".format(t))

            logger.debug("Running Command: %r" % " ".join(command))
            return True

        def run(self):
            """Run the process."""

            # NOTE: DO_NOT_REAP_CHILD: the child will not be automatically reaped;
            # you must use g_child_watch_add yourself (or call waitpid or handle `SIGCHLD` yourself),
            # or the child will become a zombie.
            # source:
            # http://valadoc.org/#!api=glib-2.0/GLib.SpawnFlags.DO_NOT_REAP_CHILD

            # NOTE: SEARCH_PATH: argv[0] need not be an absolute path, it will be looked for in the user's PATH
            # source:
            # http://lazka.github.io/pgi-docs/#GLib-2.0/flags.html#GLib.SpawnFlags.SEARCH_PATH

            self.pid, self.stdin, self.stdout, self.stderr = self.spawn_command()

            logger.debug("command: {}".format(self.command))
            logger.debug("stdin: {}".format(self.stdin))
            logger.debug("stdout: {}".format(self.stdout))
            logger.debug("stderr: {}".format(self.stderr))
            logger.debug("pid: {}".format(self.pid))

            # close file descriptor
            self.pid.close()

            print(self.stderr)

            # NOTE: GLib.PRIORITY_HIGH = -100
            # Use this for high priority event sources.
            # It is not used within GLib or GTK+.
            watch = GLib.child_watch_add(GLib.PRIORITY_HIGH, 
                                         self.pid, 
                                         self.exited_cb)

            return self.pid

        def exited_cb(self, pid, condition):
            if not self.forked:
                self.emit('exited', pid, condition)

        def fork(self):
            """Fork the process."""
            try:
                # first fork
                pid = os.fork()
                if pid > 0:
                    logger.debug('pid greater than 0 first time')
                    sys.exit(0)
            except OSError as e:
                logger.error('Error forking process first time')
                sys.exit(1)

            # Change the current working directory to path.
            os.chdir("/")

            # Description: setsid() creates a new session if the calling process is not a process group leader. 
            # The calling process is the leader of the new session, 
            # the process group leader of the new process group, 
            # and has no controlling terminal. 
            # The process group ID and session ID of the calling process are set to the PID of the calling process. 
            # The calling process will be the only process in this new process group and in this new session.

            # Return Value: On success, the (new) session ID of the calling process is returned. 
            # On error, (pid_t) -1 is returned, and errno is set to indicate the error.
            os.setsid()

            # Set the current numeric umask and return the previous umask.
            os.umask(0)

            try:
                # second fork
                pid = os.fork()
                if pid > 0:
                    logger.debug('pid greater than 0 second time')
                    sys.exit(0)
            except OSError as e:
                logger.error('Error forking process second time')
                sys.exit(1)

My Test:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    """
    test_subprocess
    ----------------------------------
    """

    import os
    import sys
    import pytest

    import scarlett_os
    # import signal
    # import builtins
    # import re

    class TestScarlettSubprocess(object):
        '''Units tests for Scarlett Subprocess, subclass of GObject.Gobject.'''

        def test_check_pid_os_error(self, mocker):
            # Feels like mocks are leaking into other tests, 
            # stop mock before starting each test function
            mocker.stopall()

            # Setup mock objects
            kill_mock = mocker.MagicMock(name=__name__ + "_kill_mock_OSError")
            kill_mock.side_effect = OSError

            # patch things
            mocker.patch.object(scarlett_os.subprocess.os, 'kill', kill_mock)

            # When OSError occurs, throw False
            assert not scarlett_os.subprocess.check_pid(4353634632623)
            # Verify that os.kill only called once
            assert kill_mock.call_count == 1

        def test_check_pid(self, mocker):
            # Feels like mocks are leaking into other tests, 
            # stop mock before starting each test function
            mocker.stopall()

            # Setup mock objects
            kill_mock = mocker.MagicMock(name=__name__ + "_kill_mock")

            mocker.patch.object(scarlett_os.subprocess.os, 'kill', kill_mock)

            result = scarlett_os.subprocess.check_pid(123)
            assert kill_mock.called
            # NOTE: test against signal 0
            # sending the signal 0 to a given PID just checks if any
            # process with the given PID is running and you have the
            # permission to send a signal to it.
            kill_mock.assert_called_once_with(123, 0)
            assert result is True

        # FIXME: I THINK THIS GUYS IS LEAKING MOCK OBJECTS
        def test_subprocess_init(self, mocker):
            # Feels like mocks are leaking into other tests, 
            # stop mock before starting each test function
            mocker.stopall()

            mock_check_command_type = MagicMock(name="mock_check_command_type")
            mock_check_command_type.return_value = True
            mock_fork = mocker.MagicMock(name="mock_fork")
            mock_logging_debug = mocker.MagicMock(name="mock_logging_debug")

            # mock
            mocker.patch.object(scarlett_os.subprocess.logging.Logger, 'debug', mock_logging_debug)
            mocker.patch.object(scarlett_os.subprocess.Subprocess, 'check_command_type', mock_check_command_type)
            mocker.patch.object(scarlett_os.subprocess.Subprocess, 'fork', mock_fork)

            # NOTE: On purpose this is an invalid cmd. Should be of type array
            test_command = ['who']

            test_name = 'test_who'
            test_fork = False

            s_test = scarlett_os.subprocess.Subprocess(test_command,
                                                       name=test_name,
                                                       fork=test_fork)

            # action
            assert s_test.check_command_type(test_command) is True
            mock_check_command_type.assert_called_with(['who'])
            assert not s_test.process
            assert not s_test.pid
            assert s_test.name == 'test_who'
            assert not s_test.forked
            assert s_test.stdout is True
            assert s_test.stderr is True

            mock_logging_debug.assert_any_call("command: ['who']")
            mock_logging_debug.assert_any_call("name: test_who")
            mock_logging_debug.assert_any_call("forked: False")
            mock_logging_debug.assert_any_call("process: None")
            mock_logging_debug.assert_any_call("pid: None")
            mock_fork.assert_not_called()

        # FIXME: I THINK THIS GUYS IS LEAKING MOCK OBJECTS
        def test_subprocess_map_type_to_command(self, mocker):
            """Using the mock.patch decorator (removes the need to import builtins)"""
            # Feels like mocks are leaking into other tests, 
            # stop mock before starting each test function
            mocker.stopall()

            mock_check_command_type = mocker.MagicMock(name="mock_check_command_type")
            mock_check_command_type.return_value = True
            mock_fork = mocker.MagicMock(name="mock_fork")
            mock_logging_debug = mocker.MagicMock(name="mock_logging_debug")

            # mock
            mocker.patch.object(scarlett_os.subprocess.logging.Logger, 'debug', mock_logging_debug)
            mocker.patch.object(scarlett_os.subprocess.Subprocess, 'check_command_type', mock_check_command_type)
            mocker.patch.object(scarlett_os.subprocess.Subprocess, 'fork', mock_fork)

            # NOTE: On purpose this is an invalid cmd. Should be of type array
            test_command = ["who", "-b"]
            test_name = 'test_who'
            test_fork = False

            # create subprocess object
            s_test = scarlett_os.subprocess.Subprocess(test_command,
                                                       name=test_name,
                                                       fork=test_fork)
            mocker.spy(s_test, 'map_type_to_command')
            assert isinstance(s_test.map_type_to_command(test_command), list)
            assert s_test.map_type_to_command.call_count == 1

            assert s_test.check_command_type(test_command)
            assert s_test.check_command_type(
                test_command) == mock_check_command_type.return_value

        def test_subprocess_check_command_type(self, mocker):
            """Using the mock.patch decorator (removes the need to import builtins)"""
            # Feels like mocks are leaking into other tests, 
            # stop mock before starting each test function
            mocker.stopall()

            test_command = ["who", "-b"]
            test_name = 'test_who'
            test_fork = False

            # mock
            mock_map_type_to_command = mocker.MagicMock(name="mock_map_type_to_command")
            # mock_map_type_to_command.return_value = int
            mock_map_type_to_command.side_effect = [int, [int, int]]
            mock_fork = mocker.MagicMock(name="mock_fork")
            mock_logging_debug = mocker.MagicMock(name="mock_logging_debug")

            mocker.patch.object(scarlett_os.subprocess.logging.Logger, 'debug', mock_logging_debug)
            mocker.patch.object(scarlett_os.subprocess.Subprocess, 'map_type_to_command', mock_map_type_to_command)
            mocker.patch.object(scarlett_os.subprocess.Subprocess, 'fork', mock_fork)


            # action
            with pytest.raises(TypeError) as excinfo:
                scarlett_os.subprocess.Subprocess(test_command,
                                                  name=test_name,
                                                  fork=test_fork,
                                                  run_check_command=True)
            assert str(
                excinfo.value) == "Variable types should return a list in python3. Got: <class 'int'>"

            with pytest.raises(TypeError) as excinfo:
                scarlett_os.subprocess.Subprocess(test_command,
                                                  name=test_name,
                                                  fork=test_fork,
                                                  run_check_command=True)

            assert str(
                excinfo.value) == "Executables and arguments must be str objects. types: <class 'int'>"

My folder structure( Note I removed a couple things since it was overly verbose ):

    pi@0728af726f1f:~/dev/bossjones-github/scarlett_os$ tree -I *.pyc
    .
    ├── requirements_dev.txt
    ├── requirements_test_experimental.txt
    ├── requirements_test.txt
    ├── requirements.txt
    ├── scarlett_os
    │   ├── automations
    │   │   ├── __init__.py
    │   │   └── __pycache__
    │   ├── commands.py
    │   ├── compat.py
    │   ├── config.py
    │   ├── const.py
    │   ├── core.py
    │   ├── emitter.py
    │   ├── exceptions.py
    │   ├── __init__.py
    │   ├── internal
    │   │   ├── debugger.py
    │   │   ├── deps.py
    │   │   ├── encoding.py
    │   │   ├── formatting.py
    │   │   ├── gi.py
    │   │   ├── __init__.py
    │   │   ├── path.py
    │   │   ├── __pycache__
    │   │   └── system_utils.py
    │   ├── listener.py
    │   ├── loader.py
    │   ├── logger.py
    │   ├── log.py
    │   ├── __main__.py
    │   ├── mpris.py
    │   ├── player.py
    │   ├── __pycache__
    │   ├── receiver.py
    │   ├── speaker.py
    │   ├── subprocess.py
    │   ├── tasker.py
    │   ├── tools
    │   │   ├── __init__.py
    │   │   ├── package.py
    │   │   ├── __pycache__
    │   │   └── verify.py
    │   └── utility
    │       ├── audio.py
    │       ├── dbus_runner.py
    │       ├── dbus_utils.py
    │       ├── distance.py
    │       ├── dt.py
    │       ├── file.py
    │       ├── generators.py
    │       ├── gnome.py
    │       ├── __init__.py
    │       ├── location.py
    │       ├── __pycache__
    │       ├── temperature.py
    │       ├── threadmanager.py
    │       ├── thread.py
    │       ├── unit_system.py
    │       └── yaml.py
    ├── setup.cfg
    ├── setup.py
    ├── tests
    │   ├── common_integration.py
    │   ├── common.py
    │   ├── helpers
    │   │   ├── __init__.py
    │   │   ├── __pycache__
    │   │   ├── test_config_validation.py
    │   │   ├── test_entity.py
    │   │   └── test_init.py
    │   ├── __init__.py
    │   ├── integration
    │   │   ├── baseclass.py
    │   │   ├── conftest.py
    │   │   ├── __init__.py
    │   │   ├── __pycache__
    │   │   ├── README.md
    │   │   ├── stubs.py
    │   │   ├── test_integration_end_to_end.py
    │   │   ├── test_integration_listener.py
    │   │   ├── test_integration_mpris.py
    │   │   ├── test_integration_player.py
    │   │   ├── test_integration_tasker.py
    │   │   ├── test_integration_tasker.py.enable_sound.diff
    │   │   └── test_integration_threadmanager.py
    │   ├── internal
    │   │   ├── __init__.py
    │   │   ├── __pycache__
    │   │   ├── test_deps.py
    │   │   ├── test_encoding.py
    │   │   └── test_path.py
    │   ├── performancetests
    │   │   ├── baseclass.py
    │   │   ├── __init__.py
    │   │   └── __pycache__
    │   ├── __pycache__
    │   ├── run_all_tests
    │   ├── run_dbus_tests.sh
    │   ├── test_cli.py
    │   ├── test_commands.py
    │   ├── testing_config
    │   │   └── custom_automations
    │   │       ├── light
    │   │       │   └── test.py
    │   │       └── switch
    │   │           └── test.py
    │   ├── test_listener.py
    │   ├── test_mpris.py
    │   ├── test_player.py
    │   ├── test_scarlett_os.py
    │   ├── test_speaker.py
    │   ├── test_subprocess.py
    │   ├── test_tasker.py
    │   ├── test_threadmanager.py
    │   ├── tools_common.py
    │   ├── unit_scarlett_os.py
    │   └── utility
    │       ├── __init__.py
    │       ├── __pycache__
    │       ├── test_dbus_utils.py
    │       ├── test_distance.py
    │       ├── test_dt.py
    │       ├── test_gnome.py
    │       ├── test_init.py
    │       ├── test_location.py
    │       ├── test_unit_system.py
    │       └── test_yaml.py
    67 directories, 256 files
    pi@0728af726f1f:~/dev/bossjones-github/scarlett_os$

Other details( Extended pip freeze just in case of incompatibilities ):

    # Python version
    Python 3.5.2

    # Pytest version ( and plugins )
    pytest==3.0.7
    pytest-benchmark==3.1.0a2
    pytest-catchlog==1.2.2
    pytest-cov==2.4.0
    pytest-ipdb==0.1.dev2
    pytest-leaks==0.2.2
    pytest-mock==1.6.0
    pytest-rerunfailures==2.1.0
    pytest-sugar==0.8.0
    pytest-timeout==1.2.0
    python-dateutil==2.6.0
    python-dbusmock==0.16.7


    # Pip Freeze ( Just in case )
    alabaster==0.7.10
    appdirs==1.4.3
    argh==0.26.2
    asn1crypto==0.22.0
    astroid==1.5.2
    Babel==2.4.0
    bleach==2.0.0
    bumpversion==0.5.3
    cffi==1.10.0
    click==6.7
    click-plugins==1.0.3
    colorama==0.3.7
    colorlog==2.10.0
    coverage==4.3.4
    coveralls==1.1
    cryptography==1.8.1
    Cython==0.25.2
    decorator==4.0.11
    docopt==0.6.2
    docutils==0.13.1
    ecdsa==0.13
    entrypoints==0.2.2
    Fabric3==1.12.post1
    fancycompleter==0.7
    fields==5.0.0
    flake8==3.3.0
    flake8-docstrings==1.0.3
    flake8-polyfill==1.0.1
    freezegun==0.3.8
    gnureadline==6.3.3
    graphviz==0.6
    html5lib==0.999999999
    hunter==1.4.1
    idna==2.5
    imagesize==0.7.1
    ipdb==0.10.2
    ipykernel==4.6.1
    ipython==6.0.0
    ipython-genutils==0.2.0
    ipywidgets==6.0.0
    isort==4.2.5
    jedi==0.10.2
    Jinja2==2.9.6
    jsonschema==2.6.0
    jupyter==1.0.0
    jupyter-client==5.0.1
    jupyter-console==5.1.0
    jupyter-core==4.3.0
    lazy-object-proxy==1.2.2
    MarkupSafe==1.0
    mccabe==0.6.1
    mistune==0.7.4
    mock==2.0.0
    mock-open==1.3.1
    mypy-lang==0.4.6
    nbconvert==5.1.1
    nbformat==4.3.0
    notebook==5.0.0
    objgraph==3.1.0
    ordereddict==1.1
    packaging==16.8
    pandocfilters==1.4.1
    paramiko==1.18.2
    pathtools==0.1.2
    pbr==1.10.0
    pdbpp==0.8.3
    pexpect==4.2.1
    pickleshare==0.7.4
    pluggy==0.4.0
    plumbum==1.6.3
    prompt-toolkit==1.0.14
    psutil==5.2.2
    ptyprocess==0.5.1
    py==1.4.33
    py-cpuinfo==3.2.0
    pyasn1==0.2.3
    pycodestyle==2.3.1
    pycparser==2.17
    pycrypto==2.6.1
    pydbus==0.6.0
    pydocstyle==2.0.0
    pyflakes==1.5.0
    pygal==2.3.1
    pygaljs==1.0.1
    Pygments==2.2.0
    pygobject==3.22.0
    pylint==1.7.1
    pyparsing==2.2.0
    pystuck==0.8.5
    pytest==3.0.7
    pytest-benchmark==3.1.0a2
    pytest-catchlog==1.2.2
    pytest-cov==2.4.0
    pytest-ipdb==0.1.dev2
    pytest-leaks==0.2.2
    pytest-mock==1.6.0
    pytest-rerunfailures==2.1.0
    pytest-sugar==0.8.0
    pytest-timeout==1.2.0
    python-dateutil==2.6.0
    python-dbusmock==0.16.7
    pytz==2017.2
    PyYAML==3.12
    pyzmq==16.0.2
    qtconsole==4.3.0
    requests==2.13.0
    requests-mock==1.3.0
    rpyc==3.3.0
    -e [email protected]:bossjones/scarlett_os.git@c14ffcde608da12f5c2d4d9b81a63c7e618b3eed#egg=scarlett_os
    simplegeneric==0.8.1
    six==1.10.0
    snowballstemmer==1.2.1
    Sphinx==1.5.5
    stevedore==1.18.0
    termcolor==1.1.0
    terminado==0.6
    testpath==0.3
    tornado==4.5.1
    tox==2.7.0
    traitlets==4.3.2
    typing==3.6.1
    virtualenv==15.0.3
    virtualenv-clone==0.2.6
    virtualenvwrapper==4.7.2
    voluptuous==0.9.3
    watchdog==0.8.3
    wcwidth==0.1.7
    webencodings==0.5.1
    widgetsnbextension==2.0.0
    wmctrl==0.3
    wrapt==1.10.10
    xdot==0.7

Missing version specifier for `mock`

Hello,

https://github.com/pytest-dev/pytest-mock/blob/master/setup.py#L8 adds mock as a dependency without a version specifier.

After yesterday's mock update, we're now getting an installation error:

 Downloading pytest-mock-0.7.0.zip
Collecting pytest-cov
  Downloading pytest-cov-1.8.1.tar.gz
Requirement already satisfied (use --upgrade to upgrade): py>=1.4.25 in /usr/local/lib/python2.7/dist-packages (from pytest)
Collecting mock (from pytest-mock)
  Downloading mock-1.1.1.tar.gz (69kB)
Collecting coverage<4.0a1,>=3.7.1 (from pytest-cov)
  Downloading coverage-3.7.1.tar.gz (284kB)
Collecting cov-core>=1.14.0 (from pytest-cov)
  Downloading cov-core-1.15.0.tar.gz
Collecting pbr>=0.11 (from mock->pytest-mock)
  Downloading pbr-1.2.0-py2.py3-none-any.whl (83kB)
Collecting six>=1.7 (from mock->pytest-mock)
  Downloading six-1.9.0-py2.py3-none-any.whl
Installing collected packages: pbr, six, mock, pytest-mock, coverage, cov-core, pytest-cov
  Running setup.py install for mock
    Complete output from command /usr/bin/python -c "import setuptools, tokenize;__file__='/tmp/pip-build-VRYOyE/mock/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /tmp/pip-bke1Cs-record/install-record.txt --single-version-externally-managed --compile:
    error in setup command: Invalid environment marker: python_version<"3" or python_version>="3.3"

    ----------------------------------------
    Command "/usr/bin/python -c "import setuptools, tokenize;__file__='/tmp/pip-build-VRYOyE/mock/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /tmp/pip-bke1Cs-record/install-record.txt --single-version-externally-managed --compile" failed with error code 1 in /tmp/pip-build-VRYOyE/mock
Removing intermediate container 94340479c1ba
The command '/bin/sh -c pip install pytest pytest-mock pytest-cov' returned a non-zero code: 1

Would it be better to specify a dependency version that we know works?

How to spy call of async/await function?

I'm trying to do this:

async def async_func2():
    pass


async def async_func1():
    await async_func2()


@pytest.mark.asyncio
async def test_async_func2(mocker):
    mock_async_func2 = mocker.patch(__name__ + '.async_func2')
    mock_async_func2.return_value = 'something'
    res = await async_func1()
    mock_async_func2.assert_called_once_with()

But got:

_________________________________________ test_async_func2 __________________________________________

mocker = <pytest_mock.MockFixture object at 0x7febf7b4c1d0>

    @pytest.mark.asyncio
    async def test_async_func2(mocker):
        mock_async_func2 = mocker.patch(__name__ + '.async_func2')
        mock_async_func2.return_value = 'something'
>       res = await async_func1()

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    async def async_func1():
>       await async_func2()
E       TypeError: object str can't be used in 'await' expression

My goal is to mock async_func2 function and catch all calls of it. How can I archive it with this library?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.