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