1import os
2import platform
3import sys
4from contextlib import contextmanager
5
6import py.code
7import pytest
8
9pytest_plugins = "pytester"
10
11# could not make some of the tests work on PyPy, patches are welcome!
12skip_pypy = pytest.mark.skipif(
13    platform.python_implementation() == "PyPy", reason="could not make it work on pypy"
14)
15
16# Python 3.8 changed the output formatting (bpo-35500), which has been ported to mock 3.0
17NEW_FORMATTING = sys.version_info >= (3, 8) or sys.version_info[0] == 2
18
19
20@pytest.fixture
21def needs_assert_rewrite(pytestconfig):
22    """
23    Fixture which skips requesting test if assertion rewrite is disabled (#102)
24
25    Making this a fixture to avoid acessing pytest's config in the global context.
26    """
27    option = pytestconfig.getoption("assertmode")
28    if option != "rewrite":
29        pytest.skip(
30            "this test needs assertion rewrite to work but current option "
31            'is "{}"'.format(option)
32        )
33
34
35class UnixFS(object):
36    """
37    Wrapper to os functions to simulate a Unix file system, used for testing
38    the mock fixture.
39    """
40
41    @classmethod
42    def rm(cls, filename):
43        os.remove(filename)
44
45    @classmethod
46    def ls(cls, path):
47        return os.listdir(path)
48
49
50@pytest.fixture
51def check_unix_fs_mocked(tmpdir, mocker):
52    """
53    performs a standard test in a UnixFS, assuming that both `os.remove` and
54    `os.listdir` have been mocked previously.
55    """
56
57    def check(mocked_rm, mocked_ls):
58        assert mocked_rm is os.remove
59        assert mocked_ls is os.listdir
60
61        file_name = tmpdir / "foo.txt"
62        file_name.ensure()
63
64        UnixFS.rm(str(file_name))
65        mocked_rm.assert_called_once_with(str(file_name))
66        assert os.path.isfile(str(file_name))
67
68        mocked_ls.return_value = ["bar.txt"]
69        assert UnixFS.ls(str(tmpdir)) == ["bar.txt"]
70        mocked_ls.assert_called_once_with(str(tmpdir))
71
72        mocker.stopall()
73
74        assert UnixFS.ls(str(tmpdir)) == ["foo.txt"]
75        UnixFS.rm(str(file_name))
76        assert not os.path.isfile(str(file_name))
77
78    return check
79
80
81def mock_using_patch_object(mocker):
82    return mocker.patch.object(os, "remove"), mocker.patch.object(os, "listdir")
83
84
85def mock_using_patch(mocker):
86    return mocker.patch("os.remove"), mocker.patch("os.listdir")
87
88
89def mock_using_patch_multiple(mocker):
90    r = mocker.patch.multiple("os", remove=mocker.DEFAULT, listdir=mocker.DEFAULT)
91    return r["remove"], r["listdir"]
92
93
94@pytest.mark.parametrize(
95    "mock_fs", [mock_using_patch_object, mock_using_patch, mock_using_patch_multiple]
96)
97def test_mock_patches(mock_fs, mocker, check_unix_fs_mocked):
98    """
99    Installs mocks into `os` functions and performs a standard testing of
100    mock functionality. We parametrize different mock methods to ensure
101    all (intended, at least) mock API is covered.
102    """
103    # mock it twice on purpose to ensure we unmock it correctly later
104    mock_fs(mocker)
105    mocked_rm, mocked_ls = mock_fs(mocker)
106    check_unix_fs_mocked(mocked_rm, mocked_ls)
107    mocker.resetall()
108    mocker.stopall()
109
110
111def test_mock_patch_dict(mocker):
112    """
113    Testing
114    :param mock:
115    """
116    x = {"original": 1}
117    mocker.patch.dict(x, values=[("new", 10)], clear=True)
118    assert x == {"new": 10}
119    mocker.stopall()
120    assert x == {"original": 1}
121
122
123def test_mock_patch_dict_resetall(mocker):
124    """
125    We can call resetall after patching a dict.
126    :param mock:
127    """
128    x = {"original": 1}
129    mocker.patch.dict(x, values=[("new", 10)], clear=True)
130    assert x == {"new": 10}
131    mocker.resetall()
132    assert x == {"new": 10}
133
134
135def test_deprecated_mock(testdir):
136    """
137    Use backward-compatibility-only mock fixture to ensure complete coverage.
138    """
139    p1 = testdir.makepyfile(
140        """
141        import os
142
143        def test(mock, tmpdir):
144            mock.patch("os.listdir", return_value=["mocked"])
145            assert os.listdir(str(tmpdir)) == ["mocked"]
146            mock.stopall()
147            assert os.listdir(str(tmpdir)) == []
148        """
149    )
150    result = testdir.runpytest(str(p1))
151    result.stdout.fnmatch_lines(
152        ['*DeprecationWarning: "mock" fixture has been deprecated, use "mocker"*']
153    )
154    assert result.ret == 0
155
156
157@pytest.mark.parametrize(
158    "name",
159    [
160        "ANY",
161        "call",
162        "create_autospec",
163        "MagicMock",
164        "Mock",
165        "mock_open",
166        "NonCallableMock",
167        "PropertyMock",
168        "sentinel",
169    ],
170)
171def test_mocker_aliases(name, pytestconfig):
172    from pytest_mock import _get_mock_module, MockFixture
173
174    mock_module = _get_mock_module(pytestconfig)
175
176    mocker = MockFixture(pytestconfig)
177    assert getattr(mocker, name) is getattr(mock_module, name)
178
179
180def test_mocker_resetall(mocker):
181    listdir = mocker.patch("os.listdir")
182    open = mocker.patch("os.open")
183
184    listdir("/tmp")
185    open("/tmp/foo.txt")
186    listdir.assert_called_once_with("/tmp")
187    open.assert_called_once_with("/tmp/foo.txt")
188
189    mocker.resetall()
190
191    assert not listdir.called
192    assert not open.called
193
194
195class TestMockerStub:
196    def test_call(self, mocker):
197        stub = mocker.stub()
198        stub("foo", "bar")
199        stub.assert_called_once_with("foo", "bar")
200
201    def test_repr_with_no_name(self, mocker):
202        stub = mocker.stub()
203        assert "name" not in repr(stub)
204
205    def test_repr_with_name(self, mocker):
206        test_name = "funny walk"
207        stub = mocker.stub(name=test_name)
208        assert "name={0!r}".format(test_name) in repr(stub)
209
210    def __test_failure_message(self, mocker, **kwargs):
211        expected_name = kwargs.get("name") or "mock"
212        if NEW_FORMATTING:
213            msg = "expected call not found.\nExpected: {0}()\nActual: not called."
214        else:
215            msg = "Expected call: {0}()\nNot called"
216        expected_message = msg.format(expected_name)
217        stub = mocker.stub(**kwargs)
218        with pytest.raises(AssertionError) as exc_info:
219            stub.assert_called_with()
220        assert str(exc_info.value) == expected_message
221
222    def test_failure_message_with_no_name(self, mocker):
223        self.__test_failure_message(mocker)
224
225    @pytest.mark.parametrize("name", (None, "", "f", "The Castle of aaarrrrggh"))
226    def test_failure_message_with_name(self, mocker, name):
227        self.__test_failure_message(mocker, name=name)
228
229
230def test_instance_method_spy(mocker):
231    class Foo(object):
232        def bar(self, arg):
233            return arg * 2
234
235    foo = Foo()
236    other = Foo()
237    spy = mocker.spy(foo, "bar")
238    assert foo.bar(arg=10) == 20
239    assert other.bar(arg=10) == 20
240    foo.bar.assert_called_once_with(arg=10)
241    spy.assert_called_once_with(arg=10)
242
243
244@skip_pypy
245def test_instance_method_by_class_spy(mocker):
246    class Foo(object):
247        def bar(self, arg):
248            return arg * 2
249
250    spy = mocker.spy(Foo, "bar")
251    foo = Foo()
252    other = Foo()
253    assert foo.bar(arg=10) == 20
254    assert other.bar(arg=10) == 20
255    calls = [mocker.call(foo, arg=10), mocker.call(other, arg=10)]
256    assert spy.call_args_list == calls
257
258
259@skip_pypy
260def test_instance_method_by_subclass_spy(mocker):
261    class Base(object):
262        def bar(self, arg):
263            return arg * 2
264
265    class Foo(Base):
266        pass
267
268    spy = mocker.spy(Foo, "bar")
269    foo = Foo()
270    other = Foo()
271    assert foo.bar(arg=10) == 20
272    assert other.bar(arg=10) == 20
273    calls = [mocker.call(foo, arg=10), mocker.call(other, arg=10)]
274    assert spy.call_args_list == calls
275
276
277@skip_pypy
278def test_class_method_spy(mocker):
279    class Foo(object):
280        @classmethod
281        def bar(cls, arg):
282            return arg * 2
283
284    spy = mocker.spy(Foo, "bar")
285    assert Foo.bar(arg=10) == 20
286    Foo.bar.assert_called_once_with(arg=10)
287    spy.assert_called_once_with(arg=10)
288
289
290@skip_pypy
291@pytest.mark.xfail(sys.version_info[0] == 2, reason="does not work on Python 2")
292def test_class_method_subclass_spy(mocker):
293    class Base(object):
294        @classmethod
295        def bar(self, arg):
296            return arg * 2
297
298    class Foo(Base):
299        pass
300
301    spy = mocker.spy(Foo, "bar")
302    assert Foo.bar(arg=10) == 20
303    Foo.bar.assert_called_once_with(arg=10)
304    spy.assert_called_once_with(arg=10)
305
306
307@skip_pypy
308def test_class_method_with_metaclass_spy(mocker):
309    class MetaFoo(type):
310        pass
311
312    class Foo(object):
313
314        __metaclass__ = MetaFoo
315
316        @classmethod
317        def bar(cls, arg):
318            return arg * 2
319
320    spy = mocker.spy(Foo, "bar")
321    assert Foo.bar(arg=10) == 20
322    Foo.bar.assert_called_once_with(arg=10)
323    spy.assert_called_once_with(arg=10)
324
325
326@skip_pypy
327def test_static_method_spy(mocker):
328    class Foo(object):
329        @staticmethod
330        def bar(arg):
331            return arg * 2
332
333    spy = mocker.spy(Foo, "bar")
334    assert Foo.bar(arg=10) == 20
335    Foo.bar.assert_called_once_with(arg=10)
336    spy.assert_called_once_with(arg=10)
337
338
339@skip_pypy
340@pytest.mark.xfail(sys.version_info[0] == 2, reason="does not work on Python 2")
341def test_static_method_subclass_spy(mocker):
342    class Base(object):
343        @staticmethod
344        def bar(arg):
345            return arg * 2
346
347    class Foo(Base):
348        pass
349
350    spy = mocker.spy(Foo, "bar")
351    assert Foo.bar(arg=10) == 20
352    Foo.bar.assert_called_once_with(arg=10)
353    spy.assert_called_once_with(arg=10)
354
355
356@contextmanager
357def assert_traceback():
358    """
359    Assert that this file is at the top of the filtered traceback
360    """
361    try:
362        yield
363    except AssertionError:
364        traceback = py.code.ExceptionInfo().traceback
365        crashentry = traceback.getcrashentry()
366        assert crashentry.path == __file__
367    else:
368        raise AssertionError("DID NOT RAISE")
369
370
371@contextmanager
372def assert_argument_introspection(left, right):
373    """
374    Assert detailed argument introspection is used
375    """
376    try:
377        yield
378    except AssertionError as e:
379        # this may be a bit too assuming, but seems nicer then hard-coding
380        import _pytest.assertion.util as util
381
382        # NOTE: we assert with either verbose or not, depending on how our own
383        #       test was run by examining sys.argv
384        verbose = any(a.startswith("-v") for a in sys.argv)
385        expected = "\n  ".join(util._compare_eq_iterable(left, right, verbose))
386        assert expected in str(e)
387    else:
388        raise AssertionError("DID NOT RAISE")
389
390
391@pytest.mark.skipif(
392    sys.version_info[:2] == (3, 4),
393    reason="assert_not_called not available in Python 3.4",
394)
395def test_assert_not_called_wrapper(mocker):
396    stub = mocker.stub()
397    stub.assert_not_called()
398    stub()
399    with assert_traceback():
400        stub.assert_not_called()
401
402
403def test_assert_called_with_wrapper(mocker):
404    stub = mocker.stub()
405    stub("foo")
406    stub.assert_called_with("foo")
407    with assert_traceback():
408        stub.assert_called_with("bar")
409
410
411def test_assert_called_once_with_wrapper(mocker):
412    stub = mocker.stub()
413    stub("foo")
414    stub.assert_called_once_with("foo")
415    stub("foo")
416    with assert_traceback():
417        stub.assert_called_once_with("foo")
418
419
420def test_assert_called_once_wrapper(mocker):
421    stub = mocker.stub()
422    if not hasattr(stub, "assert_called_once"):
423        pytest.skip("assert_called_once not available")
424    stub("foo")
425    stub.assert_called_once()
426    stub("foo")
427    with assert_traceback():
428        stub.assert_called_once()
429
430
431def test_assert_called_wrapper(mocker):
432    stub = mocker.stub()
433    if not hasattr(stub, "assert_called"):
434        pytest.skip("assert_called_once not available")
435    with assert_traceback():
436        stub.assert_called()
437    stub("foo")
438    stub.assert_called()
439    stub("foo")
440    stub.assert_called()
441
442
443@pytest.mark.usefixtures("needs_assert_rewrite")
444def test_assert_called_args_with_introspection(mocker):
445    stub = mocker.stub()
446
447    complex_args = ("a", 1, set(["test"]))
448    wrong_args = ("b", 2, set(["jest"]))
449
450    stub(*complex_args)
451    stub.assert_called_with(*complex_args)
452    stub.assert_called_once_with(*complex_args)
453
454    with assert_argument_introspection(complex_args, wrong_args):
455        stub.assert_called_with(*wrong_args)
456        stub.assert_called_once_with(*wrong_args)
457
458
459@pytest.mark.usefixtures("needs_assert_rewrite")
460def test_assert_called_kwargs_with_introspection(mocker):
461    stub = mocker.stub()
462
463    complex_kwargs = dict(foo={"bar": 1, "baz": "spam"})
464    wrong_kwargs = dict(foo={"goo": 1, "baz": "bran"})
465
466    stub(**complex_kwargs)
467    stub.assert_called_with(**complex_kwargs)
468    stub.assert_called_once_with(**complex_kwargs)
469
470    with assert_argument_introspection(complex_kwargs, wrong_kwargs):
471        stub.assert_called_with(**wrong_kwargs)
472        stub.assert_called_once_with(**wrong_kwargs)
473
474
475def test_assert_any_call_wrapper(mocker):
476    stub = mocker.stub()
477    stub("foo")
478    stub("foo")
479    stub.assert_any_call("foo")
480    with assert_traceback():
481        stub.assert_any_call("bar")
482
483
484def test_assert_has_calls(mocker):
485    stub = mocker.stub()
486    stub("foo")
487    stub.assert_has_calls([mocker.call("foo")])
488    with assert_traceback():
489        stub.assert_has_calls([mocker.call("bar")])
490
491
492def test_monkeypatch_ini(mocker, testdir):
493    # Make sure the following function actually tests something
494    stub = mocker.stub()
495    assert stub.assert_called_with.__module__ != stub.__module__
496
497    testdir.makepyfile(
498        """
499        import py.code
500        def test_foo(mocker):
501            stub = mocker.stub()
502            assert stub.assert_called_with.__module__ == stub.__module__
503    """
504    )
505    testdir.makeini(
506        """
507        [pytest]
508        mock_traceback_monkeypatch = false
509    """
510    )
511    result = runpytest_subprocess(testdir)
512    assert result.ret == 0
513
514
515def test_parse_ini_boolean():
516    import pytest_mock
517
518    assert pytest_mock.parse_ini_boolean("True") is True
519    assert pytest_mock.parse_ini_boolean("false") is False
520    with pytest.raises(ValueError):
521        pytest_mock.parse_ini_boolean("foo")
522
523
524def test_patched_method_parameter_name(mocker):
525    """Test that our internal code uses uncommon names when wrapping other
526    "mock" methods to avoid conflicts with user code (#31).
527    """
528
529    class Request:
530        @classmethod
531        def request(cls, method, args):
532            pass
533
534    m = mocker.patch.object(Request, "request")
535    Request.request(method="get", args={"type": "application/json"})
536    m.assert_called_once_with(method="get", args={"type": "application/json"})
537
538
539def test_monkeypatch_native(testdir):
540    """Automatically disable monkeypatching when --tb=native.
541    """
542    testdir.makepyfile(
543        """
544        def test_foo(mocker):
545            stub = mocker.stub()
546            stub(1, greet='hello')
547            stub.assert_called_once_with(1, greet='hey')
548    """
549    )
550    result = runpytest_subprocess(testdir, "--tb=native")
551    assert result.ret == 1
552    assert "During handling of the above exception" not in result.stdout.str()
553    assert "Differing items:" not in result.stdout.str()
554    traceback_lines = [
555        x
556        for x in result.stdout.str().splitlines()
557        if "Traceback (most recent call last)" in x
558    ]
559    assert (
560        len(traceback_lines) == 1
561    )  # make sure there are no duplicated tracebacks (#44)
562
563
564def test_monkeypatch_no_terminal(testdir):
565    """Don't crash without 'terminal' plugin.
566    """
567    testdir.makepyfile(
568        """
569        def test_foo(mocker):
570            stub = mocker.stub()
571            stub(1, greet='hello')
572            stub.assert_called_once_with(1, greet='hey')
573        """
574    )
575    result = runpytest_subprocess(testdir, "-p", "no:terminal", "-s")
576    assert result.ret == 1
577    assert result.stdout.lines == []
578
579
580@pytest.mark.skipif(sys.version_info[0] < 3, reason="Py3 only")
581def test_standalone_mock(testdir):
582    """Check that the "mock_use_standalone" is being used.
583    """
584    testdir.makepyfile(
585        """
586        def test_foo(mocker):
587            pass
588    """
589    )
590    testdir.makeini(
591        """
592        [pytest]
593        mock_use_standalone_module = true
594    """
595    )
596    result = runpytest_subprocess(testdir)
597    assert result.ret == 3
598    result.stderr.fnmatch_lines(["*No module named 'mock'*"])
599
600
601def runpytest_subprocess(testdir, *args):
602    """Testdir.runpytest_subprocess only available in pytest-2.8+"""
603    if hasattr(testdir, "runpytest_subprocess"):
604        return testdir.runpytest_subprocess(*args)
605    else:
606        # pytest 2.7.X
607        return testdir.runpytest(*args)
608
609
610@pytest.mark.usefixtures("needs_assert_rewrite")
611def test_detailed_introspection(testdir):
612    """Check that the "mock_use_standalone" is being used.
613    """
614    testdir.makepyfile(
615        """
616        def test(mocker):
617            m = mocker.Mock()
618            m('fo')
619            m.assert_called_once_with('', bar=4)
620    """
621    )
622    result = testdir.runpytest("-s")
623    if NEW_FORMATTING:
624        expected_lines = [
625            "*AssertionError: expected call not found.",
626            "*Expected: mock('', bar=4)",
627            "*Actual: mock('fo')",
628        ]
629    else:
630        expected_lines = [
631            "*AssertionError: Expected call: mock('', bar=4)*",
632            "*Actual call: mock('fo')*",
633        ]
634    expected_lines += [
635        "*pytest introspection follows:*",
636        "*Args:",
637        "*assert ('fo',) == ('',)",
638        "*At index 0 diff: 'fo' != ''*",
639        "*Use -v to get the full diff*",
640        "*Kwargs:*",
641        "*assert {} == {'bar': 4}*",
642        "*Right contains more items:*",
643        "*{'bar': 4}*",
644        "*Use -v to get the full diff*",
645    ]
646    result.stdout.fnmatch_lines(expected_lines)
647
648
649def test_assert_called_with_unicode_arguments(mocker):
650    """Test bug in assert_call_with called with non-ascii unicode string (#91)"""
651    stub = mocker.stub()
652    stub(b"l\xc3\xb6k".decode("UTF-8"))
653
654    with pytest.raises(AssertionError):
655        stub.assert_called_with(u"lak")
656
657
658def test_plain_stopall(testdir):
659    """patch.stopall() in a test should not cause an error during unconfigure (#137)"""
660    testdir.makepyfile(
661        """
662        import random
663
664        def get_random_number():
665            return random.randint(0, 100)
666
667        def test_get_random_number(mocker):
668            patcher = mocker.mock_module.patch("random.randint", lambda x, y: 5)
669            patcher.start()
670            assert get_random_number() == 5
671            mocker.mock_module.patch.stopall()
672    """
673    )
674    result = testdir.runpytest_subprocess()
675    result.stdout.fnmatch_lines("* 1 passed in *")
676    assert "RuntimeError" not in result.stderr.str()
677