├── .bumpversion.cfg ├── .codespellrc ├── .flake8 ├── .github ├── FUNDING.yml └── workflows │ └── test-lint-go.yml ├── .gitignore ├── .python-version ├── .readthedocs.yaml ├── AUTHORS ├── CHANGES.txt ├── LICENSE ├── MANIFEST.in ├── README.rst ├── bump.bat ├── docs ├── Makefile ├── _static │ └── alabaster.css ├── changes.rst ├── conf.py ├── index.rst ├── make.bat ├── nutshell.rst ├── recipes.rst ├── the-functions.rst ├── the-matchers.rst └── walk-through.rst ├── mockito ├── __init__.py ├── inorder.py ├── invocation.py ├── matchers.py ├── mock_registry.py ├── mocking.py ├── mockito.py ├── signature.py ├── spying.py ├── utils.py └── verification.py ├── pyproject.toml ├── pytest.ini ├── requirements-dev.lock ├── requirements.lock ├── setup.py └── tests ├── __init__.py ├── call_original_implem_test.py ├── classmethods_test.py ├── conftest.py ├── deepcopy_test.py ├── ellipsis_test.py ├── instancemethods_test.py ├── issue_82_test.py ├── issue_86_test.py ├── late_imports_test.py ├── matchers_test.py ├── mocking_properties_test.py ├── module.py ├── modulefunctions_test.py ├── my_dict_test.py ├── numpy_test.py ├── signatures_test.py ├── speccing_test.py ├── spying_test.py ├── staticmethods_test.py ├── stubbing_test.py ├── test_base.py ├── unstub_test.py ├── verification_errors_test.py ├── verifications_test.py ├── when2_test.py └── when_interface_test.py /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.6.0-dev 3 | commit = True 4 | message = Bump version to {new_version} 5 | tag = True 6 | tag_name = {new_version} 7 | parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+))? 8 | serialize = 9 | {major}.{minor}.{patch}-{release} 10 | {major}.{minor}.{patch} 11 | 12 | [bumpversion:file:mockito/__init__.py] 13 | 14 | [bumpversion:file:pyproject.toml] 15 | search = version = "{current_version}" 16 | replace = version = "{new_version}" 17 | 18 | [bumpversion:part:release] 19 | optional_value = release 20 | values = 21 | dev 22 | release 23 | -------------------------------------------------------------------------------- /.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | skip=./docs/_build,./.mypy_cache,*.sublime-workspace,.git 3 | ignore-words-list=atleast,atmost 4 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = 3 | D, 4 | W291, 5 | W293, 6 | W391, 7 | W503, 8 | E302, 9 | E303, 10 | E306, 11 | E731 12 | exclude = .git,.cache,docs,.env,.build,build 13 | max-complexity = 10 14 | max-line-length = 79 -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: https://paypal.me/herrkaste 14 | -------------------------------------------------------------------------------- /.github/workflows/test-lint-go.yml: -------------------------------------------------------------------------------- 1 | name: Run tests and lint, maybe deploy 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | os: [ubuntu-latest, macos-latest, windows-latest] 12 | python-version: 13 | - '3.8' 14 | - '3.9' 15 | - '3.10' 16 | - '3.11' 17 | - '3.12' 18 | - '3.13' 19 | name: Run tests on Python ${{ matrix.python-version }} (${{ matrix.os }}) 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Setup python 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install pytest numpy 30 | - name: Run pytest 31 | run: pytest 32 | 33 | lint: 34 | runs-on: ubuntu-latest 35 | name: Lint with flake8 36 | steps: 37 | - uses: actions/checkout@v4 38 | - name: Setup python 39 | uses: actions/setup-python@v5 40 | with: 41 | python-version: '3.10' 42 | - name: Install dependencies 43 | run: | 44 | python -m pip install --upgrade pip 45 | pip install flake8 46 | - name: Run flake8 47 | run: flake8 . 48 | 49 | spellcheck: 50 | runs-on: ubuntu-latest 51 | name: Spellcheck with codespell 52 | steps: 53 | - uses: actions/checkout@v4 54 | - name: Setup python 55 | uses: actions/setup-python@v5 56 | with: 57 | python-version: '3.10' 58 | - name: Install dependencies 59 | run: | 60 | python -m pip install --upgrade pip 61 | pip install codespell 62 | - name: Run codespell 63 | run: codespell 64 | 65 | type-check: 66 | runs-on: ubuntu-latest 67 | name: Check with mypy 68 | steps: 69 | - uses: actions/checkout@v4 70 | - name: Setup python 71 | uses: actions/setup-python@v5 72 | with: 73 | python-version: '3.10' 74 | - name: Install dependencies 75 | run: | 76 | python -m pip install --upgrade pip 77 | pip install mypy 78 | - name: Run mypy 79 | run: mypy mockito 80 | 81 | deploy: 82 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 83 | needs: [test, lint, type-check] 84 | runs-on: ubuntu-latest 85 | name: Deploy to pypi 86 | steps: 87 | - uses: actions/checkout@v4 88 | - name: Set up Python 89 | uses: actions/setup-python@v5 90 | with: 91 | python-version: '3.10' 92 | - name: Install dependencies 93 | run: | 94 | python -m pip install --upgrade pip 95 | pip install build 96 | - name: Build package 97 | run: python -m build 98 | - name: Publish package 99 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 100 | uses: pypa/gh-action-pypi-publish@release/v1 101 | with: 102 | user: __token__ 103 | password: ${{ secrets.PYPI_API_TOKEN }} 104 | verbose: true 105 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build/ 3 | dist/ 4 | docs/_build 5 | .eggs/ 6 | .pytest_cache 7 | __pycache__ 8 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the version of Python and other tools you might need 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.11" 12 | 13 | # Build documentation in the docs/ directory with Sphinx 14 | sphinx: 15 | configuration: docs/conf.py 16 | 17 | # We recommend specifying your dependencies to enable reproducible builds: 18 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 19 | python: 20 | install: 21 | - requirements: requirements-dev.lock 22 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Szczepan Faber 2 | Serhiy Oplakanets 3 | Herr Kaste 4 | Justin Hopper 5 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | MOCKITO CHANGE LOG 2 | ================== 3 | 4 | 5 | Release 1.6.0 6 | -------------------------------- 7 | 8 | - Improved behavior of the ad-hoc methods of mocks. (#92) 9 | - Make the shortcut `when(mock).foo().thenReturn()` officially work and just assume 10 | the user forgot the `None` as return value. 11 | - Disallow the shortcut `when(mock).foo().thenAnswer()` as it reads odd. 12 | 13 | 14 | 15 | Release 1.5.4 (January 22, 2025) 16 | -------------------------------- 17 | 18 | - Implement defined behavior for `deepcopy` for mocks. (#91) 19 | 20 | 21 | 22 | Release 1.5.3 (November 9, 2024) 23 | -------------------------------- 24 | 25 | - Maintenance release adding support for Python 3.13. 26 | 27 | 28 | 29 | Release 1.5.2 (November 6, 2024) 30 | -------------------------------- 31 | 32 | - Fixed issue (#86) where you couldn't setup expectations for objects or mocks with an overridden `__eq__` method. 33 | 34 | 35 | 36 | Release 1.5.1 (September 4, 2024) 37 | --------------------------------- 38 | 39 | - Fixed issue (#82) with methods which have arguments named `value` or `exception`. 40 | 41 | 42 | 43 | Release 1.5.0 (March 4, 2024) 44 | ----------------------------- 45 | 46 | - @Oracking fixed `expect` to support a string as its first argument. All 47 | other functions supported that already. E.g. 48 | 49 | :: 50 | expect('os.path', times=2).exists(...).thenReturn(True) 51 | 52 | 53 | 54 | Release 1.4.0 (August 25, 2022) 55 | ------------------------------- 56 | 57 | - @avandierast implemented `thenCallOriginalImplementation`. See #60 58 | 59 | :: 60 | 61 | # Let `os.path.exists` use the real filesystem (often needed when 62 | # the testing framework needs itself a working `os.path.exists` 63 | # implementation) *but* fake a `.flake8` file. 64 | when(os.path).exists(...).thenCallOriginalImplementation() 65 | when(os.path).exists('.flake8').thenReturn(True) 66 | 67 | 68 | 69 | Release 1.3.5 (August 18, 2022) 70 | ------------------------------- 71 | 72 | - Restore compatibility with Python 2.7 73 | 74 | 75 | Release 1.3.3 (June 23, 2022) 76 | ----------------------------- 77 | 78 | - Hotfix: Correctly unstub methods extracted to the module level, for example ``random.randint()`` et.al. from the standard library. See #53 79 | 80 | 81 | Release 1.3.2 (June 23, 2022) 82 | ----------------------------- 83 | 84 | - Let `mock(spec=SomeClass)` work just as `mock(SomeClass)` 85 | 86 | 87 | Release 1.3.1 (June 14, 2022) 88 | ----------------------------- 89 | 90 | - Reimplement `captor` to capture only during execution phase of a test. 91 | 92 | 93 | Release 1.3.0 (December 3, 2021) 94 | -------------------------------- 95 | 96 | - Teach `captor` to remember all used values (@shashankrnr32). E.g. 97 | 98 | :: 99 | 100 | arg = captor() 101 | mock.do_something(123) 102 | mock.do_something(456) 103 | verify(mock).do_something(arg) 104 | assert arg.all_values == [123, 456] 105 | 106 | 107 | Release 1.2.2 (September 9, 2020) 108 | --------------------------------- 109 | 110 | - Fix typo in ``spy2`` doc 111 | 112 | 113 | Release 1.2.1 (February 19, 2020) 114 | --------------------------------- 115 | 116 | - @nielsvaneck fixed how we can lookup inherited classmethods. 117 | 118 | 119 | Release 1.2.0 (November 25, 2019) 120 | --------------------------------- 121 | 122 | - Code base now is python 3 compatible. No 2to3 anymore. 123 | - Fine tune error messages on unexpected calls or verifications. E.g. if you expect ``when(dog).bark('Wuff')`` but on call time do ``dog.bark('Wufff')``. Likewise, if you call ``dog.bark('Miau')`` and then ``verify(dog).bark('Maui')``. 124 | - @felixonmars fixed a small compatibility issue with python 3.8 125 | - Mocking properties has become a bit easier. (#26) E.g. 126 | 127 | :: 128 | 129 | prop = mock() 130 | when(prop).__get__(...).thenReturn(23) 131 | m = mock({'name': prop}) 132 | 133 | 134 | Release 1.1.1 (August 28, 2018) 135 | ------------------------------- 136 | 137 | - Fix: The context manager (``with``) has now a proper implementation 138 | - Fix: Calling ``patch`` with two arguments can now be used with ``with`` 139 | - Fix: Do not treat the placeholder arguments (Ellipsis, args, kwargs) as special on call time anymore. (T.i. they only have a meaning when stubbing or verifying.) 140 | - Enhancement: Changed some truthy or equality tests to identity (``is``) tests. This reduces edge-cases where some user object defines ``__eq__`` or ``__bool__``. (Hello _numpy_!) 141 | 142 | 143 | Release 1.1.0 (May 2, 2018) 144 | --------------------------- 145 | 146 | - Added ``forget_invocations`` function. Thanks to @maximkulkin 147 | 148 | This is generally useful if you already call mocks during your setup routine. 149 | Now you could call ``forget_invocations`` at the end of your setup, and 150 | have a clean 'recording' for your actual test code. T.i. you don't have 151 | to count the invocations from your setup code anymore. 152 | 153 | 154 | Release 1.0.12 (June 3, 2017) 155 | ----------------------------- 156 | 157 | - Better error messages for failed verifications. By @lwoydziak 158 | 159 | 160 | Release 1.0.7 - 1.0.10 (January 31 - February 2, 2017) 161 | ------------------------------------------------------ 162 | 163 | - ``verifyZeroInteractions`` implemented. This is actually a *breaking change*, because ``verifyZeroInteractions`` was an alias for ``verifyNoMoreInteractions`` (sic!). If you used it, just call the other function. 164 | 165 | - ``verifyStubbedInvocationsAreUsed`` implemented. This is meant to be called right before an ``unstub`` and should improve long time maintenance. It doesn't help during design time. Note that `pytest-mockito` automatically calls this for you. 166 | 167 | - All `verify*` functions now warn you if you pass in an object which was never stubbed. 168 | 169 | 170 | Release 1.0.0 - 1.0.5 (January 24 - 27, 2017) 171 | --------------------------------------------- 172 | 173 | This is a major update; mostly because of internal code reorganization (`imports`) it cannot be guaranteed that this will not break for you. Though if you just used the public API you should be fine. None of the vintage old tests have been removed and they at least pass. 174 | 175 | In general unclassified imports (``from mocktio import *``) are not recommended. But if you did, we do not export `Mock` anymore. `Mock` has been deprecated long ago and is now for internal use only. You must use `mock`. 176 | 177 | Another important change is, that *mockito*'s strict mode is far more strict than before. We now generally try to match the signature of the target method 178 | with your usage. Usually this should help you find bugs in your code, because 179 | it will make it easier to spot changing interfaces. 180 | 181 | - ``mock``, ``when``, ``verify`` return mostly empty objects. It is unlikely to have a method_name clash. 182 | 183 | - Specced mocks ``instance = mock(Class)`` will pass isinstance tests like ``isinstance(instance, Class)`` 184 | 185 | - For ``when`` and ``verify`` the function signature or argument matchers can be greatly simplified. E.g. ``when(requests).get(...).thenReturn('OK')`` will match any argument you pass in. There are ``args`` and ``kwargs`` matchers as well. So ``when(requests).get('https://...', **kwargs).thenReturn(...)`` will make an exact match on the first argument, the url, and ignore all the headers and other stuff. 186 | 187 | - Mocks can be preconfigured: ``mock({'text': 'OK'})``. For specced mocks this would be e.g. ``mock({'text': 'OK'}, spec=requests.Response)``. 188 | 189 | - If you mock or patch an object, the function signatures will be matched. So:: 190 | 191 | def foo(a, b=1): ... 192 | 193 | when(main).foo(12) # will pass 194 | when(main).foo(c=13) # will raise immediately 195 | 196 | - Mock Dummies are now callable:: 197 | 198 | m = mock() 199 | m(1, 2) 200 | verify(m).__call__(...) 201 | 202 | - ``Mock()`` is now an implementation detail; it is **not** exported anymore. Use ``mock()``. 203 | 204 | - You can unstub individual patched objects ``unstub(obj)``. (Before it was all or nothing.) 205 | 206 | - Added basic context manager support when using ``when``. Note that ``verify`` has to be called within the with context. 207 | 208 | :: 209 | 210 | with when(rex).waggle().thenReturn('Yup'): 211 | assert rex.waggle() == 'Yup' 212 | verify(rex).waggle() 213 | 214 | - Aliased ``any_`` to ``ANY``, ``args`` to ``ARGS`` and ``kwargs`` to ``KWARGS``. You can use python's builtin ``any`` as a stand in for ``ANY``. 215 | 216 | - As a convenience you can use our ``any_`` matcher like a type instead of ``any_()``:: 217 | 218 | dummy(1) 219 | verify(dummy).__call__(ANY) 220 | 221 | - Added ``when2``, ``expect``, ``spy2`` 222 | 223 | - Make the mocked function (replacement) more inspectable. Copy `__doc__`, `__name__` etc. 224 | 225 | - You can configure magic methods on mocks:: 226 | 227 | dummy = mock() 228 | when(dummy).__getitem__(1).thenReturn(2) 229 | assert dummy[1] == 2 230 | 231 | 232 | 233 | Release 0.7.1 (December 27, 2016) 234 | --------------------------------- 235 | 236 | - Fix: Allow ``verifyNoMoreInteractions`` call for real (stubbed) objects 237 | 238 | 239 | Release 0.7.0 (July 15, 2016) 240 | ----------------------------- 241 | 242 | - Added a ton of new argument matchers. Namely:: 243 | 244 | 'and_', 'or_', 'not_', 'eq', 'neq', 'lt', 'lte', 'gt', 'gte', 245 | 'arg_that', 'matches', 'captor' 246 | 247 | - Aliases ``any`` matcher to ``any_`` because it's a builtin. 248 | - Fixes an issue where mockito could not correctly verify your function invocations, if you grabbed a method from its object and used it ('detached') as a plain function:: 249 | 250 | m = mock() 251 | f = m.foo # detach 252 | f(1, 2) # pass it around and use it like a function 253 | f(2, 3) 254 | verify(m).foo(...) # finally verify interactions 255 | 256 | Thank you @maximkulkin 257 | 258 | 259 | Release 0.6.1 (May 20, 2016) 260 | ---------------------------- 261 | 262 | - Added ``thenAnswer(callable)``. The callable will be called to compute the answer the stubbed method will return. For that it will receive the arguments of the caller:: 263 | 264 | m = mock() 265 | when(m).do_times(any(), any()).thenAnswer(lambda one, two: one * two) 266 | self.assertEquals(20, m.do_times(5, 4)) 267 | 268 | Thank you @stubbsd 269 | 270 | Release 0.6.0 (April 25, 2016) 271 | ------------------------------ 272 | 273 | - Print keyword arguments nicely. 274 | - Be very forgiving about return values and assume None as default. T.i. ``when(Dog).bark('Miau').thenReturn()`` is enough to return None. 275 | - Make keyword argument order unimportant. 276 | - BREAKING CHANGE: Throw early when calling not expected methods in strict mode. 277 | 278 | Release 0.5.3 (April 23, 2016) 279 | ------------------------------ 280 | 281 | - Remove hard coded distribute setup files. 282 | 283 | Release 0.5.1 (August 4, 2010) 284 | ------------------------------ 285 | BUG Fixes: 286 | - Fixed issue #9 [http://bitbucket.org/szczepiq/mockito-python/issue/9] : Generating stubs from classes caused method to be replaced in original classes. 287 | 288 | Release 0.5.0 (July 26, 2010) 289 | ----------------------------- 290 | API Changes: 291 | - Added possibility to spy on real objects. 292 | - Added "never" syntactic sugar for verifications. 293 | 294 | BUG Fixes: 295 | - Fixed issue with named arguments matching. 296 | 297 | Other Changes: 298 | - Python 2.7 support 299 | - Deprecated APIs now generate deprecation warnings. 300 | 301 | Release 0.4.0 (July 2, 2010) 302 | ---------------------------- 303 | API Changes: 304 | - Added possibility to verify invocations in order. 305 | 306 | BUG Fixes: 307 | - Fixed issue with installing mockito from egg without distribute installed. 308 | 309 | Release 0.3.1 310 | ------------- 311 | Bug-fix release. 312 | 313 | Bug Fixes: 314 | - Fixed annoying issue #8 [http://bitbucket.org/szczepiq/mockito-python/issue/8] 315 | 316 | Release 0.3.0 317 | ------------- 318 | API Changes: 319 | - Renamed mock creation method from "Mock" (upper "M") to "mock". Old name stays for compatibility until 1.0 release. 320 | Other Changes: 321 | - Official Python3 support via distutils + 2to3. 322 | 323 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2019 Szczepan Faber, Serhiy Oplakanets, Herr Kaste 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include CHANGES.txt 3 | include LICENSE 4 | include README.rst 5 | 6 | global-exclude *.py[co] -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Mockito is a spying framework originally based on `the Java library with the same name 2 | `_. (Actually *we* invented the strict stubbing mode 3 | back in 2009.) 4 | 5 | .. image:: https://github.com/kaste/mockito-python/actions/workflows/test-lint-go.yml/badge.svg 6 | :target: https://github.com/kaste/mockito-python/actions/workflows/test-lint-go.yml 7 | 8 | 9 | Install 10 | ======= 11 | 12 | ``pip install mockito`` 13 | 14 | 15 | 16 | Quick Start 17 | =========== 18 | 19 | 90% use case is that you want to stub out a side effect. 20 | 21 | :: 22 | 23 | from mockito import when, mock, unstub 24 | 25 | when(os.path).exists('/foo').thenReturn(True) 26 | 27 | # or: 28 | import requests # the famous library 29 | # you actually want to return a Response-like obj, we'll fake it 30 | response = mock({'status_code': 200, 'text': 'Ok'}) 31 | when(requests).get(...).thenReturn(response) 32 | 33 | # use it 34 | requests.get('http://google.com/') 35 | 36 | # clean up 37 | unstub() 38 | 39 | 40 | 41 | 42 | Read the docs 43 | ============= 44 | 45 | http://mockito-python.readthedocs.io/en/latest/ 46 | 47 | 48 | 49 | Development 50 | =========== 51 | 52 | I use `rye `_, and if you do too: you just clone this repo 53 | to your computer, then run ``rye sync`` in the root directory. Finally, activate 54 | the virtualenv. (``rye shell`` gives you a hint on how to do that.) 55 | 56 | :: 57 | 58 | pytest 59 | -------------------------------------------------------------------------------- /bump.bat: -------------------------------------------------------------------------------- 1 | bump2version release --tag 2 | bump2version patch --no-tag --message "master is {new_version}" 3 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " epub3 to make an epub3" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | @echo " dummy to check syntax errors of document sources" 51 | 52 | .PHONY: clean 53 | clean: 54 | rm -rf $(BUILDDIR)/* 55 | 56 | .PHONY: html 57 | html: 58 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 61 | 62 | .PHONY: dirhtml 63 | dirhtml: 64 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 65 | @echo 66 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 67 | 68 | .PHONY: singlehtml 69 | singlehtml: 70 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 71 | @echo 72 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 73 | 74 | .PHONY: pickle 75 | pickle: 76 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 77 | @echo 78 | @echo "Build finished; now you can process the pickle files." 79 | 80 | .PHONY: json 81 | json: 82 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 83 | @echo 84 | @echo "Build finished; now you can process the JSON files." 85 | 86 | .PHONY: htmlhelp 87 | htmlhelp: 88 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 89 | @echo 90 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 91 | ".hhp project file in $(BUILDDIR)/htmlhelp." 92 | 93 | .PHONY: qthelp 94 | qthelp: 95 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 96 | @echo 97 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 98 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 99 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/mockito-python.qhcp" 100 | @echo "To view the help file:" 101 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/mockito-python.qhc" 102 | 103 | .PHONY: applehelp 104 | applehelp: 105 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 106 | @echo 107 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 108 | @echo "N.B. You won't be able to view it unless you put it in" \ 109 | "~/Library/Documentation/Help or install it in your application" \ 110 | "bundle." 111 | 112 | .PHONY: devhelp 113 | devhelp: 114 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 115 | @echo 116 | @echo "Build finished." 117 | @echo "To view the help file:" 118 | @echo "# mkdir -p $$HOME/.local/share/devhelp/mockito-python" 119 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/mockito-python" 120 | @echo "# devhelp" 121 | 122 | .PHONY: epub 123 | epub: 124 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 125 | @echo 126 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 127 | 128 | .PHONY: epub3 129 | epub3: 130 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 131 | @echo 132 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 133 | 134 | .PHONY: latex 135 | latex: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo 138 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 139 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 140 | "(use \`make latexpdf' here to do that automatically)." 141 | 142 | .PHONY: latexpdf 143 | latexpdf: 144 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 145 | @echo "Running LaTeX files through pdflatex..." 146 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 147 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 148 | 149 | .PHONY: latexpdfja 150 | latexpdfja: 151 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 152 | @echo "Running LaTeX files through platex and dvipdfmx..." 153 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 154 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 155 | 156 | .PHONY: text 157 | text: 158 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 159 | @echo 160 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 161 | 162 | .PHONY: man 163 | man: 164 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 165 | @echo 166 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 167 | 168 | .PHONY: texinfo 169 | texinfo: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo 172 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 173 | @echo "Run \`make' in that directory to run these through makeinfo" \ 174 | "(use \`make info' here to do that automatically)." 175 | 176 | .PHONY: info 177 | info: 178 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 179 | @echo "Running Texinfo files through makeinfo..." 180 | make -C $(BUILDDIR)/texinfo info 181 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 182 | 183 | .PHONY: gettext 184 | gettext: 185 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 186 | @echo 187 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 188 | 189 | .PHONY: changes 190 | changes: 191 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 192 | @echo 193 | @echo "The overview file is in $(BUILDDIR)/changes." 194 | 195 | .PHONY: linkcheck 196 | linkcheck: 197 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 198 | @echo 199 | @echo "Link check complete; look for any errors in the above output " \ 200 | "or in $(BUILDDIR)/linkcheck/output.txt." 201 | 202 | .PHONY: doctest 203 | doctest: 204 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 205 | @echo "Testing of doctests in the sources finished, look at the " \ 206 | "results in $(BUILDDIR)/doctest/output.txt." 207 | 208 | .PHONY: coverage 209 | coverage: 210 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 211 | @echo "Testing of coverage in the sources finished, look at the " \ 212 | "results in $(BUILDDIR)/coverage/python.txt." 213 | 214 | .PHONY: xml 215 | xml: 216 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 217 | @echo 218 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 219 | 220 | .PHONY: pseudoxml 221 | pseudoxml: 222 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 223 | @echo 224 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 225 | 226 | .PHONY: dummy 227 | dummy: 228 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 229 | @echo 230 | @echo "Build finished. Dummy builder generates no files." 231 | -------------------------------------------------------------------------------- /docs/changes.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGES.txt -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # mockito-python documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Apr 26 14:00:19 2016. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import pkg_resources 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.todo', 35 | # 'sphinx.ext.githubpages', 36 | ] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix(es) of source filenames. 42 | # You can specify multiple suffix as a list of string: 43 | # source_suffix = ['.rst', '.md'] 44 | source_suffix = '.rst' 45 | 46 | # The encoding of source files. 47 | #source_encoding = 'utf-8-sig' 48 | 49 | # The master toctree document. 50 | master_doc = 'index' 51 | 52 | # General information about the project. 53 | project = u'mockito-python' 54 | copyright = u'2016, Szczepan Faber, Serhiy Oplakanets, herr.kaste' 55 | author = u'Szczepan Faber, Serhiy Oplakanets, herr.kaste' 56 | 57 | # The version info for the project you're documenting, acts as replacement for 58 | # |version| and |release|, also used in various other places throughout the 59 | # built documents. 60 | # 61 | # The short X.Y version. 62 | # version = u'0.6' 63 | # The full version, including alpha/beta/rc tags. 64 | # release = u'0.6.1' 65 | 66 | try: 67 | release = pkg_resources.get_distribution('mockito').version 68 | except pkg_resources.DistributionNotFound: 69 | print('mockito must be installed to build the documentation.') 70 | print('Install from source using `pip install -e .` in a virtualenv.') 71 | sys.exit(1) 72 | 73 | if 'dev' in release: 74 | release = ''.join(release.partition('dev')[:2]) 75 | 76 | version = '.'.join(release.split('.')[:2]) 77 | 78 | # The language for content autogenerated by Sphinx. Refer to documentation 79 | # for a list of supported languages. 80 | # 81 | # This is also used if you do content translation via gettext catalogs. 82 | # Usually you set "language" from the command line for these cases. 83 | language = None 84 | 85 | # There are two options for replacing |today|: either, you set today to some 86 | # non-false value, then it is used: 87 | #today = '' 88 | # Else, today_fmt is used as the format for a strftime call. 89 | #today_fmt = '%B %d, %Y' 90 | 91 | # List of patterns, relative to source directory, that match files and 92 | # directories to ignore when looking for source files. 93 | # This patterns also effect to html_static_path and html_extra_path 94 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 95 | 96 | # The reST default role (used for this markup: `text`) to use for all 97 | # documents. 98 | #default_role = None 99 | 100 | # If true, '()' will be appended to :func: etc. cross-reference text. 101 | #add_function_parentheses = True 102 | 103 | # If true, the current module name will be prepended to all description 104 | # unit titles (such as .. function::). 105 | #add_module_names = True 106 | 107 | # If true, sectionauthor and moduleauthor directives will be shown in the 108 | # output. They are ignored by default. 109 | #show_authors = False 110 | 111 | # The name of the Pygments (syntax highlighting) style to use. 112 | pygments_style = 'sphinx' 113 | 114 | # A list of ignored prefixes for module index sorting. 115 | #modindex_common_prefix = [] 116 | 117 | # If true, keep warnings as "system message" paragraphs in the built documents. 118 | #keep_warnings = False 119 | 120 | # If true, `todo` and `todoList` produce output, else they produce nothing. 121 | todo_include_todos = True 122 | 123 | 124 | # -- Options for HTML output ---------------------------------------------- 125 | 126 | # The theme to use for HTML and HTML Help pages. See the documentation for 127 | # a list of builtin themes. 128 | html_theme = 'alabaster' 129 | 130 | # Theme options are theme-specific and customize the look and feel of a theme 131 | # further. For a list of options available for each theme, see the 132 | # documentation. 133 | #html_theme_options = {} 134 | 135 | # Add any paths that contain custom themes here, relative to this directory. 136 | #html_theme_path = [] 137 | 138 | # The name for this set of Sphinx documents. 139 | # " v documentation" by default. 140 | #html_title = u'mockito-python v0.6.1' 141 | 142 | # A shorter title for the navigation bar. Default is the same as html_title. 143 | #html_short_title = None 144 | 145 | # The name of an image file (relative to this directory) to place at the top 146 | # of the sidebar. 147 | #html_logo = None 148 | 149 | # The name of an image file (relative to this directory) to use as a favicon of 150 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 151 | # pixels large. 152 | #html_favicon = None 153 | 154 | # Add any paths that contain custom static files (such as style sheets) here, 155 | # relative to this directory. They are copied after the builtin static files, 156 | # so a file named "default.css" will overwrite the builtin "default.css". 157 | html_static_path = ['_static'] 158 | 159 | # Add any extra paths that contain custom files (such as robots.txt or 160 | # .htaccess) here, relative to this directory. These files are copied 161 | # directly to the root of the documentation. 162 | #html_extra_path = [] 163 | 164 | # If not None, a 'Last updated on:' timestamp is inserted at every page 165 | # bottom, using the given strftime format. 166 | # The empty string is equivalent to '%b %d, %Y'. 167 | #html_last_updated_fmt = None 168 | 169 | # If true, SmartyPants will be used to convert quotes and dashes to 170 | # typographically correct entities. 171 | #html_use_smartypants = True 172 | 173 | # Custom sidebar templates, maps document names to template names. 174 | #html_sidebars = {} 175 | html_sidebars = { '**': ['localtoc.html', 'relations.html', 'searchbox.html'], } 176 | 177 | # Additional templates that should be rendered to pages, maps page names to 178 | # template names. 179 | #html_additional_pages = {} 180 | 181 | # If false, no module index is generated. 182 | #html_domain_indices = True 183 | 184 | # If false, no index is generated. 185 | #html_use_index = True 186 | 187 | # If true, the index is split into individual pages for each letter. 188 | #html_split_index = False 189 | 190 | # If true, links to the reST sources are added to the pages. 191 | #html_show_sourcelink = True 192 | 193 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 194 | #html_show_sphinx = True 195 | 196 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 197 | #html_show_copyright = True 198 | 199 | # If true, an OpenSearch description file will be output, and all pages will 200 | # contain a tag referring to it. The value of this option must be the 201 | # base URL from which the finished HTML is served. 202 | #html_use_opensearch = '' 203 | 204 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 205 | #html_file_suffix = None 206 | 207 | # Language to be used for generating the HTML full-text search index. 208 | # Sphinx supports the following languages: 209 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 210 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' 211 | #html_search_language = 'en' 212 | 213 | # A dictionary with options for the search language support, empty by default. 214 | # 'ja' uses this config value. 215 | # 'zh' user can custom change `jieba` dictionary path. 216 | #html_search_options = {'type': 'default'} 217 | 218 | # The name of a javascript file (relative to the configuration directory) that 219 | # implements a search results scorer. If empty, the default will be used. 220 | #html_search_scorer = 'scorer.js' 221 | 222 | # Output file base name for HTML help builder. 223 | htmlhelp_basename = 'mockito-pythondoc' 224 | 225 | # -- Options for LaTeX output --------------------------------------------- 226 | 227 | latex_elements = { 228 | # The paper size ('letterpaper' or 'a4paper'). 229 | #'papersize': 'letterpaper', 230 | 231 | # The font size ('10pt', '11pt' or '12pt'). 232 | #'pointsize': '10pt', 233 | 234 | # Additional stuff for the LaTeX preamble. 235 | #'preamble': '', 236 | 237 | # Latex figure (float) alignment 238 | #'figure_align': 'htbp', 239 | } 240 | 241 | # Grouping the document tree into LaTeX files. List of tuples 242 | # (source start file, target name, title, 243 | # author, documentclass [howto, manual, or own class]). 244 | latex_documents = [ 245 | (master_doc, 'mockito-python.tex', u'mockito-python Documentation', 246 | u'Szczepan Faber, Serhiy Oplakanets, herr.kaste', 'manual'), 247 | ] 248 | 249 | # The name of an image file (relative to this directory) to place at the top of 250 | # the title page. 251 | #latex_logo = None 252 | 253 | # For "manual" documents, if this is true, then toplevel headings are parts, 254 | # not chapters. 255 | #latex_use_parts = False 256 | 257 | # If true, show page references after internal links. 258 | #latex_show_pagerefs = False 259 | 260 | # If true, show URL addresses after external links. 261 | #latex_show_urls = False 262 | 263 | # Documents to append as an appendix to all manuals. 264 | #latex_appendices = [] 265 | 266 | # If false, no module index is generated. 267 | #latex_domain_indices = True 268 | 269 | 270 | # -- Options for manual page output --------------------------------------- 271 | 272 | # One entry per manual page. List of tuples 273 | # (source start file, name, description, authors, manual section). 274 | man_pages = [ 275 | (master_doc, 'mockito-python', u'mockito-python Documentation', 276 | [author], 1) 277 | ] 278 | 279 | # If true, show URL addresses after external links. 280 | #man_show_urls = False 281 | 282 | 283 | # -- Options for Texinfo output ------------------------------------------- 284 | 285 | # Grouping the document tree into Texinfo files. List of tuples 286 | # (source start file, target name, title, author, 287 | # dir menu entry, description, category) 288 | texinfo_documents = [ 289 | (master_doc, 'mockito-python', u'mockito-python Documentation', 290 | author, 'mockito-python', 'One line description of project.', 291 | 'Miscellaneous'), 292 | ] 293 | 294 | # Documents to append as an appendix to all manuals. 295 | #texinfo_appendices = [] 296 | 297 | # If false, no module index is generated. 298 | #texinfo_domain_indices = True 299 | 300 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 301 | #texinfo_show_urls = 'footnote' 302 | 303 | # If true, do not generate a @detailmenu in the "Top" node's menu. 304 | #texinfo_no_detailmenu = False 305 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. mockito-python documentation master file, created by 2 | sphinx-quickstart on Tue Apr 26 14:00:19 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. module:: mockito 7 | 8 | Mockito is a spying framework originally based on the Java library with the same name. 9 | 10 | .. image:: https://github.com/kaste/mockito-python/actions/workflows/test-lint-go.yml/badge.svg 11 | :target: https://github.com/kaste/mockito-python/actions/workflows/test-lint-go.yml 12 | 13 | 14 | 15 | Install 16 | ------- 17 | 18 | .. code-block:: python 19 | 20 | pip install mockito 21 | 22 | If you already use `pytest`, consider using the plugin `pytest-mockito `_. 23 | 24 | 25 | Use 26 | --- 27 | 28 | .. code-block:: python 29 | 30 | from mockito import when, mock, unstub 31 | 32 | when(os.path).exists('/foo').thenReturn(True) 33 | 34 | # or: 35 | import requests # the famous library 36 | # you actually want to return a Response-like obj, we'll fake it 37 | response = mock({'status_code': 200, 'text': 'Ok'}) 38 | when(requests).get(...).thenReturn(response) 39 | 40 | # use it 41 | requests.get('http://google.com/') 42 | 43 | # clean up 44 | unstub() 45 | 46 | 47 | Features 48 | -------- 49 | 50 | Super easy to set up different answers. 51 | 52 | :: 53 | 54 | # Well, you know the internet 55 | when(requests).get(...).thenReturn(mock({'status': 501})) \ 56 | .thenRaise(Timeout("I'm flaky")) \ 57 | .thenReturn(mock({'status': 200, 'text': 'Ok'})) 58 | 59 | State-of-the-art, high-five argument matchers:: 60 | 61 | # Use the Ellipsis, if you don't care 62 | when(deferred).defer(...).thenRaise(Timeout) 63 | 64 | # Or **kwargs 65 | from mockito import kwargs # or KWARGS 66 | when(requests).get('http://my-api.com/user', **kwargs) 67 | 68 | # The usual matchers 69 | from mockito import ANY, or_, not_ 70 | number = or_(ANY(int), ANY(float)) 71 | when(math).sqrt(not_(number)).thenRaise( 72 | TypeError('argument must be a number')) 73 | 74 | No need to `verify` (`assert_called_with`) all the time:: 75 | 76 | # Different arguments, different answers 77 | when(foo).bar(1).thenReturn(2) 78 | when(foo).bar(2).thenReturn(3) 79 | 80 | # but: 81 | foo.bar(3) # throws immediately: unexpected invocation 82 | 83 | # because of that you just know that when 84 | # you get a `2`, you called it with `1` 85 | 86 | 87 | Signature checking:: 88 | 89 | # when stubbing 90 | when(requests).get() # throws immediately: TypeError url required 91 | 92 | # when calling 93 | request.get(location='http://example.com/') # TypeError 94 | 95 | 96 | Read 97 | ---- 98 | 99 | .. toctree:: 100 | :maxdepth: 1 101 | 102 | walk-through 103 | recipes 104 | the-functions 105 | the-matchers 106 | Changelog 107 | 108 | 109 | 110 | Report issues, contribute more documentation or give feedback at `Github `_! 111 | 112 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | echo. dummy to check syntax errors of document sources 43 | goto end 44 | ) 45 | 46 | if "%1" == "clean" ( 47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 48 | del /q /s %BUILDDIR%\* 49 | goto end 50 | ) 51 | 52 | 53 | REM Check if sphinx-build is available and fallback to Python version if any 54 | %SPHINXBUILD% 1>NUL 2>NUL 55 | if errorlevel 9009 goto sphinx_python 56 | goto sphinx_ok 57 | 58 | :sphinx_python 59 | 60 | set SPHINXBUILD=python -m sphinx.__init__ 61 | %SPHINXBUILD% 2> nul 62 | if errorlevel 9009 ( 63 | echo. 64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 65 | echo.installed, then set the SPHINXBUILD environment variable to point 66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 67 | echo.may add the Sphinx directory to PATH. 68 | echo. 69 | echo.If you don't have Sphinx installed, grab it from 70 | echo.http://sphinx-doc.org/ 71 | exit /b 1 72 | ) 73 | 74 | :sphinx_ok 75 | 76 | 77 | if "%1" == "html" ( 78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 79 | if errorlevel 1 exit /b 1 80 | echo. 81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 82 | goto end 83 | ) 84 | 85 | if "%1" == "dirhtml" ( 86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 87 | if errorlevel 1 exit /b 1 88 | echo. 89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 90 | goto end 91 | ) 92 | 93 | if "%1" == "singlehtml" ( 94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 95 | if errorlevel 1 exit /b 1 96 | echo. 97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 98 | goto end 99 | ) 100 | 101 | if "%1" == "pickle" ( 102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 103 | if errorlevel 1 exit /b 1 104 | echo. 105 | echo.Build finished; now you can process the pickle files. 106 | goto end 107 | ) 108 | 109 | if "%1" == "json" ( 110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 111 | if errorlevel 1 exit /b 1 112 | echo. 113 | echo.Build finished; now you can process the JSON files. 114 | goto end 115 | ) 116 | 117 | if "%1" == "htmlhelp" ( 118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 119 | if errorlevel 1 exit /b 1 120 | echo. 121 | echo.Build finished; now you can run HTML Help Workshop with the ^ 122 | .hhp project file in %BUILDDIR%/htmlhelp. 123 | goto end 124 | ) 125 | 126 | if "%1" == "qthelp" ( 127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 128 | if errorlevel 1 exit /b 1 129 | echo. 130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 131 | .qhcp project file in %BUILDDIR%/qthelp, like this: 132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\mockito-python.qhcp 133 | echo.To view the help file: 134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\mockito-python.ghc 135 | goto end 136 | ) 137 | 138 | if "%1" == "devhelp" ( 139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 140 | if errorlevel 1 exit /b 1 141 | echo. 142 | echo.Build finished. 143 | goto end 144 | ) 145 | 146 | if "%1" == "epub" ( 147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 148 | if errorlevel 1 exit /b 1 149 | echo. 150 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 151 | goto end 152 | ) 153 | 154 | if "%1" == "epub3" ( 155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 156 | if errorlevel 1 exit /b 1 157 | echo. 158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 159 | goto end 160 | ) 161 | 162 | if "%1" == "latex" ( 163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 164 | if errorlevel 1 exit /b 1 165 | echo. 166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdf" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "latexpdfja" ( 181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 182 | cd %BUILDDIR%/latex 183 | make all-pdf-ja 184 | cd %~dp0 185 | echo. 186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 187 | goto end 188 | ) 189 | 190 | if "%1" == "text" ( 191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 192 | if errorlevel 1 exit /b 1 193 | echo. 194 | echo.Build finished. The text files are in %BUILDDIR%/text. 195 | goto end 196 | ) 197 | 198 | if "%1" == "man" ( 199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 200 | if errorlevel 1 exit /b 1 201 | echo. 202 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 203 | goto end 204 | ) 205 | 206 | if "%1" == "texinfo" ( 207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 208 | if errorlevel 1 exit /b 1 209 | echo. 210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 211 | goto end 212 | ) 213 | 214 | if "%1" == "gettext" ( 215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 216 | if errorlevel 1 exit /b 1 217 | echo. 218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 219 | goto end 220 | ) 221 | 222 | if "%1" == "changes" ( 223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 224 | if errorlevel 1 exit /b 1 225 | echo. 226 | echo.The overview file is in %BUILDDIR%/changes. 227 | goto end 228 | ) 229 | 230 | if "%1" == "linkcheck" ( 231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 232 | if errorlevel 1 exit /b 1 233 | echo. 234 | echo.Link check complete; look for any errors in the above output ^ 235 | or in %BUILDDIR%/linkcheck/output.txt. 236 | goto end 237 | ) 238 | 239 | if "%1" == "doctest" ( 240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 241 | if errorlevel 1 exit /b 1 242 | echo. 243 | echo.Testing of doctests in the sources finished, look at the ^ 244 | results in %BUILDDIR%/doctest/output.txt. 245 | goto end 246 | ) 247 | 248 | if "%1" == "coverage" ( 249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 250 | if errorlevel 1 exit /b 1 251 | echo. 252 | echo.Testing of coverage in the sources finished, look at the ^ 253 | results in %BUILDDIR%/coverage/python.txt. 254 | goto end 255 | ) 256 | 257 | if "%1" == "xml" ( 258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 259 | if errorlevel 1 exit /b 1 260 | echo. 261 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 262 | goto end 263 | ) 264 | 265 | if "%1" == "pseudoxml" ( 266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 267 | if errorlevel 1 exit /b 1 268 | echo. 269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 270 | goto end 271 | ) 272 | 273 | if "%1" == "dummy" ( 274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy 275 | if errorlevel 1 exit /b 1 276 | echo. 277 | echo.Build finished. Dummy builder generates no files. 278 | goto end 279 | ) 280 | 281 | :end 282 | -------------------------------------------------------------------------------- /docs/nutshell.rst: -------------------------------------------------------------------------------- 1 | TL;DR 2 | ----- 3 | 4 | 5 | :: 6 | 7 | >>> from mockito import * 8 | >>> myMock = mock() 9 | >>> when(myMock).getStuff().thenReturn('stuff') 10 | 11 | >>> myMock.getStuff() 12 | 'stuff' 13 | >>> verify(myMock).getStuff() 14 | 15 | >>> when(myMock).doSomething().thenRaise(Exception('Did a bad thing')) 16 | 17 | >>> myMock.doSomething() 18 | Traceback (most recent call last): 19 | <...> 20 | Exception: Did a bad thing 21 | 22 | No difference whatsoever when you mock modules 23 | 24 | :: 25 | 26 | >>> import os.path 27 | >>> when(os.path).exists('somewhere/somewhat').thenReturn(True) 28 | 29 | >>> when(os.path).exists('somewhere/something').thenReturn(False) 30 | 31 | >>> os.path.exists('somewhere/somewhat') 32 | True 33 | >>> os.path.exists('somewhere/something') 34 | False 35 | >>> os.path.exists('another_place') 36 | Traceback (most recent call last): 37 | <...> 38 | mockito.invocation.InvocationError: You called exists with ('another_place',) as 39 | arguments but we did not expect that. 40 | 41 | >>> when(os.path).exist('./somewhat').thenReturn(True) 42 | Traceback (most recent call last): 43 | <...> 44 | mockito.invocation.InvocationError: You tried to stub a method 'exist' 45 | the object () doesn't have. 46 | 47 | If that's too strict, you can change it 48 | 49 | :: 50 | 51 | >>> when(os.path, strict=False).exist('another_place').thenReturn('well, nice here') 52 | 53 | >>> os.path.exist('another_place') 54 | 'well, nice here' 55 | >>> os.path.exist('and here?') 56 | >>> 57 | 58 | No surprise, you can do the same with your classes 59 | 60 | :: 61 | 62 | >>> class Dog(object): 63 | ... def bark(self): 64 | ... return "Wau" 65 | ... 66 | >>> when(Dog).bark().thenReturn('Miau!') 67 | 68 | >>> rex = Dog() 69 | >>> rex.bark() 70 | 'Miau!' 71 | 72 | or just with instances, first unstub 73 | 74 | :: 75 | 76 | >>> unstub() 77 | >>> rex.bark() 78 | 'Wau' 79 | 80 | then do 81 | 82 | :: 83 | 84 | >>> when(rex).bark().thenReturn('Grrrrr').thenReturn('Wuff') 85 | 86 | 87 | and get something different on consecutive calls 88 | 89 | :: 90 | 91 | >>> rex.bark() 92 | 'Grrrrr' 93 | >>> rex.bark() 94 | 'Wuff' 95 | >>> rex.bark() 96 | 'Wuff' 97 | 98 | and since you stubbed an instance, a different instance will not be stubbed 99 | 100 | :: 101 | 102 | >>> bello = Dog() 103 | >>> bello.bark() 104 | 'Wau' 105 | 106 | You have 4 modifiers when verifying 107 | 108 | :: 109 | 110 | >>> verify(rex, times=3).bark() 111 | >>> verify(rex, atleast=1).bark() 112 | >>> verify(rex, atmost=3).bark() 113 | >>> verify(rex, between=[1,3]).bark() 114 | >>> 115 | 116 | Finally, we have two matchers 117 | 118 | :: 119 | 120 | >>> myMock = mock() 121 | >>> when(myMock).do(any(int)).thenReturn('A number') 122 | 123 | >>> when(myMock).do(any(str)).thenReturn('A string') 124 | 125 | >>> myMock.do(2) 126 | 'A number' 127 | >>> myMock.do('times') 128 | 'A string' 129 | 130 | >>> verify(myMock).do(any(int)) 131 | >>> verify(myMock).do(any(str)) 132 | >>> verify(myMock).do(contains('time')) 133 | 134 | >>> exit() 135 | 136 | .. toctree:: 137 | :maxdepth: 2 138 | 139 | -------------------------------------------------------------------------------- /docs/recipes.rst: -------------------------------------------------------------------------------- 1 | .. module:: mockito 2 | 3 | 4 | Recipes 5 | ======= 6 | 7 | 8 | Classes as factories 9 | -------------------- 10 | 11 | We want to test the following code:: 12 | 13 | import requests 14 | 15 | def fetch(url): 16 | session = requests.Session() 17 | return session.get(url) 18 | 19 | In a traditional sense this code is not designed for *testability*. But we don't care here. 20 | 21 | Python has no `new` keyword to get fresh instances from classes. Man, that was a good decision, Guido! So the uppercase `S` in `requests.Session()` doesn't have to stop us in any way. It looks like a function call, and we treat it like such: The plan is to replace `Session` with a factory function that returns a (mocked) session:: 22 | 23 | from mockito import when, mock, verifyStubbedInvocationsAreUsed 24 | 25 | def test_fetch(unstub): 26 | url = 'http://example.com/' 27 | response = mock({'text': 'Ok'}, spec=requests.Response) 28 | # remember: `mock` here just creates an empty object specced after 29 | # requests.Session 30 | session = mock(requests.Session) 31 | # `when` here configures the mock 32 | when(session).get(url).thenReturn(response) 33 | # `when` *patches* the globally available *requests* module 34 | when(requests).Session().thenReturn(session) # <= 35 | 36 | res = fetch(url) 37 | assert res.text == 'Ok' 38 | 39 | # no need to verify anything here, if we get the expected response 40 | # back, `url` must have been passed through the system, otherwise 41 | # mockito would have thrown. 42 | # We *could* ensure that our mocks are actually used, if we want: 43 | verifyStubbedInvocationsAreUsed() 44 | 45 | 46 | Faking magic methods 47 | -------------------- 48 | 49 | We want to test the following code:: 50 | 51 | import requests 52 | 53 | def fetch_2(url): 54 | with requests.Session() as session: 55 | return session.get(url) 56 | 57 | It's basically the same problem, but we need to add support for the context manager, the `with` interface:: 58 | 59 | from mockito import when, mock, args 60 | 61 | def test_fetch_with(unstub): 62 | url = 'http://example.com/' 63 | response = mock({'text': 'Ok'}, spec=requests.Response) 64 | 65 | session = mock(requests.Session) 66 | when(session).get(url).thenReturn(response) 67 | when(session).__enter__().thenReturn(session) # <= 68 | when(session).__exit__(*args) # <= 69 | 70 | when(requests).Session().thenReturn(session) 71 | 72 | res = fetch_2(url) 73 | assert res.text == 'Ok' 74 | 75 | 76 | Deepcopies 77 | ---------- 78 | 79 | Python's `deepcopy` is tied to `__deepcopy__`, in a nutshell `deepcopy(m)` will call `m.__deepcopy__()`. 80 | For a strict mock, `deepcopy(m)` will raise an error as long as the call is unexpected -- as usual. 81 | 82 | While you could completely fake it -- 83 | 84 | :: 85 | 86 | when(m).__deepcopy__(...).thenReturn(42) 87 | 88 | -- you could also enable the standard implementation by configuring the mock, e.g. 89 | 90 | :: 91 | 92 | mock({"__deepcopy__": None}, strict=True) 93 | 94 | Dumb mocks are copied correctly by default. 95 | 96 | However, there is a possible catch: deep mutable objects must be set on the mock's instance, not the class. 97 | And the constructors configuration is set on the class, not the instance. Huh? Let's show an example:: 98 | 99 | m = mock() 100 | m.foo = [1] # <= this is set on the instance, not the class 101 | 102 | m = mock({"foo": [1]}) # <= this is set on the class, not the instance 103 | 104 | Don't rely on that latter "feature", initially the configurataion was meant to only set methods, and especially 105 | special, dunder methods, -- and properties. If we get proper support for properties, we'll likely make a change 106 | here too. 107 | 108 | Btw, `copy` will *just work* for strict mocks and does not raise an error when not configured/expected. This is 109 | just not implemented and considered not-worth-the-effort. 110 | -------------------------------------------------------------------------------- /docs/the-functions.rst: -------------------------------------------------------------------------------- 1 | .. module:: mockito 2 | 3 | 4 | The functions 5 | ============= 6 | 7 | Stable entrypoints are: :func:`when`, :func:`mock`, :func:`unstub`, :func:`verify`, :func:`spy`. Experimental or new function introduces with v1.0.x are: :func:`when2`, :func:`expect`, :func:`verifyNoUnwantedInteractions`, :func:`verifyStubbedInvocationsAreUsed`, :func:`patch` 8 | 9 | .. autofunction:: when 10 | .. autofunction:: when2 11 | .. autofunction:: patch 12 | .. autofunction:: expect 13 | .. autofunction:: mock 14 | .. autofunction:: unstub 15 | .. autofunction:: forget_invocations 16 | .. autofunction:: spy 17 | .. autofunction:: spy2 18 | 19 | This looks like a plethora of verification functions, and especially since you often don't need to `verify` at all. 20 | 21 | .. autofunction:: verify 22 | .. autofunction:: verifyNoMoreInteractions 23 | .. autofunction:: verifyZeroInteractions 24 | .. autofunction:: verifyNoUnwantedInteractions 25 | .. autofunction:: verifyStubbedInvocationsAreUsed 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/the-matchers.rst: -------------------------------------------------------------------------------- 1 | The matchers 2 | ============ 3 | 4 | 5 | .. automodule:: mockito.matchers 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/walk-through.rst: -------------------------------------------------------------------------------- 1 | The Walk-through 2 | ================ 3 | 4 | The 90% use case is that want to stub out a side effect. This is also known as (monkey-)patching. With mockito, it's:: 5 | 6 | from mockito import when 7 | 8 | # stub `os.path.exists` 9 | when(os.path).exists('/foo').thenReturn(True) 10 | 11 | os.path.exists('/foo') # => True 12 | os.path.exists('/bar') # -> throws unexpected invocation 13 | 14 | So in difference to traditional patching, in mockito you always specify concrete arguments (a call signature), and its outcome, usually a return value via `thenReturn` or a raised exception via `thenRaise`. That effectively turns function calls into constants for the time of the test. 15 | 16 | There are of course reasons when you don't want to overspecify specific tests. You *just* want the desired stub answer. Here we go:: 17 | 18 | when(os.path).exists(...).thenReturn(True) 19 | 20 | # now, obviously, you get the same answer, regardless of the arguments 21 | os.path.exists('FooBar') # => True 22 | os.path.exists('BarFoo') # => True 23 | 24 | You can combine both stubs. E.g. nothing exists, except one file:: 25 | 26 | when(os.path).exists(...).thenReturn(False) 27 | when(os.path).exists('.flake8').thenReturn(True) 28 | 29 | And because it's a similar pattern, we can introduce :func:`spy2` here. Spies call through the original implementation of a given function. E.g. everything is as it is, except `'.flake8'` is just not there:: 30 | 31 | from mockito import spy2 32 | spy2(os.path.exists) 33 | when(os.path).exists('.flake8').thenReturn(False) 34 | 35 | Another way to write the same thing is:: 36 | 37 | when(os.path).exists(...).thenCallOriginalImplementation() 38 | when(os.path).exists('.flake8').thenReturn(False) 39 | 40 | And actually `spy2` uses `thenCallOriginalImplementation` under the hood. Why spying at all: either you want the implementation *almost* intact as above or you 41 | need the implementation to stay intact but want to `verify` its usage. E.g.:: 42 | 43 | spy2(os.path.exists) 44 | # now do some stuff 45 | do_stuff() 46 | # then verify the we asked for the cache exactly once 47 | verify(os.path, times=1).exists("cache/.foo") 48 | 49 | 50 | When patching, you **MUST** **not** forget to :func:`unstub` of course! You can do this explicitly 51 | 52 | :: 53 | 54 | from mockito import unstub 55 | unstub() # restore os.path module 56 | 57 | Usually you do this unconditionally in your `teardown` function. If you're using `pytest`, you could define a fixture instead 58 | 59 | :: 60 | 61 | # conftest.py 62 | import pytest 63 | 64 | @pytest.fixture 65 | def unstub(): 66 | from mockito import unstub 67 | yield 68 | unstub() 69 | 70 | # my_test.py 71 | import pytest 72 | pytestmark = pytest.mark.usefixtures("unstub") 73 | 74 | But very often you just use context managers (aka `with`), and mockito will unstub on 'exit' automatically:: 75 | 76 | # E.g. test that `exists` gets never called 77 | with expect(os.path, times=0).exists('.flake8'): 78 | # within the block `os.path.exists` is patched 79 | cached_dir_lookup('.flake8') 80 | # at the end of the block `os.path` gets unpatched 81 | 82 | # Which is btw roughly the same as doing 83 | with when(os.path).exists('.flake8'): 84 | cached_dir_lookup('.flake8') 85 | verify(os.path, times=0).exists(...) 86 | 87 | Now let's mix global module patching with mocks. We want to test the following function using the fab `requests` library:: 88 | 89 | import requests 90 | 91 | def get_text(url): 92 | res = requests.get(url) 93 | if 200 <= res.status_code < 300: 94 | return res.text 95 | return None 96 | 97 | How, dare, we did not inject our dependencies! Obviously we can get over that by patching at the module level like before:: 98 | 99 | when(requests).get('https://example.com/api').thenReturn(...) 100 | 101 | But what should we return? We know it's a `requests.Response` object, (Actually I know this bc I typed this in the ipython REPL first.) But how to construct such a `Response`, its `__init__` doesn't even take any arguments? 102 | 103 | Should we actually use a 'real' response object? No, we fake it using :func:`mock`. 104 | 105 | :: 106 | 107 | # setup 108 | response = mock({ 109 | 'status_code': 200, 110 | 'text': 'Ok' 111 | }, spec=requests.Response) 112 | when(requests).get('https://example.com/api').thenReturn(response) 113 | 114 | # run 115 | assert get_text('https://example.com/api') == 'Ok' 116 | 117 | # done! 118 | 119 | Say you want to mock the class Dog:: 120 | 121 | class Dog(object): 122 | def bark(self): 123 | return 'Wuff' 124 | 125 | 126 | # either mock the class 127 | when(Dog).bark().thenReturn('Miau!') 128 | # now all instances have a different behavior 129 | rex = Dog() 130 | assert rex.bark() == 'Miau!' 131 | 132 | # or mock a concrete instance 133 | when(rex).bark().thenReturn('Grrrr') 134 | assert rex.bark() == 'Grrrr' 135 | # a different dog will still 'Miau!' 136 | assert Dog().bark() == 'Miau!' 137 | 138 | # be sure to call unstub() once in while 139 | unstub() 140 | 141 | 142 | Sure, you can verify your interactions:: 143 | 144 | from mockito import verify 145 | # once again 146 | rex = Dog() 147 | when(rex).bark().thenReturn('Grrrr') 148 | 149 | rex.bark() 150 | rex.bark() 151 | 152 | # `times` defaults to 1 153 | verify(rex, times=2).bark() 154 | 155 | 156 | In general mockito is very picky:: 157 | 158 | # this will fail because `Dog` has no method named `waggle` 159 | when(rex).waggle().thenReturn('Nope') 160 | # this will fail because `bark` does not take any arguments 161 | when(rex).bark('Grrr').thenReturn('Nope') 162 | 163 | 164 | # given this function 165 | def bark(sound, post='!'): 166 | return sound + post 167 | 168 | from mockito import kwargs 169 | when(main).bark('Grrr', **kwargs).thenReturn('Nope') 170 | 171 | # now this one will fail 172 | bark('Grrr') # because there are no keyword arguments used 173 | # this one will fail because `then` does not match the function signature 174 | bark('Grrr', then='!!') 175 | # this one will go 176 | bark('Grrr', post='?') 177 | 178 | # there is also an args matcher 179 | def add_tasks(*tasks, verbose=False): 180 | pass 181 | 182 | from mockito import args 183 | # If you omit the `thenReturn` it will just return `None` 184 | when(main).add_tasks(*args) 185 | 186 | add_tasks('task1', 'task2') # will go 187 | add_tasks() # will fail 188 | add_tasks('task1', verbose=True) # will fail too 189 | 190 | # On Python 3 you can also use `...` 191 | when(main).add_tasks(...) 192 | # when(main).add_tasks(Ellipsis) on Python 2 193 | 194 | add_tasks('task1') # will go 195 | add_tasks(verbose=True) # will go 196 | add_tasks('task1', verbose=True) # will go 197 | add_tasks() # will go 198 | 199 | 200 | To start with an empty stub use :func:`mock`:: 201 | 202 | from mockito import mock 203 | 204 | obj = mock() 205 | 206 | # pass it around, eventually it will be used 207 | obj.say('Hi') 208 | 209 | # back in the tests, verify the interactions 210 | verify(obj).say('Hi') 211 | 212 | # by default all invoked methods take any arguments and return None 213 | # you can configure your expected method calls with the usual `when` 214 | when(obj).say('Hi').thenReturn('Ho') 215 | 216 | # There is also a shortcut to set some attributes 217 | obj = mock({ 218 | 'hi': 'ho' 219 | }) 220 | 221 | assert obj.hi == 'ho' 222 | 223 | # This would work for methods as well; in this case 224 | obj = mock({ 225 | 'say': lambda _: 'Ho' 226 | }) 227 | 228 | # But you don't have any argument and signature matching 229 | assert obj.say('Anything') == 'Ho' 230 | 231 | # At least you can verify your calls 232 | verify(obj).say(...) 233 | 234 | # Btw, you can make screaming strict mocks:: 235 | obj = mock(strict=True) # every unconfigured, unexpected call will raise 236 | 237 | 238 | You can use an empty stub specced against a concrete class:: 239 | 240 | # Given the above `Dog` 241 | rex = mock(Dog) 242 | 243 | # Now you can stub out any known method on `Dog` but other will throw 244 | when(rex).bark().thenReturn('Miau') 245 | # this one will fail 246 | when(rex).waggle() 247 | 248 | # These mocks are in general very strict, so even this will fail 249 | rex.health # unconfigured attribute 250 | 251 | # Of course you can just set it in a setup routine 252 | rex.health = 121 253 | 254 | # Or again preconfigure 255 | rex = mock({'health': 121}, spec=Dog) 256 | 257 | # preconfigure stubbed method 258 | rex = mock({'bark': lambda sound: 'Miau'}, spec=Dog) 259 | 260 | # as you specced the mock, you get at least function signature matching 261 | # `bark` does not take any arguments so 262 | rex.bark('sound') # will throw TypeError 263 | 264 | # Btw, you can make loose specced mocks:: 265 | rex = mock(Dog, strict=False) 266 | 267 | 268 | -------------------------------------------------------------------------------- /mockito/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008-2016 Szczepan Faber, Serhiy Oplakanets, Herr Kaste 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | '''Mockito is a Test Spy framework.''' 22 | 23 | 24 | from .mockito import ( 25 | when, 26 | when2, 27 | patch, 28 | expect, 29 | unstub, 30 | forget_invocations, 31 | verify, 32 | verifyNoMoreInteractions, 33 | verifyZeroInteractions, 34 | verifyNoUnwantedInteractions, 35 | verifyStubbedInvocationsAreUsed, 36 | ArgumentError, 37 | ) 38 | from . import inorder 39 | from .spying import spy, spy2 40 | from .mocking import mock 41 | from .verification import VerificationError 42 | 43 | from .matchers import * # noqa: F401 F403 44 | from .matchers import any, contains, times 45 | from .verification import never 46 | 47 | __version__ = '1.6.0-dev' 48 | 49 | __all__ = [ 50 | 'mock', 51 | 'spy', 52 | 'spy2', 53 | 'when', 54 | 'when2', 55 | 'patch', 56 | 'expect', 57 | 'verify', 58 | 'verifyNoMoreInteractions', 59 | 'verifyZeroInteractions', 60 | 'verifyNoUnwantedInteractions', 61 | 'verifyStubbedInvocationsAreUsed', 62 | 'inorder', 63 | 'unstub', 64 | 'forget_invocations', 65 | 'VerificationError', 66 | 'ArgumentError', 67 | 'any', # compatibility 68 | 'contains', # compatibility 69 | 'never', # compatibility 70 | 'times', # deprecated 71 | ] 72 | -------------------------------------------------------------------------------- /mockito/inorder.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008-2016 Szczepan Faber, Serhiy Oplakanets, Herr Kaste 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from .mockito import verify as verify_main 22 | 23 | 24 | def verify(object, *args, **kwargs): 25 | kwargs['inorder'] = True 26 | return verify_main(object, *args, **kwargs) 27 | 28 | -------------------------------------------------------------------------------- /mockito/matchers.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008-2016 Szczepan Faber, Serhiy Oplakanets, Herr Kaste 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | """Argument matchers for stubbing and verifications. 22 | 23 | In general the call signature you specify when stubbing or verifying in mockito 24 | is as concrete as possible: it consists of values only:: 25 | 26 | when(os.path).exists('/foo/bar.txt').thenReturn(True) 27 | 28 | This is for a reason. In controlled test environments, for the scope of a 29 | single test, you should usually know exactly how you use a function, and what 30 | you expect its outcome to be. In mockito usually (in `strict` mode) all 31 | invocations you did not specify upfront will throw at call time. 32 | 33 | If you reason about your code, the above `when` tirade turns - for the time 34 | of the test - the specific stubbed function into a constant. 35 | 36 | You can use so called argument matchers below if you can't or don't 37 | want to specify a single concrete value for an argument, but a type or class of 38 | possible values. E.g.:: 39 | 40 | when(os.path).exists(...).thenReturn(True) 41 | when(os.path).exists(ANY).thenReturn(True) 42 | when(os.path).exists(ANY(str)).thenReturn(True) 43 | 44 | when(requests).get(ANY(str), **kwargs) 45 | when(requests).get('https://example.com', ...) 46 | 47 | when(math).sqrt(not_(_or(ANY(float), ANY(int)))).thenRaise(TypeError) 48 | 49 | Now what you get each time is a function that up to a degree takes various 50 | arguments and responds with the same outcome each time. Now that's a weird 51 | thing. So use the matchers for a reason, they're powerful. 52 | 53 | The one usage you should not care about is a loose signature when using 54 | :func:`verify`. Since mockito will throw for unexpected calls, a very loose 55 | `verify` should be ok:: 56 | 57 | verify(requests, times=1).get(...) 58 | 59 | 60 | """ 61 | 62 | from abc import ABC, abstractmethod 63 | import re 64 | builtin_any = any 65 | 66 | __all__ = [ 67 | 'and_', 'or_', 'not_', 68 | 'eq', 'neq', 69 | 'lt', 'lte', 70 | 'gt', 'gte', 71 | 'any', 'any_', 'ANY', 72 | 'arg_that', 73 | 'contains', 74 | 'matches', 75 | 'captor', 76 | 'times', 77 | 'args', 'ARGS', 78 | 'kwargs', 'KWARGS' 79 | ] 80 | 81 | 82 | class _ArgsSentinel(object): 83 | def __repr__(self): 84 | return '*args' 85 | 86 | 87 | ARGS_SENTINEL = _ArgsSentinel() 88 | ARGS = args = [ARGS_SENTINEL] 89 | # ARGS.__doc__ = """Matches multiple positional arguments. 90 | 91 | # Note: `args` must match at least one argument. 92 | 93 | # Example:: 94 | 95 | # when(manager).add_tasks(1, 2, *args) 96 | 97 | # """ 98 | 99 | KWARGS_SENTINEL = '**' 100 | KWARGS = kwargs = {KWARGS_SENTINEL: '_'} 101 | # KWARGS.__doc__ = """Matches multiple keyword arguments. 102 | 103 | # Note that `kwargs` must match at least one remaining keyword argument. 104 | 105 | # Example:: 106 | 107 | # when(requests).get('http://myapi/', **KWARGS) 108 | 109 | # """ 110 | 111 | 112 | class MatcherError(RuntimeError): 113 | '''Indicates generic runtime error raised by mockito-python matchers 114 | ''' 115 | pass 116 | 117 | 118 | class Matcher: 119 | def matches(self, arg): 120 | pass 121 | 122 | class Capturing(ABC): 123 | @abstractmethod 124 | def capture_value(self, value): 125 | pass 126 | 127 | 128 | class Any(Matcher): 129 | def __init__(self, wanted_type=None): 130 | self.wanted_type = wanted_type 131 | 132 | def matches(self, arg): 133 | if self.wanted_type: 134 | return isinstance(arg, self.wanted_type) 135 | else: 136 | return True 137 | 138 | def __repr__(self): 139 | return "" % self.wanted_type 140 | 141 | 142 | class ValueMatcher(Matcher): 143 | def __init__(self, value): 144 | self.value = value 145 | 146 | def __repr__(self): 147 | return "<%s: %s>" % (self.__class__.__name__, self.value) 148 | 149 | 150 | class Eq(ValueMatcher): 151 | def matches(self, arg): 152 | return arg == self.value 153 | 154 | 155 | class Neq(ValueMatcher): 156 | def matches(self, arg): 157 | return arg != self.value 158 | 159 | 160 | class Lt(ValueMatcher): 161 | def matches(self, arg): 162 | return arg < self.value 163 | 164 | 165 | class Lte(ValueMatcher): 166 | def matches(self, arg): 167 | return arg <= self.value 168 | 169 | 170 | class Gt(ValueMatcher): 171 | def matches(self, arg): 172 | return arg > self.value 173 | 174 | 175 | class Gte(ValueMatcher): 176 | def matches(self, arg): 177 | return arg >= self.value 178 | 179 | 180 | class And(Matcher): 181 | def __init__(self, matchers): 182 | self.matchers = [ 183 | matcher if isinstance(matcher, Matcher) else Eq(matcher) 184 | for matcher in matchers] 185 | 186 | def matches(self, arg): 187 | return all(matcher.matches(arg) for matcher in self.matchers) 188 | 189 | def __repr__(self): 190 | return "" % self.matchers 191 | 192 | 193 | class Or(Matcher): 194 | def __init__(self, matchers): 195 | self.matchers = [ 196 | matcher if isinstance(matcher, Matcher) else Eq(matcher) 197 | for matcher in matchers] 198 | 199 | def matches(self, arg): 200 | return builtin_any([matcher.matches(arg) for matcher in self.matchers]) 201 | 202 | def __repr__(self): 203 | return "" % self.matchers 204 | 205 | 206 | class Not(Matcher): 207 | def __init__(self, matcher): 208 | self.matcher = matcher if isinstance(matcher, Matcher) else Eq(matcher) 209 | 210 | def matches(self, arg): 211 | return not self.matcher.matches(arg) 212 | 213 | def __repr__(self): 214 | return "" % self.matcher 215 | 216 | 217 | class ArgThat(Matcher): 218 | def __init__(self, predicate): 219 | self.predicate = predicate 220 | 221 | def matches(self, arg): 222 | return self.predicate(arg) 223 | 224 | def __repr__(self): 225 | return "" 226 | 227 | 228 | class Contains(Matcher): 229 | def __init__(self, sub): 230 | self.sub = sub 231 | 232 | def matches(self, arg): 233 | if not hasattr(arg, 'find'): 234 | return 235 | return self.sub and len(self.sub) > 0 and arg.find(self.sub) > -1 236 | 237 | def __repr__(self): 238 | return "" % self.sub 239 | 240 | 241 | class Matches(Matcher): 242 | def __init__(self, regex, flags=0): 243 | self.regex = re.compile(regex, flags) 244 | 245 | def matches(self, arg): 246 | if not isinstance(arg, str): 247 | return 248 | return self.regex.match(arg) is not None 249 | 250 | def __repr__(self): 251 | if self.regex.flags: 252 | return "" % (self.regex.pattern, 253 | self.regex.flags) 254 | else: 255 | return "" % self.regex.pattern 256 | 257 | 258 | class ArgumentCaptor(Matcher, Capturing): 259 | def __init__(self, matcher=None): 260 | self.matcher = matcher or Any() 261 | self.all_values = [] 262 | 263 | def matches(self, arg): 264 | result = self.matcher.matches(arg) 265 | if not result: 266 | return 267 | return True 268 | 269 | @property 270 | def value(self): 271 | if not self.all_values: 272 | raise MatcherError("No argument value was captured!") 273 | return self.all_values[-1] 274 | 275 | def capture_value(self, value): 276 | self.all_values.append(value) 277 | 278 | def __repr__(self): 279 | return "" % ( 280 | repr(self.matcher), self.all_values, 281 | ) 282 | 283 | 284 | def any(wanted_type=None): 285 | """Matches against type of argument (`isinstance`). 286 | 287 | If you want to match *any* type, use either `ANY` or `ANY()`. 288 | 289 | Examples:: 290 | 291 | when(mock).foo(any).thenReturn(1) 292 | verify(mock).foo(any(int)) 293 | 294 | """ 295 | return Any(wanted_type) 296 | 297 | 298 | ANY = any_ = any 299 | 300 | 301 | def eq(value): 302 | """Matches particular value (`==`)""" 303 | return Eq(value) 304 | 305 | 306 | def neq(value): 307 | """Matches any but given value (`!=`)""" 308 | return Neq(value) 309 | 310 | 311 | def lt(value): 312 | """Matches any value that is less than given value (`<`)""" 313 | return Lt(value) 314 | 315 | 316 | def lte(value): 317 | """Matches any value that is less than or equal to given value (`<=`)""" 318 | return Lte(value) 319 | 320 | 321 | def gt(value): 322 | """Matches any value that is greater than given value (`>`)""" 323 | return Gt(value) 324 | 325 | 326 | def gte(value): 327 | """Matches any value that is greater than or equal to given value (`>=`)""" 328 | return Gte(value) 329 | 330 | 331 | def and_(*matchers): 332 | """Matches if all given matchers match 333 | 334 | Example:: 335 | 336 | when(mock).foo(and_(ANY(str), contains('foo'))) 337 | 338 | """ 339 | return And(matchers) 340 | 341 | 342 | def or_(*matchers): 343 | """Matches if any given matcher match 344 | 345 | Example:: 346 | 347 | when(mock).foo(or_(ANY(int), ANY(float))) 348 | 349 | """ 350 | return Or(matchers) 351 | 352 | 353 | def not_(matcher): 354 | """Matches if given matcher does not match 355 | 356 | Example:: 357 | 358 | when(mock).foo(not_(ANY(str))).thenRaise(TypeError) 359 | 360 | """ 361 | return Not(matcher) 362 | 363 | 364 | def arg_that(predicate): 365 | """Matches any argument for which predicate returns True 366 | 367 | Example:: 368 | 369 | verify(mock).foo(arg_that(lambda arg: arg > 3 and arg < 7)) 370 | 371 | """ 372 | return ArgThat(predicate) 373 | 374 | 375 | def contains(sub): 376 | """Matches any string containing given substring 377 | 378 | Example:: 379 | 380 | mock.foo([120, 121, 122, 123]) 381 | verify(mock).foo(contains(123)) 382 | 383 | """ 384 | return Contains(sub) 385 | 386 | 387 | def matches(regex, flags=0): 388 | """Matches any string that matches given regex""" 389 | return Matches(regex, flags) 390 | 391 | 392 | def captor(matcher=None): 393 | """Returns argument captor that captures values for further assertions 394 | 395 | Example:: 396 | 397 | arg = captor() 398 | mock.do_something(123) 399 | mock.do_something(456) 400 | verify(mock).do_something(arg) 401 | assert arg.value == 456 402 | assert arg.all_values == [123, 456] 403 | 404 | You can restrict what the captor captures using the other matchers 405 | shown herein:: 406 | 407 | arg = captor(any(str)) 408 | arg = captor(contains("foo")) 409 | 410 | """ 411 | return ArgumentCaptor(matcher) 412 | 413 | 414 | def times(count): 415 | return count 416 | -------------------------------------------------------------------------------- /mockito/mock_registry.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008-2016 Szczepan Faber, Serhiy Oplakanets, Herr Kaste 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | 22 | 23 | class MockRegistry: 24 | """Registry for mocks 25 | 26 | Registers mock()s, ensures that we only have one mock() per mocked_obj, and 27 | iterates over them to unstub each stubbed method. 28 | """ 29 | 30 | def __init__(self): 31 | self.mocks = IdentityMap() 32 | 33 | def register(self, obj, mock): 34 | self.mocks[obj] = mock 35 | 36 | def mock_for(self, obj): 37 | return self.mocks.get(obj, None) 38 | 39 | def unstub(self, obj): 40 | try: 41 | mock = self.mocks.pop(obj) 42 | except KeyError: 43 | pass 44 | else: 45 | mock.unstub() 46 | 47 | def unstub_all(self): 48 | for mock in self.get_registered_mocks(): 49 | mock.unstub() 50 | self.mocks.clear() 51 | 52 | def get_registered_mocks(self): 53 | return self.mocks.values() 54 | 55 | 56 | # We have this dict like because we want non-hashable items in our registry. 57 | class IdentityMap(object): 58 | def __init__(self): 59 | self._store = [] 60 | 61 | def __setitem__(self, key, value): 62 | self.remove(key) 63 | self._store.append((key, value)) 64 | 65 | def remove(self, key): 66 | self._store = [(k, v) for k, v in self._store if k is not key] 67 | 68 | def pop(self, key): 69 | rv = self.get(key) 70 | if rv is not None: 71 | self.remove(key) 72 | return rv 73 | else: 74 | raise KeyError() 75 | 76 | def get(self, key, default=None): 77 | for k, value in self._store: 78 | if k is key: 79 | return value 80 | return default 81 | 82 | def values(self): 83 | return [v for k, v in self._store] 84 | 85 | def clear(self): 86 | self._store[:] = [] 87 | 88 | 89 | mock_registry = MockRegistry() 90 | -------------------------------------------------------------------------------- /mockito/signature.py: -------------------------------------------------------------------------------- 1 | 2 | from . import matchers 3 | from .utils import contains_strict 4 | 5 | import functools 6 | import inspect 7 | 8 | try: 9 | from inspect import signature, Parameter 10 | except ImportError: 11 | from funcsigs import signature, Parameter # type: ignore[import, no-redef] 12 | 13 | 14 | 15 | def get_signature(obj, method_name): 16 | method = getattr(obj, method_name) 17 | 18 | # Eat self for unbound methods bc signature doesn't do it 19 | if ( 20 | inspect.isclass(obj) 21 | and not inspect.ismethod(method) 22 | and not isinstance(obj.__dict__.get(method_name), staticmethod) 23 | ): 24 | method = functools.partial(method, None) 25 | 26 | try: 27 | return signature(method) 28 | except Exception: 29 | return None 30 | 31 | 32 | def match_signature(sig, args, kwargs): 33 | sig.bind(*args, **kwargs) 34 | return sig 35 | 36 | 37 | def match_signature_allowing_placeholders(sig, args, kwargs): # noqa: C901 38 | # Let's face it. If this doesn't work out, we have to do it the hard 39 | # way and reimplement something like `sig.bind` with our specific 40 | # need for `...`, `*args`, and `**kwargs` support. 41 | 42 | if contains_strict(args, Ellipsis): 43 | # Invariant: Ellipsis as the sole argument should just pass, regardless 44 | # if it actually can consume an arg or the function does not take any 45 | # arguments at all 46 | if len(args) == 1: 47 | return 48 | 49 | has_kwargs = has_var_keyword(sig) 50 | # Ellipsis is always the last arg in args; it matches all keyword 51 | # arguments as well. So the strategy here is to strip off all 52 | # the keyword arguments from the signature, and do a partial 53 | # bind with the rest. 54 | params = [p for n, p in sig.parameters.items() 55 | if p.kind not in (Parameter.KEYWORD_ONLY, 56 | Parameter.VAR_KEYWORD)] 57 | sig = sig.replace(parameters=params) 58 | # Ellipsis should fill at least one argument. We strip it off if 59 | # it can stand for a `kwargs` argument. 60 | sig.bind_partial(*(args[:-1] if has_kwargs else args)) 61 | else: 62 | # `*args` should at least match one arg (t.i. not `*[]`), so we 63 | # keep it here. The value and its type is irrelevant in python. 64 | args_provided = contains_strict(args, matchers.ARGS_SENTINEL) 65 | 66 | # If we find the `**kwargs` sentinel we must remove it, bc its 67 | # name cannot be matched against the sig. 68 | kwargs_provided = matchers.KWARGS_SENTINEL in kwargs 69 | if kwargs_provided: 70 | kwargs = kwargs.copy() 71 | kwargs.pop(matchers.KWARGS_SENTINEL) 72 | 73 | 74 | if args_provided or kwargs_provided: 75 | try: 76 | sig.bind(*args, **kwargs) 77 | except TypeError as e: 78 | error = str(e) 79 | if 'too many positional arguments' in error: 80 | raise TypeError('no argument for *args left') 81 | if 'multiple values for argument' in error: 82 | raise 83 | if 'too many keyword arguments' in error: # PY<3.5 84 | raise 85 | if 'got an unexpected keyword argument' in error: # PY>3.5 86 | raise 87 | 88 | else: 89 | if kwargs_provided and not has_var_keyword(sig): 90 | pos_args = positional_arguments(sig) 91 | len_args = len(args) - int(args_provided) 92 | len_kwargs = len(kwargs) 93 | provided_args = len_args + len_kwargs 94 | # Substitute at least one argument for the `**kwargs`, 95 | # the user provided; t.i. do not allow kwargs to 96 | # satisfy an empty `{}`. 97 | if provided_args + 1 > pos_args: 98 | raise TypeError( 99 | 'no keyword argument for **kwargs left') 100 | 101 | else: 102 | # Without Ellipsis and the other stuff this would really be 103 | # straight forward. 104 | sig.bind(*args, **kwargs) 105 | 106 | return sig 107 | 108 | 109 | def positional_arguments(sig): 110 | return len([p for n, p in sig.parameters.items() 111 | if p.kind in (Parameter.POSITIONAL_ONLY, 112 | Parameter.POSITIONAL_OR_KEYWORD)]) 113 | 114 | def has_var_keyword(sig): 115 | return any(p for n, p in sig.parameters.items() 116 | if p.kind is Parameter.VAR_KEYWORD) 117 | 118 | -------------------------------------------------------------------------------- /mockito/spying.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008-2016 Szczepan Faber, Serhiy Oplakanets, Herr Kaste 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | '''Spying on real objects.''' 22 | 23 | import inspect 24 | 25 | from .mockito import when2 26 | from .invocation import RememberedProxyInvocation 27 | from .mocking import Mock, _Dummy, mock_registry 28 | 29 | __all__ = ['spy'] 30 | 31 | 32 | def spy(object): 33 | """Spy an object. 34 | 35 | Spying means that all functions will behave as before, so they will 36 | be side effects, but the interactions can be verified afterwards. 37 | 38 | Returns Dummy-like, almost empty object as proxy to `object`. 39 | 40 | The *returned* object must be injected and used by the code under test; 41 | after that all interactions can be verified as usual. 42 | T.i. the original object **will not be patched**, and has no further 43 | knowledge as before. 44 | 45 | E.g.:: 46 | 47 | import time 48 | time = spy(time) 49 | # inject time 50 | do_work(..., time) 51 | verify(time).time() 52 | 53 | """ 54 | if inspect.isclass(object) or inspect.ismodule(object): 55 | class_ = None 56 | else: 57 | class_ = object.__class__ 58 | 59 | class Spy(_Dummy): 60 | if class_: 61 | __class__ = class_ 62 | 63 | def __getattr__(self, method_name): 64 | return RememberedProxyInvocation(theMock, method_name) 65 | 66 | def __repr__(self): 67 | name = 'Spied' 68 | if class_: 69 | name += class_.__name__ 70 | return "<%s id=%s>" % (name, id(self)) 71 | 72 | 73 | obj = Spy() 74 | theMock = Mock(obj, strict=True, spec=object) 75 | 76 | mock_registry.register(obj, theMock) 77 | return obj 78 | 79 | 80 | def spy2(fn) -> None: 81 | """Spy usage of given `fn`. 82 | 83 | Patches the module, class or object `fn` lives in, so that all 84 | interactions can be recorded; otherwise executes `fn` as before, so 85 | that all side effects happen as before. 86 | 87 | E.g.:: 88 | 89 | import time 90 | spy2(time.time) 91 | do_work(...) # nothing injected, uses global patched `time` module 92 | verify(time).time() 93 | 94 | Note that builtins often cannot be patched because they're read-only. 95 | 96 | 97 | """ 98 | when2(fn, Ellipsis).thenCallOriginalImplementation() 99 | -------------------------------------------------------------------------------- /mockito/utils.py: -------------------------------------------------------------------------------- 1 | 2 | import importlib 3 | import inspect 4 | import sys 5 | import types 6 | import re 7 | 8 | 9 | NEEDS_OS_PATH_HACK = ( 10 | sys.platform == "win32" and sys.version_info >= (3, 12) 11 | ) 12 | 13 | 14 | def contains_strict(seq, element): 15 | return any(item is element for item in seq) 16 | 17 | 18 | def newmethod(fn, obj): 19 | return types.MethodType(fn, obj) 20 | 21 | 22 | def get_function_host(fn): 23 | """Destructure a given function into its host and its name. 24 | 25 | The 'host' of a function is a module, for methods it is usually its 26 | instance or its class. This is safe only for methods, for module wide, 27 | globally declared names it must be considered experimental. 28 | 29 | For all reasonable fn: ``getattr(*get_function_host(fn)) == fn`` 30 | 31 | Returns tuple (host, fn-name) 32 | Otherwise should raise TypeError 33 | """ 34 | 35 | obj = None 36 | try: 37 | name = fn.__name__ 38 | obj = fn.__self__ 39 | if ( 40 | NEEDS_OS_PATH_HACK 41 | and obj.__name__ == "nt" 42 | and name.startswith('_path_') 43 | ): 44 | obj = None 45 | except AttributeError: 46 | pass 47 | 48 | if obj is None: 49 | # Due to how python imports work, everything that is global on a module 50 | # level must be regarded as not safe here. For now, we go for the extra 51 | # mile, TBC, because just specifying `os.path.exists` would be 'cool'. 52 | # 53 | # TLDR;: 54 | # E.g. `inspect.getmodule(os.path.exists)` returns `genericpath` bc 55 | # that's where `exists` is defined and comes from. But from the point 56 | # of view of the user `exists` always comes and is used from `os.path` 57 | # which points e.g. to `ntpath`. We thus must patch `ntpath`. 58 | # But that's the same for most imports:: 59 | # 60 | # # b.py 61 | # from a import foo 62 | # 63 | # Now asking `getmodule(b.foo)` it tells you `a`, but we access and use 64 | # `b.foo` and we therefore must patch `b`. 65 | 66 | obj, name = find_invoking_frame_and_try_parse() 67 | # safety check! 68 | assert getattr(obj, name) == fn, ( 69 | "`getattr(obj, name) != fn` where\n" 70 | "fn: %s\n" 71 | "obj: %s\n" 72 | "name: %s\n" 73 | "getattr(obj, name): %s\n" 74 | % (fn, obj, name, getattr(obj, name)) 75 | ) 76 | 77 | return obj, name 78 | 79 | 80 | FIND_ID = re.compile(r'.*\s*.*(?:when2|patch|spy2)\(\s*(.+?)[,\)]', re.M) 81 | 82 | 83 | def find_invoking_frame_and_try_parse(): 84 | # Actually we just want the first frame in user land; we're open for 85 | # refactorings here and don't yet decide on which frame exactly we hit 86 | # that user land. 87 | stack = inspect.stack(3)[2:10] 88 | for frame_info in stack: 89 | # Within `patch` and `spy2` we delegate to `when2` but that's not 90 | # user land code 91 | if frame_info[3] in ('patch', 'spy2'): 92 | continue 93 | 94 | source = ''.join(frame_info[4] or []) 95 | m = FIND_ID.match(source) 96 | if m: 97 | # id should be something like `os.path.exists` etc. 98 | id = m.group(1) 99 | parts = id.split('.') 100 | if len(parts) < 2: 101 | raise TypeError("can't guess origin of '%s'" % id) 102 | 103 | frame = frame_info[0] 104 | vars = frame.f_globals.copy() 105 | vars.update(frame.f_locals) 106 | 107 | # Now that's a simple reduce; we get the initial value from the 108 | # locally available `vars`, and then reduce the middle parts via 109 | # `getattr`. The last path component gets not resolved, but is 110 | # returned as plain string value. 111 | obj = vars.get(parts[0]) 112 | for part in parts[1:-1]: 113 | obj = getattr(obj, part) 114 | return obj, parts[-1] 115 | 116 | raise TypeError('could not destructure first argument') 117 | 118 | def get_obj(path): 119 | """Return obj for given dotted path. 120 | 121 | Typical inputs for `path` are 'os' or 'os.path' in which case you get a 122 | module; or 'os.path.exists' in which case you get a function from that 123 | module. 124 | 125 | Just returns the given input in case it is not a str. 126 | 127 | Note: Relative imports not supported. 128 | Raises ImportError or AttributeError as appropriate. 129 | 130 | """ 131 | # Since we usually pass in mocks here; duck typing is not appropriate 132 | # (mocks respond to every attribute). 133 | if not isinstance(path, str): 134 | return path 135 | 136 | if path.startswith('.'): 137 | raise TypeError('relative imports are not supported') 138 | 139 | parts = path.split('.') 140 | head, tail = parts[0], parts[1:] 141 | 142 | obj = importlib.import_module(head) 143 | 144 | # Normally a simple reduce, but we go the extra mile 145 | # for good exception messages. 146 | for i, name in enumerate(tail): 147 | try: 148 | obj = getattr(obj, name) 149 | except AttributeError: 150 | # Note the [:i] instead of [:i+1], so we get the path just 151 | # *before* the AttributeError, t.i. the part of it that went ok. 152 | module = '.'.join([head] + tail[:i]) 153 | try: 154 | importlib.import_module(module) 155 | except ImportError: 156 | raise AttributeError( 157 | "object '%s' has no attribute '%s'" % (module, name)) 158 | else: 159 | raise AttributeError( 160 | "module '%s' has no attribute '%s'" % (module, name)) 161 | return obj 162 | 163 | def get_obj_attr_tuple(path): 164 | """Split path into (obj, attribute) tuple. 165 | 166 | Given `path` is 'os.path.exists' will thus return `(os.path, 'exists')` 167 | 168 | If path is not a str, delegates to `get_function_host(path)` 169 | 170 | """ 171 | if not isinstance(path, str): 172 | return get_function_host(path) 173 | 174 | if path.startswith('.'): 175 | raise TypeError('relative imports are not supported') 176 | 177 | try: 178 | leading, end = path.rsplit('.', 1) 179 | except ValueError: 180 | raise TypeError('path must have dots') 181 | 182 | return get_obj(leading), end 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /mockito/verification.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008-2016 Szczepan Faber, Serhiy Oplakanets, Herr Kaste 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | import operator 22 | 23 | __all__ = ['never', 'VerificationError'] 24 | 25 | 26 | class VerificationError(AssertionError): 27 | '''Indicates error during verification of invocations. 28 | 29 | Raised if verification fails. Error message contains the cause. 30 | ''' 31 | pass 32 | 33 | 34 | __tracebackhide__ = operator.methodcaller("errisinstance", VerificationError) 35 | 36 | 37 | class AtLeast(object): 38 | def __init__(self, wanted_count): 39 | self.wanted_count = wanted_count 40 | 41 | def verify(self, invocation, actual_count): 42 | if actual_count < self.wanted_count: 43 | raise VerificationError("\nWanted at least: %i, actual times: %i" 44 | % (self.wanted_count, actual_count)) 45 | 46 | def __repr__(self): 47 | return "<%s wanted=%s>" % (type(self).__name__, self.wanted_count) 48 | 49 | class AtMost(object): 50 | def __init__(self, wanted_count): 51 | self.wanted_count = wanted_count 52 | 53 | def verify(self, invocation, actual_count): 54 | if actual_count > self.wanted_count: 55 | raise VerificationError("\nWanted at most: %i, actual times: %i" 56 | % (self.wanted_count, actual_count)) 57 | 58 | def __repr__(self): 59 | return "<%s wanted=%s>" % (type(self).__name__, self.wanted_count) 60 | 61 | class Between(object): 62 | def __init__(self, wanted_from, wanted_to): 63 | self.wanted_from = wanted_from 64 | self.wanted_to = wanted_to 65 | 66 | def verify(self, invocation, actual_count): 67 | if actual_count < self.wanted_from or actual_count > self.wanted_to: 68 | raise VerificationError( 69 | "\nWanted between: [%i, %i], actual times: %i" 70 | % (self.wanted_from, self.wanted_to, actual_count)) 71 | 72 | def __repr__(self): 73 | return "<%s [%s, %s]>" % ( 74 | type(self).__name__, self.wanted_from, self.wanted_to) 75 | 76 | class Times(object): 77 | def __init__(self, wanted_count): 78 | self.wanted_count = wanted_count 79 | 80 | def verify(self, invocation, actual_count): 81 | if actual_count == self.wanted_count: 82 | return 83 | 84 | if actual_count == 0: 85 | invocations = ( 86 | [ 87 | invoc 88 | for invoc in invocation.mock.invocations 89 | if invoc.method_name == invocation.method_name 90 | ] 91 | or invocation.mock.invocations 92 | ) 93 | wanted_section = ( 94 | "\nWanted but not invoked:\n\n %s\n" % invocation 95 | ) 96 | instead_section = ( 97 | "\nInstead got:\n\n %s\n" 98 | % "\n ".join(map(str, invocations)) 99 | ) if invocations else "" 100 | 101 | raise VerificationError( 102 | "%s%s\n" % (wanted_section, instead_section)) 103 | 104 | else: 105 | if self.wanted_count == 0: 106 | raise VerificationError( 107 | "\nUnwanted invocation of %s, times: %i" 108 | % (invocation, actual_count)) 109 | else: 110 | raise VerificationError("\nWanted times: %i, actual times: %i" 111 | % (self.wanted_count, actual_count)) 112 | 113 | def __repr__(self): 114 | return "<%s wanted=%s>" % (type(self).__name__, self.wanted_count) 115 | 116 | class InOrder(object): 117 | '''Verifies invocations in order. 118 | 119 | Verifies if invocation was in expected order, and if yes -- degrades to 120 | original Verifier (AtLeast, Times, Between, ...). 121 | ''' 122 | 123 | def __init__(self, original_verification): 124 | ''' 125 | 126 | @param original_verification: Original verification to degrade to if 127 | order of invocation was ok. 128 | ''' 129 | self.original_verification = original_verification 130 | 131 | def verify(self, wanted_invocation, count): 132 | for invocation in wanted_invocation.mock.invocations: 133 | if not invocation.verified_inorder: 134 | if not wanted_invocation.matches(invocation): 135 | raise VerificationError( 136 | '\nWanted %s to be invoked,' 137 | '\ngot %s instead.' % 138 | (wanted_invocation, invocation)) 139 | invocation.verified_inorder = True 140 | break 141 | # proceed with original verification 142 | self.original_verification.verify(wanted_invocation, count) 143 | 144 | 145 | never = 0 146 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "mockito" 3 | version = "1.6.0-dev" 4 | description = "Spying framework" 5 | authors = [ 6 | { name = "herr kaste", email = "herr.kaste@gmail.com" } 7 | ] 8 | dependencies = [] 9 | readme = "README.rst" 10 | requires-python = ">=3.7" 11 | license = { text = "MIT" } 12 | 13 | [build-system] 14 | requires = ["hatchling"] 15 | build-backend = "hatchling.build" 16 | 17 | [tool.rye] 18 | managed = true 19 | dev-dependencies = [ 20 | "pytest>=4.6.11", 21 | "numpy>=2.1.3", 22 | "bump2version>=0.5.11", 23 | "Sphinx>=5.3.0", 24 | "sphinx-autobuild>=2021.3.14", 25 | ] 26 | 27 | [tool.hatch.metadata] 28 | allow-direct-references = true 29 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | xfail_strict=true 3 | -------------------------------------------------------------------------------- /requirements-dev.lock: -------------------------------------------------------------------------------- 1 | # generated by rye 2 | # use `rye lock` or `rye sync` to update this lockfile 3 | # 4 | # last locked with the following flags: 5 | # pre: false 6 | # features: [] 7 | # all-features: false 8 | # with-sources: false 9 | # generate-hashes: false 10 | # universal: false 11 | 12 | -e file:. 13 | alabaster==0.7.13 14 | # via sphinx 15 | babel==2.13.1 16 | # via sphinx 17 | bump2version==1.0.1 18 | certifi==2023.7.22 19 | # via requests 20 | charset-normalizer==3.3.2 21 | # via requests 22 | colorama==0.4.6 23 | # via pytest 24 | # via sphinx 25 | # via sphinx-autobuild 26 | docutils==0.20.1 27 | # via sphinx 28 | idna==3.4 29 | # via requests 30 | imagesize==1.4.1 31 | # via sphinx 32 | iniconfig==2.0.0 33 | # via pytest 34 | jinja2==3.1.2 35 | # via sphinx 36 | livereload==2.6.3 37 | # via sphinx-autobuild 38 | markupsafe==2.1.3 39 | # via jinja2 40 | numpy==2.1.3 41 | packaging==23.2 42 | # via pytest 43 | # via sphinx 44 | pluggy==1.3.0 45 | # via pytest 46 | pygments==2.16.1 47 | # via sphinx 48 | pytest==7.4.3 49 | requests==2.31.0 50 | # via sphinx 51 | setuptools==69.1.1 52 | # via babel 53 | six==1.16.0 54 | # via livereload 55 | snowballstemmer==2.2.0 56 | # via sphinx 57 | sphinx==7.2.6 58 | # via sphinx-autobuild 59 | # via sphinxcontrib-applehelp 60 | # via sphinxcontrib-devhelp 61 | # via sphinxcontrib-htmlhelp 62 | # via sphinxcontrib-qthelp 63 | # via sphinxcontrib-serializinghtml 64 | sphinx-autobuild==2021.3.14 65 | sphinxcontrib-applehelp==1.0.7 66 | # via sphinx 67 | sphinxcontrib-devhelp==1.0.5 68 | # via sphinx 69 | sphinxcontrib-htmlhelp==2.0.4 70 | # via sphinx 71 | sphinxcontrib-jsmath==1.0.1 72 | # via sphinx 73 | sphinxcontrib-qthelp==1.0.6 74 | # via sphinx 75 | sphinxcontrib-serializinghtml==1.1.9 76 | # via sphinx 77 | tornado==6.3.3 78 | # via livereload 79 | urllib3==2.1.0 80 | # via requests 81 | -------------------------------------------------------------------------------- /requirements.lock: -------------------------------------------------------------------------------- 1 | # generated by rye 2 | # use `rye lock` or `rye sync` to update this lockfile 3 | # 4 | # last locked with the following flags: 5 | # pre: false 6 | # features: [] 7 | # all-features: false 8 | # with-sources: false 9 | # generate-hashes: false 10 | # universal: false 11 | 12 | -e file:. 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | import re 4 | import ast 5 | 6 | 7 | _version_re = re.compile(r'__version__\s+=\s+(.*)') 8 | 9 | with open('mockito/__init__.py', 'rb') as f: 10 | version = str(ast.literal_eval(_version_re.search( 11 | f.read().decode('utf-8')).group(1))) 12 | 13 | 14 | setup(name='mockito', 15 | version=version, 16 | packages=['mockito'], 17 | url='https://github.com/kaste/mockito-python', 18 | maintainer='herr.kaste', 19 | maintainer_email='herr.kaste@gmail.com', 20 | license='MIT', 21 | description='Spying framework', 22 | long_description=open('README.rst').read(), 23 | python_requires='>=3.7', 24 | classifiers=[ 25 | 'Development Status :: 5 - Production/Stable', 26 | 'Intended Audience :: Developers', 27 | 'License :: OSI Approved :: MIT License', 28 | 'Topic :: Software Development :: Testing', 29 | 'Programming Language :: Python :: 3', 30 | 'Programming Language :: Python :: 3.7', 31 | 'Programming Language :: Python :: 3.8', 32 | 'Programming Language :: Python :: 3.9', 33 | 'Programming Language :: Python :: 3.10', 34 | 'Programming Language :: Python :: 3.11', 35 | 'Programming Language :: Python :: 3.12', 36 | ]) 37 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaste/mockito-python/98d333e301c046ff672bafe191ae194306120946/tests/__init__.py -------------------------------------------------------------------------------- /tests/call_original_implem_test.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | 4 | import pytest 5 | from mockito import mock, when 6 | from mockito.invocation import AnswerError 7 | 8 | from . import module 9 | from .test_base import TestBase 10 | 11 | 12 | class Dog: 13 | def __init__(self, huge=False): 14 | self.huge = huge 15 | 16 | def bark(self): 17 | if self.huge: 18 | return "woof" 19 | else: 20 | return "waf ! waf ! waf ! waf ! waf ! waf !" 21 | 22 | @classmethod 23 | def class_bark(cls): 24 | return cls.__name__ + " woof" 25 | 26 | @staticmethod 27 | def static_bark(arg): 28 | return str(arg) + " woof" 29 | 30 | 31 | class CallOriginalImplementationTest(TestBase): 32 | 33 | def testClassMethod(self): 34 | when(Dog).class_bark().thenCallOriginalImplementation() 35 | 36 | self.assertEqual("Dog woof", Dog.class_bark()) 37 | 38 | def testStaticMethod(self): 39 | when(Dog).static_bark("wif").thenCallOriginalImplementation() 40 | self.assertEqual("wif woof", Dog.static_bark("wif")) 41 | 42 | def testStaticMethodOnInstance(self): 43 | dog = Dog() 44 | when(Dog).static_bark("wif").thenCallOriginalImplementation() 45 | self.assertEqual("wif woof", dog.static_bark("wif")) 46 | 47 | def testMethod(self): 48 | when(Dog).bark().thenCallOriginalImplementation() 49 | 50 | assert Dog(huge=True).bark() == "woof" 51 | 52 | def testMethodOnInstance(self): 53 | dog = Dog(huge=True) 54 | when(dog).bark().thenCallOriginalImplementation() 55 | 56 | assert dog.bark() == "woof" 57 | 58 | def testFunction(self): 59 | when(module).one_arg(Ellipsis).thenCallOriginalImplementation() 60 | assert module.one_arg("woof") == "woof" 61 | 62 | def testChain(self): 63 | when(module).one_arg(Ellipsis) \ 64 | .thenReturn("wif") \ 65 | .thenCallOriginalImplementation() \ 66 | .thenReturn("waf") 67 | assert module.one_arg("woof") == "wif" 68 | assert module.one_arg("woof") == "woof" 69 | assert module.one_arg("woof") == "waf" 70 | 71 | def testDumbMockHasNoOriginalImplementations(self): 72 | dog = mock() 73 | answer_selector = when(dog).bark() 74 | with pytest.raises(AnswerError) as exc: 75 | answer_selector.thenCallOriginalImplementation() 76 | 77 | if sys.version_info >= (3, 0): 78 | class_str_value = "mockito.mocking.mock..Dummy" 79 | else: 80 | class_str_value = "mockito.mocking.Dummy" 81 | assert str(exc.value) == ( 82 | "'' " 83 | "has no original implementation for 'bark'." 84 | ) % class_str_value 85 | 86 | def testSpeccedMockHasOriginalImplementations(self): 87 | dog = mock({"huge": True}, spec=Dog) 88 | when(dog).bark().thenCallOriginalImplementation() 89 | assert dog.bark() == "woof" 90 | -------------------------------------------------------------------------------- /tests/classmethods_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008-2016 Szczepan Faber, Serhiy Oplakanets, Herr Kaste 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from mockito import unstub, verify, when 22 | from mockito.verification import VerificationError 23 | 24 | from .test_base import TestBase 25 | 26 | 27 | class Dog: 28 | @classmethod 29 | def bark(cls): 30 | return "woof!" 31 | 32 | 33 | class Cat: 34 | @classmethod 35 | def meow(cls, m): 36 | return cls.__name__ + " " + str(m) 37 | 38 | 39 | class Lion(object): 40 | @classmethod 41 | def roar(cls): 42 | return "Rrrrr!" 43 | 44 | 45 | class ClassMethodsTest(TestBase): 46 | 47 | def tearDown(self): 48 | unstub() 49 | 50 | def testUnstubs(self): 51 | when(Dog).bark().thenReturn("miau!") 52 | unstub() 53 | self.assertEqual("woof!", Dog.bark()) 54 | 55 | # TODO decent test case please :) without testing irrelevant implementation 56 | # details 57 | def testUnstubShouldPreserveMethodType(self): 58 | when(Dog).bark().thenReturn("miau!") 59 | unstub() 60 | self.assertTrue(isinstance(Dog.__dict__.get("bark"), classmethod)) 61 | 62 | def testStubs(self): 63 | self.assertEqual("woof!", Dog.bark()) 64 | 65 | when(Dog).bark().thenReturn("miau!") 66 | 67 | self.assertEqual("miau!", Dog.bark()) 68 | 69 | def testStubsClassesDerivedFromTheObjectClass(self): 70 | self.assertEqual("Rrrrr!", Lion.roar()) 71 | 72 | when(Lion).roar().thenReturn("miau!") 73 | 74 | self.assertEqual("miau!", Lion.roar()) 75 | 76 | def testVerifiesMultipleCallsOnClassmethod(self): 77 | when(Dog).bark().thenReturn("miau!") 78 | 79 | Dog.bark() 80 | Dog.bark() 81 | 82 | verify(Dog, times=2).bark() 83 | 84 | def testFailsVerificationOfMultipleCallsOnClassmethod(self): 85 | when(Dog).bark().thenReturn("miau!") 86 | 87 | Dog.bark() 88 | 89 | self.assertRaises(VerificationError, verify(Dog, times=2).bark) 90 | 91 | def testStubsAndVerifiesClassmethod(self): 92 | when(Dog).bark().thenReturn("miau!") 93 | 94 | self.assertEqual("miau!", Dog.bark()) 95 | 96 | verify(Dog).bark() 97 | 98 | def testPreservesClassArgumentAfterUnstub(self): 99 | self.assertEqual("Cat foo", Cat.meow("foo")) 100 | 101 | when(Cat).meow("foo").thenReturn("bar") 102 | 103 | self.assertEqual("bar", Cat.meow("foo")) 104 | 105 | unstub() 106 | 107 | self.assertEqual("Cat foo", Cat.meow("foo")) 108 | 109 | 110 | class Retriever: 111 | @classmethod 112 | def retrieve(cls, item): 113 | return item 114 | 115 | 116 | class TrickDog(Dog, Retriever): 117 | pass 118 | 119 | 120 | class InheritedClassMethodsTest(TestBase): 121 | 122 | def tearDown(self): 123 | unstub() 124 | 125 | def testUnstubs(self): 126 | when(TrickDog).bark().thenReturn("miau!") 127 | when(TrickDog).retrieve("stick").thenReturn("ball") 128 | unstub() 129 | self.assertEqual("woof!", TrickDog.bark()) 130 | self.assertEqual("stick", TrickDog.retrieve("stick")) 131 | 132 | def testStubs(self): 133 | self.assertEqual("woof!", TrickDog.bark()) 134 | self.assertEqual("stick", TrickDog.retrieve("stick")) 135 | 136 | when(TrickDog).bark().thenReturn("miau!") 137 | when(TrickDog).retrieve("stick").thenReturn("ball") 138 | 139 | self.assertEqual("miau!", TrickDog.bark()) 140 | self.assertEqual("ball", TrickDog.retrieve("stick")) 141 | 142 | def testVerifiesMultipleCallsOnClassmethod(self): 143 | when(TrickDog).bark().thenReturn("miau!") 144 | when(TrickDog).retrieve("stick").thenReturn("ball") 145 | 146 | TrickDog.bark() 147 | TrickDog.bark() 148 | 149 | TrickDog.retrieve("stick") 150 | TrickDog.retrieve("stick") 151 | 152 | verify(TrickDog, times=2).bark() 153 | verify(TrickDog, times=2).retrieve("stick") 154 | 155 | def testFailsVerificationOfMultipleCallsOnClassmethod(self): 156 | when(TrickDog).bark().thenReturn("miau!") 157 | when(TrickDog).retrieve("stick").thenReturn("bark") 158 | 159 | TrickDog.bark() 160 | TrickDog.retrieve("stick") 161 | 162 | self.assertRaises(VerificationError, verify(TrickDog, times=2).bark) 163 | self.assertRaises(VerificationError, verify(TrickDog, 164 | times=2).retrieve) 165 | 166 | def testStubsAndVerifiesClassmethod(self): 167 | when(TrickDog).bark().thenReturn("miau!") 168 | when(TrickDog).retrieve("stick").thenReturn("ball") 169 | 170 | self.assertEqual("miau!", TrickDog.bark()) 171 | self.assertEqual("ball", TrickDog.retrieve("stick")) 172 | 173 | verify(TrickDog).bark() 174 | verify(TrickDog).retrieve("stick") 175 | 176 | def testPreservesSuperClassClassMethodWhenStubbed(self): 177 | self.assertEqual("woof!", Dog.bark()) 178 | self.assertEqual("stick", Retriever.retrieve("stick")) 179 | 180 | self.assertEqual("woof!", TrickDog.bark()) 181 | self.assertEqual("stick", TrickDog.retrieve("stick")) 182 | 183 | when(TrickDog).bark().thenReturn("miau!") 184 | when(TrickDog).retrieve("stick").thenReturn("ball") 185 | 186 | self.assertEqual("miau!", TrickDog.bark()) 187 | self.assertEqual("ball", TrickDog.retrieve("stick")) 188 | 189 | self.assertEqual("woof!", Dog.bark()) 190 | self.assertEqual("stick", Retriever.retrieve("stick")) 191 | 192 | def testDoubleStubStubWorksAfterUnstub(self): 193 | when(TrickDog).retrieve("stick").thenReturn("ball") 194 | when(TrickDog).retrieve("stick").thenReturn("cat") 195 | unstub() 196 | self.assertEqual("stick", TrickDog.retrieve("stick")) 197 | 198 | def testUnStubWorksOnClassAndSuperClass(self): 199 | self.assertEqual("stick", Retriever.retrieve("stick")) 200 | self.assertEqual("stick", TrickDog.retrieve("stick")) 201 | 202 | when(Retriever).retrieve("stick").thenReturn("ball") 203 | self.assertEqual("ball", Retriever.retrieve("stick")) 204 | self.assertEqual("ball", TrickDog.retrieve("stick")) 205 | 206 | when(TrickDog).retrieve("stick").thenReturn("cat") 207 | self.assertEqual("ball", Retriever.retrieve("stick")) 208 | self.assertEqual("cat", TrickDog.retrieve("stick")) 209 | 210 | unstub(TrickDog) 211 | self.assertEqual("ball", Retriever.retrieve("stick")) 212 | self.assertEqual("ball", TrickDog.retrieve("stick")) 213 | 214 | unstub(Retriever) 215 | self.assertEqual("stick", Retriever.retrieve("stick")) 216 | self.assertEqual("stick", TrickDog.retrieve("stick")) 217 | 218 | def testReverseOrderWhenUnstubbing(self): 219 | when(Retriever).retrieve("stick").thenReturn("ball") 220 | when(TrickDog).retrieve("stick").thenReturn("cat") 221 | 222 | unstub(Retriever) 223 | self.assertEqual("stick", Retriever.retrieve("stick")) 224 | self.assertEqual("cat", TrickDog.retrieve("stick")) 225 | 226 | unstub(TrickDog) 227 | self.assertEqual("stick", Retriever.retrieve("stick")) 228 | self.assertEqual("stick", TrickDog.retrieve("stick")) 229 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | 4 | 5 | @pytest.fixture 6 | def unstub(): 7 | from mockito import unstub 8 | yield 9 | unstub() 10 | -------------------------------------------------------------------------------- /tests/deepcopy_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from copy import copy, deepcopy 3 | from mockito import mock, when 4 | 5 | 6 | class TestDeepcopy: 7 | def test_dumb_mocks_are_copied_correctly(self): 8 | m = mock() 9 | m.foo = [1] 10 | n = deepcopy(m) 11 | assert m is not n 12 | assert n.foo == [1] 13 | 14 | m.foo.append(2) 15 | assert n.foo == [1] 16 | 17 | def test_strict_mocks_raise_on_unexpected_calls(self): 18 | m = mock(strict=True) 19 | with pytest.raises(RuntimeError) as exc: 20 | deepcopy(m) 21 | assert str(exc.value) == ( 22 | "'Dummy' has no attribute '__deepcopy__' configured" 23 | ) 24 | 25 | def test_configured_strict_mock_answers_correctly(self): 26 | m = mock(strict=True) 27 | when(m).__deepcopy__(...).thenReturn(42) 28 | assert deepcopy(m) == 42 29 | 30 | def test_setting_none_enables_the_standard_implementation(self): 31 | m = mock({"__deepcopy__": None}, strict=True) 32 | m.foo = [1] 33 | 34 | n = deepcopy(m) 35 | assert m is not n 36 | assert n.foo == [1] 37 | 38 | m.foo.append(2) 39 | assert n.foo == [1] 40 | 41 | 42 | @pytest.mark.xfail(reason=( 43 | "the configuration is set on the mock's class, not the instance, " 44 | "which deepcopy does not copy" 45 | )) 46 | def test_deepcopy_of_a_configured_mock_is_a_new_mock(self): 47 | m = mock({"foo": [1]}, strict=True) 48 | n = deepcopy(m) 49 | 50 | m.foo.append(2) 51 | assert n.foo == [1] 52 | 53 | 54 | 55 | class TestCopy: 56 | def test_dumb_mocks_are_copied_correctly(self): 57 | m = mock() 58 | m.foo = [1] 59 | n = copy(m) 60 | assert m is not n 61 | assert n.foo == [1] 62 | 63 | m.foo.append(2) 64 | assert n.foo == [1, 2] 65 | 66 | @pytest.mark.xfail(reason=( 67 | "not working for `copy` because __copy__ is accessed on the class, " 68 | "not the instance" 69 | )) 70 | def test_strict_mocks_raise_on_unexpected_calls(self): 71 | m = mock(strict=True) 72 | with pytest.raises(RuntimeError) as exc: 73 | copy(m) 74 | assert str(exc.value) == ( 75 | "'Dummy' has no attribute '__copy__' configured" 76 | ) 77 | -------------------------------------------------------------------------------- /tests/ellipsis_test.py: -------------------------------------------------------------------------------- 1 | 2 | from collections import namedtuple 3 | 4 | import pytest 5 | from mockito import args, invocation, kwargs, mock, when 6 | 7 | 8 | class Dog(object): 9 | def bark(self, sound): 10 | return "%s!" % sound 11 | 12 | def waggle(self): 13 | return 'waggle' 14 | 15 | class CallSignature(namedtuple('CallSignature', 'args kwargs')): 16 | def raises(self, reason): 17 | return pytest.mark.xfail(self, raises=reason, strict=True) 18 | 19 | def sig(*args, **kwargs): 20 | return CallSignature(args, kwargs) 21 | 22 | 23 | class TestCallMethodWithSignature: 24 | def testNoArg(self): 25 | rex = Dog() 26 | when(rex).waggle().thenReturn('wuff') 27 | 28 | assert rex.waggle() == 'wuff' 29 | 30 | with pytest.raises(TypeError): 31 | rex.waggle(1) 32 | with pytest.raises(TypeError): 33 | rex.waggle(Ellipsis) 34 | with pytest.raises(TypeError): 35 | rex.waggle(args) 36 | with pytest.raises(TypeError): 37 | rex.waggle(kwargs) 38 | with pytest.raises(TypeError): 39 | rex.waggle(*args) 40 | with pytest.raises(TypeError): 41 | rex.waggle(**kwargs) 42 | 43 | def testExpectingSpecificInputAsPositionalArgument(self): 44 | rex = Dog() 45 | when(rex).bark(1).thenReturn('wuff') 46 | 47 | assert rex.bark(1) == 'wuff' 48 | 49 | with pytest.raises(invocation.InvocationError): 50 | rex.bark(sound=1) 51 | with pytest.raises(invocation.InvocationError): 52 | rex.bark(Ellipsis) 53 | with pytest.raises(invocation.InvocationError): 54 | rex.bark(args) 55 | with pytest.raises(invocation.InvocationError): 56 | rex.bark(*args) 57 | with pytest.raises(invocation.InvocationError): 58 | rex.bark(kwargs) 59 | 60 | with pytest.raises(TypeError): 61 | rex.bark(1, 2) 62 | with pytest.raises(TypeError): 63 | rex.bark(wuff=1) 64 | with pytest.raises(TypeError): 65 | rex.bark(**kwargs) 66 | 67 | def testExpectingSpecificInputAsKeyword(self): 68 | rex = Dog() 69 | when(rex).bark(sound=1).thenReturn('wuff') 70 | 71 | assert rex.bark(sound=1) == 'wuff' 72 | 73 | with pytest.raises(invocation.InvocationError): 74 | rex.bark(1) 75 | with pytest.raises(invocation.InvocationError): 76 | rex.bark(Ellipsis) 77 | with pytest.raises(invocation.InvocationError): 78 | rex.bark(args) 79 | with pytest.raises(invocation.InvocationError): 80 | rex.bark(*args) 81 | with pytest.raises(invocation.InvocationError): 82 | rex.bark(kwargs) 83 | 84 | with pytest.raises(TypeError): 85 | rex.bark(1, 2) 86 | with pytest.raises(TypeError): 87 | rex.bark(wuff=1) 88 | with pytest.raises(TypeError): 89 | rex.bark(**kwargs) 90 | 91 | def testExpectingStarKwargs(self): 92 | rex = Dog() 93 | when(rex).bark(**kwargs).thenReturn('wuff') 94 | 95 | assert rex.bark(sound='miau') == 'wuff' 96 | 97 | with pytest.raises(invocation.InvocationError): 98 | rex.bark('miau') 99 | with pytest.raises(invocation.InvocationError): 100 | rex.bark(Ellipsis) 101 | with pytest.raises(invocation.InvocationError): 102 | rex.bark(kwargs) 103 | with pytest.raises(invocation.InvocationError): 104 | rex.bark(args) 105 | 106 | with pytest.raises(TypeError): 107 | rex.bark(wuff='miau') 108 | with pytest.raises(TypeError): 109 | rex.bark(**kwargs) 110 | 111 | def testExpectingEllipsis(self): 112 | rex = Dog() 113 | when(rex).bark(Ellipsis).thenReturn('wuff') 114 | 115 | assert rex.bark('miau') == 'wuff' 116 | with pytest.raises(TypeError): 117 | rex.bark('miau', 'miau') 118 | 119 | assert rex.bark(sound='miau') == 'wuff' 120 | with pytest.raises(TypeError): 121 | rex.bark(wuff='miau') 122 | 123 | assert rex.bark(Ellipsis) == 'wuff' 124 | assert rex.bark(args) == 'wuff' 125 | assert rex.bark(*args) == 'wuff' 126 | assert rex.bark(kwargs) == 'wuff' 127 | 128 | with pytest.raises(TypeError): 129 | rex.bark(**kwargs) == 'wuff' 130 | 131 | def testExpectingStarArgs(self): 132 | rex = Dog() 133 | when(rex).bark(*args).thenReturn('wuff') 134 | 135 | assert rex.bark('miau') == 'wuff' 136 | 137 | with pytest.raises(invocation.InvocationError): 138 | rex.bark(sound='miau') 139 | with pytest.raises(TypeError): 140 | rex.bark(wuff='miau') 141 | 142 | assert rex.bark(*args) == 'wuff' 143 | assert rex.bark(Ellipsis) == 'wuff' 144 | 145 | with pytest.raises(TypeError): 146 | rex.bark(**kwargs) 147 | 148 | 149 | class TestEllipsises: 150 | 151 | # In python3 `bark(...)` is actually valid, but the tests must 152 | # be downwards compatible to python 2 153 | 154 | @pytest.mark.parametrize('call', [ 155 | sig(), 156 | sig('Wuff'), 157 | sig('Wuff', 'Wuff'), 158 | sig('Wuff', then='Wuff'), 159 | sig(then='Wuff'), 160 | ]) 161 | def testEllipsisAsSoleArgumentAlwaysPasses(self, call): 162 | rex = mock() 163 | when(rex).bark(Ellipsis).thenReturn('Miau') 164 | 165 | assert rex.bark(*call.args, **call.kwargs) == 'Miau' 166 | 167 | 168 | @pytest.mark.parametrize('call', [ 169 | sig('Wuff'), 170 | sig('Wuff', 'Wuff'), 171 | sig('Wuff', then='Wuff'), 172 | ]) 173 | def testEllipsisAsSecondArgumentPasses(self, call): 174 | rex = mock() 175 | when(rex).bark('Wuff', Ellipsis).thenReturn('Miau') 176 | 177 | assert rex.bark(*call.args, **call.kwargs) == 'Miau' 178 | 179 | @pytest.mark.parametrize('call', [ 180 | sig(), 181 | sig(then='Wuff'), 182 | ]) 183 | def testEllipsisAsSecondArgumentRejections(self, call): 184 | rex = mock() 185 | when(rex).bark('Wuff', Ellipsis).thenReturn('Miau') 186 | 187 | with pytest.raises(AssertionError): 188 | assert rex.bark(*call.args, **call.kwargs) == 'Miau' 189 | 190 | 191 | @pytest.mark.parametrize('call', [ 192 | sig(), 193 | sig('Wuff'), 194 | sig('Wuff', 'Wuff'), 195 | ]) 196 | def testArgsAsSoleArgumentPasses(self, call): 197 | rex = mock() 198 | when(rex).bark(*args).thenReturn('Miau') 199 | 200 | assert rex.bark(*call.args, **call.kwargs) == 'Miau' 201 | 202 | @pytest.mark.parametrize('call', [ 203 | sig('Wuff', then='Wuff'), 204 | sig(then='Wuff'), 205 | ]) 206 | def testArgsAsSoleArgumentRejections(self, call): 207 | rex = mock() 208 | when(rex).bark(*args).thenReturn('Miau') 209 | 210 | with pytest.raises(AssertionError): 211 | assert rex.bark(*call.args, **call.kwargs) == 'Miau' 212 | 213 | 214 | @pytest.mark.parametrize('call', [ 215 | sig('Wuff'), 216 | sig('Wuff', 'Wuff'), 217 | ]) 218 | def testArgsAsSecondArgumentPasses(self, call): 219 | rex = mock() 220 | when(rex).bark('Wuff', *args).thenReturn('Miau') 221 | 222 | assert rex.bark(*call.args, **call.kwargs) == 'Miau' 223 | 224 | @pytest.mark.parametrize('call', [ 225 | sig(), 226 | sig('Wuff', then='Wuff'), 227 | sig(then='Wuff'), 228 | ]) 229 | def testArgsAsSecondArgumentRejections(self, call): 230 | rex = mock() 231 | when(rex).bark('Wuff', *args).thenReturn('Miau') 232 | 233 | with pytest.raises(AssertionError): 234 | assert rex.bark(*call.args, **call.kwargs) == 'Miau' 235 | 236 | 237 | @pytest.mark.parametrize('call', [ 238 | sig('Wuff', then='Wuff'), 239 | sig('Wuff', 'Wuff', then='Wuff'), 240 | 241 | ]) 242 | def testArgsBeforeConcreteKwargPasses(self, call): 243 | rex = mock() 244 | when(rex).bark('Wuff', *args, then='Wuff').thenReturn('Miau') 245 | 246 | assert rex.bark(*call.args, **call.kwargs) == 'Miau' 247 | 248 | @pytest.mark.parametrize('call', [ 249 | sig(), 250 | sig('Wuff'), 251 | sig('Wuff', 'Wuff'), 252 | sig(then='Wuff'), 253 | 254 | ]) 255 | def testArgsBeforeConcreteKwargRejections(self, call): 256 | rex = mock() 257 | when(rex).bark('Wuff', *args, then='Wuff').thenReturn('Miau') 258 | 259 | with pytest.raises(AssertionError): 260 | assert rex.bark(*call.args, **call.kwargs) == 'Miau' 261 | 262 | 263 | @pytest.mark.parametrize('call', [ 264 | sig(), 265 | sig(then='Wuff'), 266 | sig(then='Wuff', later='Waff') 267 | ]) 268 | def testKwargsAsSoleArgumentPasses(self, call): 269 | rex = mock() 270 | when(rex).bark(**kwargs).thenReturn('Miau') 271 | 272 | assert rex.bark(*call.args, **call.kwargs) == 'Miau' 273 | 274 | @pytest.mark.parametrize('call', [ 275 | sig('Wuff'), 276 | sig('Wuff', 'Wuff'), 277 | sig('Wuff', then='Wuff'), 278 | sig('Wuff', 'Wuff', then='Wuff'), 279 | ]) 280 | def testKwargsAsSoleArgumentRejections(self, call): 281 | rex = mock() 282 | when(rex).bark(**kwargs).thenReturn('Miau') 283 | 284 | with pytest.raises(AssertionError): 285 | assert rex.bark(*call.args, **call.kwargs) == 'Miau' 286 | 287 | 288 | @pytest.mark.parametrize('call', [ 289 | sig(then='Wuff'), 290 | sig(then='Wuff', later='Waff'), 291 | sig(later='Waff', then='Wuff'), 292 | ]) 293 | def testKwargsAsSecondKwargPasses(self, call): 294 | rex = mock() 295 | when(rex).bark(then='Wuff', **kwargs).thenReturn('Miau') 296 | 297 | assert rex.bark(*call.args, **call.kwargs) == 'Miau' 298 | 299 | @pytest.mark.parametrize('call', [ 300 | sig(), 301 | sig('Wuff'), 302 | sig('Wuff', 'Wuff'), 303 | sig('Wuff', then='Wuff'), 304 | sig('Wuff', 'Wuff', then='Wuff'), 305 | sig(first='Wuff', later='Waff') 306 | ]) 307 | def testKwargsAsSecondKwargRejections(self, call): 308 | rex = mock() 309 | when(rex).bark(then='Wuff', **kwargs).thenReturn('Miau') 310 | 311 | with pytest.raises(AssertionError): 312 | assert rex.bark(*call.args, **call.kwargs) == 'Miau' 313 | 314 | 315 | @pytest.mark.parametrize('call', [ 316 | sig('Wuff', then='Waff'), 317 | sig('Wuff', 'Wuff', then='Waff'), 318 | sig('Wuff', then='Waff', later='Woff'), 319 | sig('Wuff', first="Wiff", then='Waff', later='Woff'), 320 | sig('Wuff', 'Wuff', then='Waff', later="Woff"), 321 | ]) 322 | def testCombinedArgsAndKwargsPasses(self, call): 323 | rex = mock() 324 | when(rex).bark('Wuff', *args, then='Waff', **kwargs).thenReturn('Miau') 325 | 326 | assert rex.bark(*call.args, **call.kwargs) == 'Miau' 327 | 328 | @pytest.mark.parametrize('call', [ 329 | sig(), 330 | sig('Wuff'), 331 | sig('Wuff', 'Wuff'), 332 | sig(later='Woff'), 333 | sig('Wuff', later='Woff'), 334 | ]) 335 | def testCombinedArgsAndKwargsRejections(self, call): 336 | rex = mock() 337 | when(rex).bark('Wuff', *args, then='Waff', **kwargs).thenReturn('Miau') 338 | 339 | with pytest.raises(AssertionError): 340 | assert rex.bark(*call.args, **call.kwargs) == 'Miau' 341 | 342 | 343 | @pytest.mark.parametrize('call', [ 344 | sig(Ellipsis), 345 | ]) 346 | def testEllipsisMustBeLastThing(self, call): 347 | rex = mock() 348 | when(rex).bark(*call.args, **call.kwargs).thenReturn('Miau') 349 | 350 | @pytest.mark.parametrize('call', [ 351 | sig(Ellipsis, 'Wuff'), 352 | sig(Ellipsis, then='Wuff'), 353 | sig(Ellipsis, 'Wuff', then='Waff'), 354 | ]) 355 | def testEllipsisMustBeLastThingRejections(self, call): 356 | rex = mock() 357 | with pytest.raises(TypeError): 358 | when(rex).bark(*call.args, **call.kwargs).thenReturn('Miau') 359 | 360 | 361 | def testArgsMustUsedAsStarArg(self): 362 | rex = mock() 363 | with pytest.raises(TypeError): 364 | when(rex).bark(args).thenReturn('Miau') 365 | 366 | def testKwargsMustBeUsedAsStarKwarg(self): 367 | rex = mock() 368 | with pytest.raises(TypeError): 369 | when(rex).bark(kwargs).thenReturn('Miau') 370 | 371 | with pytest.raises(TypeError): 372 | when(rex).bark(*kwargs).thenReturn('Miau') 373 | 374 | def testNiceFormattingForEllipsis(self): 375 | m = mock() 376 | m.strict = False 377 | inv = invocation.StubbedInvocation(m, 'bark', None) 378 | inv(Ellipsis) 379 | 380 | assert repr(inv) == 'bark(...)' 381 | 382 | def testNiceFormattingForArgs(self): 383 | m = mock() 384 | m.strict = False 385 | inv = invocation.StubbedInvocation(m, 'bark', None) 386 | inv(*args) 387 | 388 | assert repr(inv) == 'bark(*args)' 389 | 390 | def testNiceFormattingForKwargs(self): 391 | m = mock() 392 | m.strict = False 393 | inv = invocation.StubbedInvocation(m, 'bark', None) 394 | inv(**kwargs) 395 | 396 | assert repr(inv) == 'bark(**kwargs)' 397 | 398 | -------------------------------------------------------------------------------- /tests/issue_82_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mockito import when 3 | 4 | from . import module 5 | 6 | 7 | @pytest.mark.usefixtures('unstub') 8 | class TestIssue82: 9 | def testFunctionWithArgumentNamedValue(self): 10 | when(module).send(value="test").thenReturn("nope") 11 | assert module.send(value="test") == "nope" 12 | 13 | -------------------------------------------------------------------------------- /tests/issue_86_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mockito import when 3 | 4 | 5 | class Data: 6 | def __init__(self, name): 7 | self.name = name 8 | 9 | def __eq__(self, other): 10 | if isinstance(other, Data): 11 | return self.name == other.name 12 | return NotImplemented 13 | 14 | def tell_name(self): 15 | return self.name 16 | 17 | 18 | @pytest.mark.usefixtures('unstub') 19 | class TestIssue86: 20 | def testValueObjectsAreTreatedByIdentity(self): 21 | a = Data("Bob") 22 | b = Data("Bob") 23 | when(a).tell_name().thenReturn("Sarah") 24 | when(b).tell_name().thenReturn("Mulder") 25 | assert a.tell_name() == "Sarah" 26 | assert b.tell_name() == "Mulder" 27 | 28 | -------------------------------------------------------------------------------- /tests/late_imports_test.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | 4 | from mockito.utils import get_obj, get_obj_attr_tuple 5 | 6 | 7 | 8 | def foo(): 9 | pass 10 | 11 | class TestLateImports: 12 | 13 | def testOs(self): 14 | import os 15 | assert get_obj('os') is os 16 | 17 | def testOsPath(self): 18 | import os.path 19 | assert get_obj('os.path') is os.path 20 | 21 | def testOsPathExists(self): 22 | import os.path 23 | assert get_obj('os.path.exists') is os.path.exists 24 | 25 | def testOsPathWhatever(self): 26 | with pytest.raises(AttributeError) as exc: 27 | get_obj('os.path.whatever') 28 | 29 | assert str(exc.value) == "module 'os.path' has no attribute 'whatever'" 30 | 31 | def testOsPathExistsForever(self): 32 | with pytest.raises(AttributeError) as exc: 33 | get_obj('os.path.exists.forever') 34 | 35 | assert str(exc.value) == \ 36 | "object 'os.path.exists' has no attribute 'forever'" 37 | 38 | def testOsPathExistsForeverAndEver(self): 39 | with pytest.raises(AttributeError) as exc: 40 | get_obj('os.path.exists.forever.and.ever') 41 | 42 | assert str(exc.value) == \ 43 | "object 'os.path.exists' has no attribute 'forever'" 44 | 45 | def testUnknownMum(self): 46 | with pytest.raises(ImportError) as exc: 47 | assert get_obj('mum') is foo 48 | assert str(exc.value) == "No module named 'mum'" 49 | 50 | def testUnknownMumFoo(self): 51 | with pytest.raises(ImportError) as exc: 52 | assert get_obj('mum.foo') is foo 53 | assert str(exc.value) == "No module named 'mum'" 54 | 55 | def testReturnGivenObject(self): 56 | import os 57 | assert get_obj(os) == os 58 | assert get_obj(os.path) == os.path 59 | assert get_obj(2) == 2 60 | 61 | def testDisallowRelativeImports(self): 62 | with pytest.raises(TypeError): 63 | get_obj('.mum') 64 | 65 | class TestReturnTuple: 66 | def testOs(self): 67 | with pytest.raises(TypeError): 68 | get_obj_attr_tuple('os') 69 | 70 | def testOsPath(self): 71 | import os 72 | assert get_obj_attr_tuple('os.path') == (os, 'path') 73 | 74 | def testOsPathExists(self): 75 | import os 76 | assert get_obj_attr_tuple('os.path.exists') == (os.path, 'exists') 77 | 78 | def testOsPathExistsNot(self): 79 | import os 80 | assert get_obj_attr_tuple('os.path.exists.not') == ( 81 | os.path.exists, 'not') 82 | 83 | def testDisallowRelativeImports(self): 84 | with pytest.raises(TypeError): 85 | get_obj('.mum') 86 | 87 | 88 | -------------------------------------------------------------------------------- /tests/matchers_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008-2016 Szczepan Faber, Serhiy Oplakanets, Herr Kaste 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | from mockito.matchers import MatcherError 21 | from .test_base import TestBase 22 | from mockito import mock, verify, when 23 | from mockito.matchers import and_, or_, not_, eq, neq, lt, lte, gt, gte, \ 24 | any_, arg_that, contains, matches, captor, ANY, ARGS, KWARGS 25 | import re 26 | 27 | 28 | class TestConvenienceMatchers: 29 | def testBuiltinAnyStandsForOurAny(self): 30 | dummy = mock() 31 | dummy.foo(1) 32 | dummy.foo('a') 33 | dummy.foo(True) 34 | 35 | verify(dummy, times=3).foo(any) 36 | 37 | dummy.foo(a=12) 38 | verify(dummy).foo(a=any) 39 | 40 | def testOurAnyCanBeUsedAsAType(self): 41 | dummy = mock() 42 | dummy.foo(1) 43 | dummy.foo('a') 44 | dummy.foo(True) 45 | verify(dummy, times=3).foo(any_) 46 | 47 | 48 | class TestAliases: 49 | def testANY(self): 50 | dummy = mock() 51 | dummy.foo(1) 52 | verify(dummy).foo(ANY) 53 | 54 | def testARGS(self): 55 | dummy = mock() 56 | dummy.foo(1) 57 | verify(dummy).foo(*ARGS) 58 | 59 | def testKWARGS(self): 60 | dummy = mock() 61 | dummy.foo(a=1) 62 | verify(dummy).foo(**KWARGS) 63 | 64 | 65 | class MatchersTest(TestBase): 66 | def testVerifiesUsingContainsMatcher(self): 67 | ourMock = mock() 68 | ourMock.foo("foobar") 69 | 70 | verify(ourMock).foo(contains("foo")) 71 | verify(ourMock).foo(contains("bar")) 72 | 73 | 74 | class AndMatcherTest(TestBase): 75 | def testShouldSatisfyIfAllMatchersAreSatisfied(self): 76 | self.assertTrue( 77 | and_(contains("foo"), contains("bar")).matches("foobar")) 78 | 79 | def testShouldNotSatisfyIfOneOfMatchersIsNotSatisfied(self): 80 | self.assertFalse( 81 | and_(contains("foo"), contains("bam")).matches("foobar")) 82 | 83 | def testShouldTreatNonMatchersAsEqMatcher(self): 84 | self.assertTrue(and_("foo", any_(str)).matches("foo")) 85 | self.assertFalse(and_("foo", any_(int)).matches("foo")) 86 | 87 | 88 | class OrMatcherTest(TestBase): 89 | def testShouldSatisfyIfAnyOfMatchersIsSatisfied(self): 90 | self.assertTrue( 91 | or_(contains("foo"), contains("bam")).matches("foobar")) 92 | 93 | def testShouldNotSatisfyIfAllOfMatchersAreNotSatisfied(self): 94 | self.assertFalse( 95 | or_(contains("bam"), contains("baz")).matches("foobar")) 96 | 97 | def testShouldTreatNonMatchersAsEqMatcher(self): 98 | self.assertTrue(or_("foo", "bar").matches("foo")) 99 | self.assertFalse(or_("foo", "bar").matches("bam")) 100 | 101 | 102 | class NotMatcherTest(TestBase): 103 | def testShouldSatisfyIfInnerMatcherIsNotSatisfied(self): 104 | self.assertTrue(not_(contains("foo")).matches("bar")) 105 | 106 | def testShouldNotSatisfyIfInnerMatcherIsSatisfied(self): 107 | self.assertFalse(not_(contains("foo")).matches("foo")) 108 | 109 | def testShouldTreatNonMatchersAsEqMatcher(self): 110 | self.assertTrue(or_("foo", "bar").matches("foo")) 111 | 112 | 113 | class EqMatcherTest(TestBase): 114 | def testShouldSatisfyIfArgMatchesGivenValue(self): 115 | self.assertTrue(eq("foo").matches("foo")) 116 | 117 | def testShouldNotSatisfyIfArgDoesNotMatchGivenValue(self): 118 | self.assertFalse(eq("foo").matches("bar")) 119 | 120 | 121 | class NeqMatcherTest(TestBase): 122 | def testShouldSatisfyIfArgDoesNotMatchGivenValue(self): 123 | self.assertTrue(neq("foo").matches("bar")) 124 | 125 | def testShouldNotSatisfyIfArgMatchesGivenValue(self): 126 | self.assertFalse(neq("foo").matches("foo")) 127 | 128 | 129 | class LtMatcherTest(TestBase): 130 | def testShouldSatisfyIfArgIsLessThanGivenValue(self): 131 | self.assertTrue(lt(5).matches(4)) 132 | 133 | def testShouldNotSatisfyIfArgIsEqualToGivenValue(self): 134 | self.assertFalse(lt(5).matches(5)) 135 | 136 | def testShouldNotSatisfyIfArgIsGreaterThanGivenValue(self): 137 | self.assertFalse(lt(5).matches(6)) 138 | 139 | 140 | class LteMatcherTest(TestBase): 141 | def testShouldSatisfyIfArgIsLessThanGivenValue(self): 142 | self.assertTrue(lte(5).matches(4)) 143 | 144 | def testShouldSatisfyIfArgIsEqualToGivenValue(self): 145 | self.assertTrue(lte(5).matches(5)) 146 | 147 | def testShouldNotSatisfyIfArgIsGreaterThanGivenValue(self): 148 | self.assertFalse(lte(5).matches(6)) 149 | 150 | 151 | class GtMatcherTest(TestBase): 152 | def testShouldNotSatisfyIfArgIsLessThanGivenValue(self): 153 | self.assertFalse(gt(5).matches(4)) 154 | 155 | def testShouldNotSatisfyIfArgIsEqualToGivenValue(self): 156 | self.assertFalse(gt(5).matches(5)) 157 | 158 | def testShouldSatisfyIfArgIsGreaterThanGivenValue(self): 159 | self.assertTrue(gt(5).matches(6)) 160 | 161 | 162 | class GteMatcherTest(TestBase): 163 | def testShouldNotSatisfyIfArgIsLessThanGivenValue(self): 164 | self.assertFalse(gte(5).matches(4)) 165 | 166 | def testShouldSatisfyIfArgIsEqualToGivenValue(self): 167 | self.assertTrue(gte(5).matches(5)) 168 | 169 | def testShouldSatisfyIfArgIsGreaterThanGivenValue(self): 170 | self.assertTrue(gte(5).matches(6)) 171 | 172 | 173 | class ArgThatMatcherTest(TestBase): 174 | def testShouldSatisfyIfPredicateReturnsTrue(self): 175 | self.assertTrue(arg_that(lambda arg: arg > 5).matches(10)) 176 | 177 | def testShouldNotSatisfyIfPredicateReturnsFalse(self): 178 | self.assertFalse(arg_that(lambda arg: arg > 5).matches(1)) 179 | 180 | 181 | class ContainsMatcherTest(TestBase): 182 | def testShouldSatisfiySubstringOfGivenString(self): 183 | self.assertTrue(contains("foo").matches("foobar")) 184 | 185 | def testShouldSatisfySameString(self): 186 | self.assertTrue(contains("foobar").matches("foobar")) 187 | 188 | def testShouldNotSatisfiyStringWhichIsNotSubstringOfGivenString(self): 189 | self.assertFalse(contains("barfoo").matches("foobar")) 190 | 191 | def testShouldNotSatisfiyEmptyString(self): 192 | self.assertFalse(contains("").matches("foobar")) 193 | 194 | def testShouldNotSatisfiyNone(self): 195 | self.assertFalse(contains(None).matches("foobar")) 196 | 197 | 198 | class MatchesMatcherTest(TestBase): 199 | def testShouldSatisfyIfRegexMatchesGivenString(self): 200 | self.assertTrue(matches('f..').matches('foo')) 201 | 202 | def testShouldAllowSpecifyingRegexFlags(self): 203 | self.assertFalse(matches('f..').matches('Foo')) 204 | self.assertTrue(matches('f..', re.IGNORECASE).matches('Foo')) 205 | 206 | def testShouldNotSatisfyIfRegexIsNotMatchedByGivenString(self): 207 | self.assertFalse(matches('f..').matches('bar')) 208 | 209 | 210 | class ArgumentCaptorTest(TestBase): 211 | def test_matches_anything_by_default(self): 212 | assert captor().matches(12) 213 | assert captor().matches("anything") 214 | assert captor().matches(int) 215 | 216 | def test_matches_is_constrained_by_inner_matcher(self): 217 | assert captor(any_(int)).matches(12) 218 | assert not captor(any_(int)).matches("12") 219 | 220 | def test_all_values_initially_is_empty(self): 221 | c = captor() 222 | assert c.all_values == [] 223 | 224 | def test_captures_all_values(self): 225 | m = mock() 226 | c = captor() 227 | 228 | when(m).do(c) 229 | m.do("any") 230 | m.do("thing") 231 | 232 | assert c.all_values == ["any", "thing"] 233 | 234 | def test_captures_only_matching_values(self): 235 | m = mock() 236 | c = captor(any_(int)) 237 | 238 | when(m).do(c) 239 | m.do("any") 240 | m.do("thing") 241 | m.do(21) 242 | 243 | assert c.all_values == [21] 244 | 245 | def test_captures_all_values_while_verifying(self): 246 | m = mock() 247 | c = captor() 248 | 249 | m.do("any") 250 | m.do("thing") 251 | verify(m, times=2).do(c) 252 | 253 | assert c.all_values == ["any", "thing"] 254 | 255 | def test_remember_last_value(self): 256 | m = mock() 257 | c = captor() 258 | 259 | when(m).do(c) 260 | m.do("any") 261 | m.do("thing") 262 | 263 | assert c.value == "thing" 264 | 265 | def test_remember_last_value_while_verifying(self): 266 | m = mock() 267 | c = captor() 268 | 269 | m.do("any") 270 | m.do("thing") 271 | verify(m, times=2).do(c) 272 | 273 | assert c.value == "thing" 274 | 275 | def test_accessing_value_throws_if_nothing_captured_yet(self): 276 | c = captor() 277 | with self.assertRaises(MatcherError): 278 | _ = c.value 279 | 280 | def test_expose_issue_49_using_when(self): 281 | m = mock() 282 | c = captor() 283 | 284 | when(m).do(c, 10) 285 | when(m).do(c, 11) 286 | m.do("anything", 10) 287 | 288 | assert c.all_values == ["anything"] 289 | 290 | def test_expose_issue_49_using_verify(self): 291 | m = mock() 292 | c = captor() 293 | 294 | m.do("anything", 10) 295 | verify(m).do(c, 10) 296 | verify(m, times=0).do(c, 11) 297 | 298 | assert c.all_values == ["anything"] 299 | -------------------------------------------------------------------------------- /tests/mocking_properties_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mockito import mock, verify, when 3 | from mockito.invocation import return_ 4 | 5 | def test_deprecated_a(unstub): 6 | # Setting on `__class__` is confusing for users 7 | m = mock() 8 | 9 | prop = mock() 10 | when(prop).__get__(Ellipsis).thenRaise(ValueError) 11 | m.__class__.tx = prop 12 | 13 | with pytest.raises(ValueError): 14 | m.tx 15 | 16 | 17 | def test_deprecated_b(unstub): 18 | # Setting on `__class__` is confusing for users 19 | m = mock() 20 | 21 | def _raise(*a): 22 | print(a) 23 | raise ValueError('Boom') 24 | 25 | m.__class__.tx = property(_raise) 26 | 27 | with pytest.raises(ValueError): 28 | m.tx 29 | 30 | 31 | def test_deprecated_c(unstub): 32 | # Setting on `__class__` is confusing for users 33 | # Wrapping explicitly with `property` as well 34 | m = mock() 35 | 36 | prop = mock(strict=True) 37 | when(prop).__call__(Ellipsis).thenRaise(ValueError) 38 | m.__class__.tx = property(prop) 39 | 40 | with pytest.raises(ValueError): 41 | m.tx 42 | 43 | 44 | def test_recommended_approach_1(unstub): 45 | prop = mock() 46 | when(prop).__get__(Ellipsis).thenRaise(ValueError) 47 | 48 | m = mock({'tx': prop}) 49 | with pytest.raises(ValueError): 50 | m.tx 51 | verify(prop).__get__(...) 52 | 53 | 54 | def test_recommended_approach_2(unstub): 55 | prop = mock() 56 | when(prop).__get__(Ellipsis).thenReturn(42) 57 | 58 | m = mock({'tx': prop}) 59 | assert m.tx == 42 60 | verify(prop).__get__(...) 61 | 62 | 63 | def test_recommended_approach_3(unstub): 64 | prop = mock({"__get__": return_(42)}) 65 | m = mock({'tx': prop}) 66 | assert m.tx == 42 67 | verify(prop).__get__(...) 68 | 69 | 70 | def test_recommended_approach_4(unstub): 71 | # Elegant but you can't `verify` the usage explicitly 72 | # which makes it moot -- why not just set `m.tx = 42` then? 73 | m = mock({'tx': property(return_(42))}) 74 | assert m.tx == 42 75 | -------------------------------------------------------------------------------- /tests/module.py: -------------------------------------------------------------------------------- 1 | 2 | class Foo(object): 3 | def no_arg(self): 4 | pass 5 | 6 | 7 | def one_arg(arg): 8 | return arg 9 | 10 | 11 | def send(value): 12 | return value 13 | -------------------------------------------------------------------------------- /tests/modulefunctions_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008-2016 Szczepan Faber, Serhiy Oplakanets, Herr Kaste 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | import os 22 | 23 | from mockito import any, unstub, verify, when 24 | from mockito.invocation import InvocationError 25 | from mockito.verification import VerificationError 26 | 27 | from .test_base import TestBase 28 | 29 | 30 | class ModuleFunctionsTest(TestBase): 31 | def tearDown(self): 32 | unstub() 33 | 34 | def testUnstubs(self): 35 | when(os.path).exists("test").thenReturn(True) 36 | unstub() 37 | self.assertEqual(False, os.path.exists("test")) 38 | 39 | def testStubs(self): 40 | when(os.path).exists("test").thenReturn(True) 41 | 42 | self.assertEqual(True, os.path.exists("test")) 43 | 44 | def testStubsConsecutiveCalls(self): 45 | when(os.path).exists("test").thenReturn(False).thenReturn(True) 46 | 47 | self.assertEqual(False, os.path.exists("test")) 48 | self.assertEqual(True, os.path.exists("test")) 49 | 50 | def testStubsMultipleClasses(self): 51 | when(os.path).exists("test").thenReturn(True) 52 | when(os.path).dirname(any(str)).thenReturn("mocked") 53 | 54 | self.assertEqual(True, os.path.exists("test")) 55 | self.assertEqual("mocked", os.path.dirname("whoah!")) 56 | 57 | def testVerifiesSuccesfully(self): 58 | when(os.path).exists("test").thenReturn(True) 59 | 60 | os.path.exists("test") 61 | 62 | verify(os.path).exists("test") 63 | 64 | def testFailsVerification(self): 65 | when(os.path).exists("test").thenReturn(True) 66 | 67 | self.assertRaises(VerificationError, verify(os.path).exists, "test") 68 | 69 | def testFailsOnNumberOfCalls(self): 70 | when(os.path).exists("test").thenReturn(True) 71 | 72 | os.path.exists("test") 73 | 74 | self.assertRaises(VerificationError, verify(os.path, times=2).exists, 75 | "test") 76 | 77 | def testStubsTwiceAndUnstubs(self): 78 | when(os.path).exists("test").thenReturn(False) 79 | when(os.path).exists("test").thenReturn(True) 80 | 81 | self.assertEqual(True, os.path.exists("test")) 82 | 83 | unstub() 84 | 85 | self.assertEqual(False, os.path.exists("test")) 86 | 87 | def testStubsTwiceWithDifferentArguments(self): 88 | when(os.path).exists("Foo").thenReturn(False) 89 | when(os.path).exists("Bar").thenReturn(True) 90 | 91 | self.assertEqual(False, os.path.exists("Foo")) 92 | self.assertEqual(True, os.path.exists("Bar")) 93 | 94 | def testShouldThrowIfWeStubAFunctionNotDefinedInTheModule(self): 95 | self.assertRaises(InvocationError, 96 | lambda: when(os).walk_the_line().thenReturn(None)) 97 | 98 | def testEnsureWeCanMockTheClassOnAModule(self): 99 | from . import module 100 | when(module).Foo().thenReturn('mocked') 101 | assert module.Foo() == 'mocked' 102 | 103 | def testUnstubFunctionOnModuleWhichIsActuallyAMethod_issue_53(self): 104 | import random 105 | when(random).randint(...).thenReturn("mocked") 106 | assert random.randint(1, 10) == "mocked" 107 | unstub(random) 108 | assert random.randint(1, 10) != "mocked" 109 | 110 | def testAddFakeMethodInNotStrictMode(self): 111 | when(os.path, strict=False).new_exists("test").thenReturn(True) 112 | 113 | self.assertEqual(True, os.path.new_exists("test")) 114 | -------------------------------------------------------------------------------- /tests/my_dict_test.py: -------------------------------------------------------------------------------- 1 | 2 | from mockito.mock_registry import IdentityMap 3 | 4 | 5 | class TestIdentityMap: 6 | 7 | def testSetItemIsImplemented(self): 8 | td = IdentityMap() 9 | key = object() 10 | val = object() 11 | td[key] = val 12 | 13 | def testGetValueForKey(self): 14 | td = IdentityMap() 15 | key = object() 16 | val = object() 17 | td[key] = val 18 | 19 | assert td.get(key) == val 20 | assert td.get(object(), 42) == 42 21 | 22 | def testReplaceValueForSameKey(self): 23 | td = IdentityMap() 24 | key = object() 25 | mock1 = object() 26 | mock2 = object() 27 | td[key] = mock1 28 | td[key] = mock2 29 | 30 | assert td.values() == [mock2] 31 | 32 | def testPopKey(self): 33 | td = IdentityMap() 34 | key = object() 35 | val = object() 36 | td[key] = val 37 | 38 | assert td.pop(key) == val 39 | assert td.values() == [] 40 | 41 | def testClear(self): 42 | td = IdentityMap() 43 | key = object() 44 | val = object() 45 | td[key] = val 46 | 47 | td.clear() 48 | assert td.values() == [] 49 | 50 | def testEqualityIsIgnored(self): 51 | td = IdentityMap() 52 | td[{"one", "two", "foo"}] = object() 53 | td[{"one", "two", "foo"}] = object() 54 | assert len(td.values()) == 2 55 | -------------------------------------------------------------------------------- /tests/numpy_test.py: -------------------------------------------------------------------------------- 1 | import mockito 2 | from mockito import when, patch 3 | import pytest 4 | 5 | import numpy as np 6 | from . import module 7 | 8 | 9 | pytestmark = pytest.mark.usefixtures("unstub") 10 | 11 | 12 | def xcompare(a, b): 13 | if isinstance(a, mockito.matchers.Matcher): 14 | return a.matches(b) 15 | 16 | return np.array_equal(a, b) 17 | 18 | 19 | class TestEnsureNumpyWorks: 20 | def testEnsureNumpyArrayAllowedWhenStubbing(self): 21 | array = np.array([1, 2, 3]) 22 | when(module).one_arg(array).thenReturn('yep') 23 | 24 | with patch(mockito.invocation.MatchingInvocation.compare, xcompare): 25 | assert module.one_arg(array) == 'yep' 26 | 27 | def testEnsureNumpyArrayAllowedWhenCalling(self): 28 | array = np.array([1, 2, 3]) 29 | when(module).one_arg(Ellipsis).thenReturn('yep') 30 | assert module.one_arg(array) == 'yep' 31 | 32 | -------------------------------------------------------------------------------- /tests/signatures_test.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | 4 | from mockito import when, args, kwargs, unstub 5 | 6 | from collections import namedtuple 7 | 8 | 9 | class CallSignature(namedtuple('CallSignature', 'args kwargs')): 10 | def raises(self, reason): 11 | return pytest.mark.xfail(str(self), raises=reason, strict=True) 12 | 13 | def sig(*a, **kw): 14 | return CallSignature(a, kw) 15 | 16 | 17 | class SUT(object): 18 | def none_args(self): 19 | pass 20 | 21 | def one_arg(self, a): 22 | pass 23 | 24 | def two_args(self, a, b): 25 | pass 26 | 27 | def star_arg(self, *args): 28 | pass 29 | 30 | def star_kwarg(self, **kwargs): 31 | pass 32 | 33 | def arg_plus_star_arg(self, a, *b): 34 | pass 35 | 36 | def arg_plus_star_kwarg(self, a, **b): 37 | pass 38 | 39 | def two_args_wt_default(self, a, b=None): 40 | pass 41 | 42 | def combination(self, a, b=None, *c, **d): 43 | pass 44 | 45 | 46 | class ClassMethods(object): 47 | @classmethod 48 | def none_args(cls): 49 | pass 50 | 51 | @classmethod 52 | def one_arg(cls, a): 53 | pass 54 | 55 | @classmethod 56 | def two_args(cls, a, b): 57 | pass 58 | 59 | @classmethod 60 | def star_arg(cls, *a): 61 | pass 62 | 63 | @classmethod 64 | def star_kwarg(cls, **kw): 65 | pass 66 | 67 | @classmethod 68 | def arg_plus_star_arg(cls, a, *b): 69 | pass 70 | 71 | @classmethod 72 | def arg_plus_star_kwarg(cls, a, **b): 73 | pass 74 | 75 | @classmethod 76 | def two_args_wt_default(cls, a, b=None): 77 | pass 78 | 79 | @classmethod 80 | def combination(cls, a, b=None, *c, **d): 81 | pass 82 | 83 | 84 | class StaticMethods(object): 85 | @staticmethod 86 | def none_args(): 87 | pass 88 | 89 | @staticmethod 90 | def one_arg(a): 91 | pass 92 | 93 | @staticmethod 94 | def two_args(a, b): 95 | pass 96 | 97 | @staticmethod 98 | def star_arg(*a): 99 | pass 100 | 101 | @staticmethod 102 | def star_kwarg(**kw): 103 | pass 104 | 105 | @staticmethod 106 | def arg_plus_star_arg(a, *b): 107 | pass 108 | 109 | @staticmethod 110 | def arg_plus_star_kwarg(a, **b): 111 | pass 112 | 113 | @staticmethod 114 | def two_args_wt_default(a, b=None): 115 | pass 116 | 117 | @staticmethod 118 | def combination(a, b=None, *c, **d): 119 | pass 120 | 121 | 122 | @pytest.fixture(params=[ 123 | 'instance', 124 | 'class', 125 | 'classmethods', 126 | 'staticmethods', 127 | 'staticmethods_2', 128 | ]) 129 | def sut(request): 130 | if request.param == 'instance': 131 | yield SUT() 132 | elif request.param == 'class': 133 | yield SUT 134 | elif request.param == 'classmethods': 135 | yield ClassMethods 136 | elif request.param == 'staticmethods': 137 | yield StaticMethods 138 | elif request.param == 'staticmethods_2': 139 | yield StaticMethods() 140 | 141 | unstub() 142 | 143 | 144 | class TestSignatures: 145 | 146 | class TestNoneArg: 147 | 148 | @pytest.mark.parametrize('call', [ 149 | sig(), 150 | sig(Ellipsis), 151 | ]) 152 | def test_passing(self, sut, call): 153 | when(sut).none_args(*call.args, **call.kwargs).thenReturn('stub') 154 | 155 | 156 | @pytest.mark.parametrize('call', [ 157 | sig(12), 158 | sig(*args), 159 | sig(**kwargs), 160 | sig(*args, **kwargs) 161 | ]) 162 | def test_failing(self, sut, call): 163 | with pytest.raises(TypeError): 164 | when(sut).none_args(*call.args, **call.kwargs) 165 | 166 | 167 | class TestOneArg: 168 | 169 | @pytest.mark.parametrize('call', [ 170 | sig(12), 171 | sig(a=12), 172 | 173 | sig(Ellipsis), 174 | 175 | sig(*args), 176 | sig(*args, **kwargs), 177 | sig(**kwargs), 178 | ]) 179 | def test_passing(self, sut, call): 180 | when(sut).one_arg(*call.args, **call.kwargs).thenReturn('stub') 181 | 182 | @pytest.mark.parametrize('call', [ 183 | sig(12, 13), 184 | sig(12, b=2), 185 | sig(12, 13, 14), 186 | sig(b=2), 187 | sig(12, c=2), 188 | sig(12, b=2, c=2), 189 | 190 | sig(12, Ellipsis), 191 | 192 | sig(1, *args), 193 | sig(*args, a=1), 194 | sig(*args, b=1), 195 | sig(1, **kwargs), 196 | ]) 197 | def test_failing(self, sut, call): 198 | with pytest.raises(TypeError): 199 | when(sut).one_arg(*call.args, **call.kwargs) 200 | 201 | 202 | class TestTwoArgs: 203 | 204 | # def two_args(a, b) 205 | @pytest.mark.parametrize('call', [ 206 | sig(12, 13), 207 | sig(12, b=2), 208 | 209 | sig(Ellipsis), 210 | sig(12, Ellipsis), 211 | 212 | sig(*args), 213 | sig(*args, **kwargs), 214 | sig(12, *args), 215 | sig(**kwargs), 216 | sig(12, **kwargs), 217 | sig(b=13, **kwargs), 218 | 219 | ]) 220 | def test_passing(self, sut, call): 221 | when(sut).two_args(*call.args, **call.kwargs) 222 | 223 | @pytest.mark.parametrize('call', [ 224 | sig(12), 225 | sig(12, 13, 14), 226 | sig(b=2), 227 | sig(12, c=2), 228 | sig(12, b=2, c=2), 229 | 230 | sig(12, 13, Ellipsis), 231 | sig(12, 13, *args), 232 | sig(12, b=13, *args), 233 | sig(12, 13, **kwargs), 234 | sig(12, b=13, **kwargs), 235 | ]) 236 | def test_failing(self, sut, call): 237 | with pytest.raises(TypeError): 238 | when(sut).two_args(*call.args, **call.kwargs) 239 | 240 | class TestStarArg: 241 | # def star_arg(*args) 242 | @pytest.mark.parametrize('call', [ 243 | sig(), 244 | sig(12), 245 | sig(12, 13), 246 | 247 | sig(Ellipsis), 248 | sig(12, Ellipsis), 249 | sig(12, 13, Ellipsis), 250 | 251 | sig(*args), 252 | sig(12, *args), 253 | sig(12, 13, *args) 254 | ], ids=lambda i: str(i)) 255 | def test_passing(self, sut, call): 256 | when(sut).star_arg(*call.args, **call.kwargs) 257 | 258 | @pytest.mark.parametrize('call', [ 259 | sig(**kwargs), 260 | sig(12, **kwargs), 261 | sig(Ellipsis, **kwargs), 262 | sig(a=12), 263 | sig(args=12) 264 | ], ids=lambda i: str(i)) 265 | def test_failing(self, sut, call): 266 | with pytest.raises(TypeError): 267 | when(sut).star_arg(*call.args, **call.kwargs) 268 | 269 | 270 | class TestStarKwarg: 271 | # def star_kwarg(**kwargs) 272 | @pytest.mark.parametrize('call', [ 273 | sig(), 274 | sig(a=1), 275 | sig(a=1, b=2), 276 | 277 | sig(Ellipsis), 278 | 279 | sig(**kwargs), 280 | sig(a=1, **kwargs) 281 | 282 | ], ids=lambda i: str(i)) 283 | def test_passing(self, sut, call): 284 | when(sut).star_kwarg(*call.args, **call.kwargs) 285 | 286 | @pytest.mark.parametrize('call', [ 287 | sig(12), 288 | sig(*args), 289 | sig(*args, **kwargs), 290 | sig(12, a=1) 291 | ], ids=lambda i: str(i)) 292 | def test_failing(self, sut, call): 293 | with pytest.raises(TypeError): 294 | when(sut).star_kwarg(*call.args, **call.kwargs) 295 | 296 | 297 | class TestArgPlusStarArg: 298 | # def arg_plus_star_arg(a, *args) 299 | @pytest.mark.parametrize('call', [ 300 | sig(12), 301 | sig(a=12), 302 | sig(12, 13), 303 | 304 | sig(Ellipsis), 305 | sig(12, Ellipsis), 306 | sig(12, 13, Ellipsis), 307 | 308 | sig(*args), 309 | sig(12, *args), 310 | 311 | sig(**kwargs), 312 | ], ids=lambda i: str(i)) 313 | def test_passing(self, sut, call): 314 | when(sut).arg_plus_star_arg(*call.args, **call.kwargs) 315 | 316 | @pytest.mark.parametrize('call', [ 317 | sig(), 318 | sig(13, a=12), 319 | 320 | sig(b=13), 321 | sig(12, b=13, *args), 322 | sig(a=12, b=13, *args), 323 | 324 | sig(12, **kwargs), 325 | sig(a=12, **kwargs), 326 | ], ids=lambda i: str(i)) 327 | def test_failing(self, sut, call): 328 | with pytest.raises(TypeError): 329 | when(sut).arg_plus_star_arg(*call.args, **call.kwargs) 330 | 331 | 332 | class TestArgPlusStarKwarg: 333 | # def arg_plus_star_kwarg(a, **kwargs) 334 | @pytest.mark.parametrize('call', [ 335 | sig(12), 336 | sig(a=12), 337 | sig(12, b=1), 338 | 339 | sig(Ellipsis), 340 | sig(12, Ellipsis), 341 | 342 | sig(**kwargs), 343 | sig(12, **kwargs), 344 | sig(a=12, **kwargs), 345 | sig(12, b=1, **kwargs), 346 | sig(a=12, b=1, **kwargs), 347 | 348 | sig(*args), 349 | sig(*args, b=1), 350 | 351 | sig(*args, **kwargs) 352 | ], ids=lambda i: str(i)) 353 | def test_passing(self, sut, call): 354 | when(sut).arg_plus_star_kwarg(*call.args, **call.kwargs) 355 | 356 | @pytest.mark.parametrize('call', [ 357 | sig(), 358 | sig(12, 13), 359 | sig(b=1), 360 | sig(12, a=1), 361 | sig(12, 13, Ellipsis), 362 | sig(*args, a=1), 363 | sig(12, *args) 364 | ], ids=lambda i: str(i)) 365 | def test_failing(self, sut, call): 366 | with pytest.raises(TypeError): 367 | when(sut).arg_plus_star_kwarg(*call.args, **call.kwargs) 368 | 369 | 370 | 371 | 372 | class TestTwoArgsWtDefault: 373 | 374 | @pytest.mark.parametrize('call', [ 375 | sig(12), 376 | sig(12, 13), 377 | sig(12, b=2), 378 | 379 | sig(Ellipsis), 380 | sig(12, Ellipsis), 381 | 382 | sig(*args), 383 | sig(*args, **kwargs), 384 | sig(12, *args), 385 | sig(*args, b=2), 386 | sig(**kwargs), 387 | sig(12, **kwargs), 388 | ], ids=lambda i: str(i)) 389 | def test_passing(self, sut, call): 390 | when(sut).two_args_wt_default( 391 | *call.args, **call.kwargs).thenReturn() 392 | 393 | 394 | @pytest.mark.parametrize('call', [ 395 | sig(12, 13, 14), 396 | sig(b=2), 397 | sig(12, c=2), 398 | sig(12, b=2, c=2), 399 | 400 | sig(12, 13, Ellipsis), 401 | 402 | sig(12, 13, *args), 403 | sig(12, b=13, *args), 404 | sig(12, c=13, *args), 405 | sig(12, *args, b=2), 406 | sig(*args, a=2), 407 | sig(*args, c=2), 408 | sig(12, 13, **kwargs), 409 | sig(12, b=13, **kwargs), 410 | sig(12, c=13, **kwargs), 411 | ], ids=lambda i: str(i)) 412 | def test_failing(self, sut, call): 413 | with pytest.raises(TypeError): 414 | when(sut).two_args_wt_default( 415 | *call.args, **call.kwargs).thenReturn() 416 | 417 | class TestCombination: 418 | 419 | # def combination(self, a, b=None, *c, **d) 420 | @pytest.mark.parametrize('call', [ 421 | sig(12), 422 | sig(12, 13), 423 | sig(12, 13, 14), 424 | sig(12, 13, 14, 15), 425 | 426 | sig(Ellipsis), 427 | sig(12, Ellipsis), 428 | sig(12, 13, Ellipsis), 429 | sig(12, 13, 14, Ellipsis), 430 | sig(12, 13, 14, 15, Ellipsis) 431 | 432 | ], ids=lambda i: str(i)) 433 | def test_passing(self, sut, call): 434 | when(sut).combination( 435 | *call.args, **call.kwargs).thenReturn() 436 | 437 | 438 | @pytest.mark.parametrize('call', [ 439 | sig(12, 13, b=16), 440 | ], ids=lambda i: str(i)) 441 | def test_failing(self, sut, call): 442 | with pytest.raises(TypeError): 443 | when(sut).combination( 444 | *call.args, **call.kwargs).thenReturn() 445 | 446 | 447 | class TestBuiltin: 448 | 449 | def testBuiltinOpen(self): 450 | try: 451 | import builtins 452 | except ImportError: 453 | import __builtin__ as builtins # type: ignore[import, no-redef] # noqa: E501 454 | 455 | try: 456 | when(builtins).open('foo') 457 | finally: # just to be sure 458 | unstub() 459 | 460 | -------------------------------------------------------------------------------- /tests/speccing_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008-2016 Szczepan Faber, Serhiy Oplakanets, Herr Kaste 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | import pytest 22 | 23 | from mockito.invocation import InvocationError 24 | from mockito import mock, when, verify 25 | 26 | 27 | class Foo(object): 28 | def bar(self): 29 | pass 30 | 31 | class Action(object): 32 | def no_arg(self): 33 | pass 34 | 35 | def run(self, arg): 36 | """this is a docstring""" 37 | return arg 38 | 39 | def __call__(self, task): 40 | return task 41 | 42 | 43 | class TestSpeccing: 44 | def testStubCallAndVerify(self): 45 | action = mock(Action) 46 | 47 | when(action).run(11).thenReturn(12) 48 | assert action.run(11) == 12 49 | verify(action).run(11) 50 | 51 | def testShouldScreamWhenStubbingUnknownMethod(self): 52 | action = mock(Action) 53 | 54 | with pytest.raises(InvocationError): 55 | when(action).unknownMethod() 56 | 57 | def testShouldScreamWhenCallingUnknownMethod(self): 58 | action = mock(Action) 59 | 60 | with pytest.raises(AttributeError): 61 | action.unknownMethod() 62 | 63 | def testShouldScreamWhenCallingUnexpectedMethod(self): 64 | action = mock(Action) 65 | 66 | with pytest.raises(AttributeError): 67 | action.run(11) 68 | 69 | def testPreconfigureMockWithAttributes(self): 70 | action = mock({'foo': 'bar'}, spec=Action) 71 | 72 | assert action.foo == 'bar' 73 | 74 | def testPreconfigureWithFunction(self): 75 | action = mock({ 76 | 'run': lambda _: 12 77 | }, spec=Action) 78 | 79 | assert action.run(11) == 12 80 | 81 | verify(action).run(11) 82 | 83 | def testPreconfigureWithFunctionThatTakesNoArgs(self): 84 | action = mock({ 85 | 'no_arg': lambda: 12 86 | }, spec=Action) 87 | 88 | assert action.no_arg() == 12 89 | 90 | verify(action).no_arg() 91 | 92 | def testShouldScreamOnUnknownAttribute(self): 93 | action = mock(Action) 94 | 95 | with pytest.raises(AttributeError): 96 | action.cam 97 | 98 | def testShouldPassIsInstanceChecks(self): 99 | action = mock(Action) 100 | 101 | assert isinstance(action, Action) 102 | 103 | class TestVariousConstructorForms: 104 | def testUsingKeywordArg(self): 105 | action = mock(spec=Action) 106 | 107 | assert isinstance(action, Action) 108 | 109 | def testUsingPositionalArg(self): 110 | action = mock(Action) 111 | 112 | assert isinstance(action, Action) 113 | 114 | def testKeywordAfterConfigurationObject(self): 115 | action = mock({}, spec=Action) 116 | 117 | assert isinstance(action, Action) 118 | 119 | def testPositionalArgAfterConfigurationObject(self): 120 | action = mock({}, Action) 121 | 122 | assert isinstance(action, Action) 123 | 124 | def testMockHasANiceName(self): 125 | action = mock(Action) 126 | 127 | assert repr(action) == "" % id(action) 128 | 129 | def testConfiguredMethodHasCorrectName(self): 130 | action = mock(Action) 131 | when(action).run(11).thenReturn(12) 132 | assert action.run.__name__ == 'run' 133 | 134 | def testConfiguredMethodIsBound(self): 135 | action = mock(Action) 136 | when(action).run(11).thenReturn(12) 137 | assert action.run.__self__ == action 138 | 139 | def testConfiguredMethodHasCorrectDocstring(self): 140 | action = mock(Action) 141 | when(action).run(11).thenReturn(12) 142 | assert action.run.__doc__ == 'this is a docstring' 143 | 144 | 145 | class TestSpeccingLoose: 146 | def testReturnNoneForEveryMethod(self): 147 | action = mock(Action, strict=False) 148 | assert action.unknownMethod() is None 149 | assert action.run(11) is None 150 | 151 | -------------------------------------------------------------------------------- /tests/spying_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008-2016 Szczepan Faber, Serhiy Oplakanets, Herr Kaste 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | import pytest 22 | import sys 23 | 24 | from .test_base import TestBase 25 | from mockito import ( 26 | when, spy, spy2, verify, VerificationError, verifyZeroInteractions) 27 | 28 | import time 29 | 30 | class Dummy(object): 31 | def foo(self): 32 | return "foo" 33 | 34 | def bar(self): 35 | raise TypeError 36 | 37 | def return_args(self, *args, **kwargs): 38 | return (args, kwargs) 39 | 40 | @classmethod 41 | def class_method(cls, arg): 42 | return arg 43 | 44 | 45 | class SpyingTest(TestBase): 46 | def testPreservesReturnValues(self): 47 | dummy = Dummy() 48 | spiedDummy = spy(dummy) 49 | self.assertEqual(dummy.foo(), spiedDummy.foo()) 50 | 51 | def testPreservesSideEffects(self): 52 | dummy = spy(Dummy()) 53 | self.assertRaises(TypeError, dummy.bar) 54 | 55 | def testPassesArgumentsCorrectly(self): 56 | dummy = spy(Dummy()) 57 | self.assertEqual((('foo', 1), {'bar': 'baz'}), 58 | dummy.return_args('foo', 1, bar='baz')) 59 | 60 | def testIsVerifiable(self): 61 | dummy = spy(Dummy()) 62 | dummy.foo() 63 | verify(dummy).foo() 64 | self.assertRaises(VerificationError, verify(dummy).bar) 65 | 66 | def testVerifyZeroInteractionsWorks(self): 67 | dummy = spy(Dummy()) 68 | verifyZeroInteractions(dummy) 69 | 70 | def testRaisesAttributeErrorIfNoSuchMethod(self): 71 | original = Dummy() 72 | dummy = spy(original) 73 | try: 74 | dummy.lol() 75 | self.fail("Should fail if no such method.") 76 | except AttributeError as e: 77 | self.assertEqual("You tried to call method 'lol' which '%s' " 78 | "instance does not have." % original, str(e)) 79 | 80 | def testIsInstanceFakesOriginalClass(self): 81 | dummy = spy(Dummy()) 82 | 83 | assert isinstance(dummy, Dummy) 84 | 85 | def testHasNiceRepr(self): 86 | dummy = spy(Dummy()) 87 | 88 | assert repr(dummy) == "" % id(dummy) 89 | 90 | 91 | 92 | def testCallClassmethod(self): 93 | dummy = spy(Dummy) 94 | 95 | assert dummy.class_method('foo') == 'foo' 96 | verify(dummy).class_method('foo') 97 | 98 | 99 | @pytest.mark.xfail( 100 | sys.version_info >= (3,), 101 | reason="python3 allows any value for self" 102 | ) 103 | def testCantCallInstanceMethodWhenSpyingClass(self): 104 | dummy = spy(Dummy) 105 | with pytest.raises(TypeError): 106 | dummy.return_args('foo') 107 | 108 | 109 | def testModuleFunction(self): 110 | import time 111 | dummy = spy(time) 112 | 113 | assert dummy.time() is not None 114 | 115 | verify(dummy).time() 116 | 117 | 118 | class TestSpy2: 119 | 120 | def testA(self): 121 | dummy = Dummy() 122 | spy2(dummy.foo) 123 | 124 | assert dummy.foo() == 'foo' 125 | verify(dummy).foo() 126 | 127 | def testB(self): 128 | spy2(Dummy.class_method) 129 | 130 | assert Dummy.class_method('foo') == 'foo' 131 | verify(Dummy).class_method('foo') 132 | 133 | def testModule(self): 134 | spy2(time.time) 135 | 136 | assert time.time() is not None 137 | verify(time).time() 138 | 139 | def testEnsureStubbedResponseForSpecificInvocation(self): 140 | dummy = Dummy() 141 | spy2(dummy.return_args) 142 | when(dummy).return_args('foo').thenReturn('fox') 143 | 144 | assert dummy.return_args('bar') == (('bar',), {}) 145 | assert dummy.return_args('box') == (('box',), {}) 146 | assert dummy.return_args('foo') == 'fox' 147 | 148 | def testEnsureStubOrder(self): 149 | dummy = Dummy() 150 | when(dummy).return_args(Ellipsis).thenReturn('foo') 151 | when(dummy).return_args('fox').thenReturn('fix') 152 | 153 | assert dummy.return_args('bar') == 'foo' 154 | assert dummy.return_args('box') == 'foo' 155 | assert dummy.return_args('fox') == 'fix' 156 | 157 | def testSpyOnClass(self): 158 | spy2(Dummy.foo) 159 | assert Dummy().foo() == 'foo' 160 | 161 | -------------------------------------------------------------------------------- /tests/staticmethods_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008-2016 Szczepan Faber, Serhiy Oplakanets, Herr Kaste 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from mockito import any, unstub, verify, when 22 | from mockito.verification import VerificationError 23 | 24 | from .test_base import TestBase 25 | 26 | 27 | class Dog: 28 | @staticmethod 29 | def bark(): 30 | return "woof" 31 | 32 | @staticmethod 33 | def barkHardly(*args): 34 | return "woof woof" 35 | 36 | 37 | class Cat: 38 | @staticmethod 39 | def meow(): 40 | return "miau" 41 | 42 | 43 | class StaticMethodsTest(TestBase): 44 | 45 | def tearDown(self): 46 | unstub() 47 | 48 | def testUnstubs(self): 49 | when(Dog).bark().thenReturn("miau") 50 | unstub() 51 | self.assertEqual("woof", Dog.bark()) 52 | 53 | # TODO decent test case please :) without testing irrelevant implementation 54 | # details 55 | def testUnstubShouldPreserveMethodType(self): 56 | when(Dog).bark().thenReturn("miau!") 57 | unstub() 58 | self.assertTrue(isinstance(Dog.__dict__.get("bark"), staticmethod)) 59 | 60 | def testStubs(self): 61 | self.assertEqual("woof", Dog.bark()) 62 | 63 | when(Dog).bark().thenReturn("miau") 64 | 65 | self.assertEqual("miau", Dog.bark()) 66 | 67 | def testStubsConsecutiveCalls(self): 68 | when(Dog).bark().thenReturn(1).thenReturn(2) 69 | 70 | self.assertEqual(1, Dog.bark()) 71 | self.assertEqual(2, Dog.bark()) 72 | self.assertEqual(2, Dog.bark()) 73 | 74 | def testStubsWithArgs(self): 75 | self.assertEqual("woof woof", Dog.barkHardly(1, 2)) 76 | 77 | when(Dog).barkHardly(1, 2).thenReturn("miau") 78 | 79 | self.assertEqual("miau", Dog.barkHardly(1, 2)) 80 | 81 | def testStubsWithArgsOnInstance(self): 82 | dog = Dog() 83 | self.assertEqual("woof woof", dog.barkHardly(1, 2)) 84 | 85 | when(Dog).barkHardly(1, 2).thenReturn("miau") 86 | 87 | self.assertEqual("miau", dog.barkHardly(1, 2)) 88 | 89 | def testStubsButDoesNotMachArguments(self): 90 | self.assertEqual("woof woof", Dog.barkHardly(1, "anything")) 91 | 92 | when(Dog, strict=False).barkHardly(1, 2).thenReturn("miau") 93 | 94 | self.assertEqual(None, Dog.barkHardly(1)) 95 | 96 | def testStubsMultipleClasses(self): 97 | when(Dog).barkHardly(1, 2).thenReturn(1) 98 | when(Dog).bark().thenReturn(2) 99 | when(Cat).meow().thenReturn(3) 100 | 101 | self.assertEqual(1, Dog.barkHardly(1, 2)) 102 | self.assertEqual(2, Dog.bark()) 103 | self.assertEqual(3, Cat.meow()) 104 | 105 | unstub() 106 | 107 | self.assertEqual("woof", Dog.bark()) 108 | self.assertEqual("miau", Cat.meow()) 109 | 110 | def testVerifiesSuccesfully(self): 111 | when(Dog).bark().thenReturn("boo") 112 | 113 | Dog.bark() 114 | 115 | verify(Dog).bark() 116 | 117 | def testVerifiesWithArguments(self): 118 | when(Dog).barkHardly(1, 2).thenReturn("boo") 119 | 120 | Dog.barkHardly(1, 2) 121 | 122 | verify(Dog).barkHardly(1, any()) 123 | 124 | def testFailsVerification(self): 125 | when(Dog).bark().thenReturn("boo") 126 | 127 | Dog.bark() 128 | 129 | self.assertRaises(VerificationError, verify(Dog).barkHardly, (1, 2)) 130 | 131 | def testFailsOnInvalidArguments(self): 132 | when(Dog).bark().thenReturn("boo") 133 | 134 | Dog.barkHardly(1, 2) 135 | 136 | self.assertRaises(VerificationError, verify(Dog).barkHardly, (1, 20)) 137 | 138 | def testFailsOnNumberOfCalls(self): 139 | when(Dog).bark().thenReturn("boo") 140 | 141 | Dog.bark() 142 | 143 | self.assertRaises(VerificationError, verify(Dog, times=2).bark) 144 | 145 | def testStubsAndVerifies(self): 146 | when(Dog).bark().thenReturn("boo") 147 | 148 | self.assertEqual("boo", Dog.bark()) 149 | 150 | verify(Dog).bark() 151 | 152 | def testStubsTwiceAndUnstubs(self): 153 | when(Dog).bark().thenReturn(1) 154 | when(Dog).bark().thenReturn(2) 155 | 156 | self.assertEqual(2, Dog.bark()) 157 | 158 | unstub() 159 | 160 | self.assertEqual("woof", Dog.bark()) 161 | 162 | def testDoesNotVerifyStubbedCalls(self): 163 | when(Dog).bark().thenReturn(1) 164 | 165 | verify(Dog, times=0).bark() 166 | 167 | -------------------------------------------------------------------------------- /tests/test_base.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008-2016 Szczepan Faber, Serhiy Oplakanets, Herr Kaste 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | import unittest 22 | 23 | 24 | class TestBase(unittest.TestCase): 25 | 26 | def assertRaisesMessage(self, message, callable, *params): 27 | try: 28 | if (params): 29 | callable(params) 30 | else: 31 | callable() 32 | except Exception as e: 33 | self.assertEqual(message, str(e)) 34 | else: 35 | self.fail('Exception with message "%s" expected, but never raised' 36 | % (message)) 37 | 38 | -------------------------------------------------------------------------------- /tests/unstub_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from mockito import mock, when, unstub, verify, ArgumentError 4 | 5 | 6 | class Dog(object): 7 | def waggle(self): 8 | return 'Unsure' 9 | 10 | def bark(self, sound='Wuff'): 11 | return sound 12 | 13 | 14 | class TestUntub: 15 | def testIndependentUnstubbing(self): 16 | rex = Dog() 17 | mox = Dog() 18 | 19 | when(rex).waggle().thenReturn('Yup') 20 | when(mox).waggle().thenReturn('Nope') 21 | 22 | assert rex.waggle() == 'Yup' 23 | assert mox.waggle() == 'Nope' 24 | 25 | unstub(rex) 26 | 27 | assert rex.waggle() == 'Unsure' 28 | assert mox.waggle() == 'Nope' 29 | 30 | unstub(mox) 31 | 32 | assert mox.waggle() == 'Unsure' 33 | 34 | def testUnconfigureMock(self): 35 | m = mock() 36 | when(m).foo().thenReturn(42) 37 | assert m.foo() == 42 38 | unstub(m) 39 | assert m.foo() is None 40 | 41 | class TestAutomaticUnstubbing: 42 | 43 | def testWith1(self): 44 | rex = Dog() 45 | 46 | with when(rex).waggle().thenReturn('Yup'): 47 | assert rex.waggle() == 'Yup' 48 | verify(rex).waggle() 49 | 50 | assert rex.waggle() == 'Unsure' 51 | 52 | def testWith2(self): 53 | # Ensure the short version to return None works 54 | rex = Dog() 55 | 56 | with when(rex).waggle(): 57 | assert rex.waggle() is None 58 | verify(rex).waggle() 59 | 60 | assert rex.waggle() == 'Unsure' 61 | 62 | def testWithRaisingSideeffect(self): 63 | rex = Dog() 64 | 65 | with pytest.raises(RuntimeError): 66 | with when(rex).waggle().thenRaise(RuntimeError('Nope')): 67 | rex.waggle() 68 | assert rex.waggle() == 'Unsure' 69 | 70 | def testNesting(self): 71 | # That's not a real test, I just wanted to see how it would 72 | # look like; bc you run out of space pretty quickly 73 | rex = Dog() 74 | mox = Dog() 75 | 76 | with when(rex).waggle().thenReturn('Yup'): 77 | with when(mox).waggle().thenReturn('Nope'): 78 | assert rex.waggle() == 'Yup' 79 | 80 | assert rex.waggle() == 'Unsure' 81 | assert mox.waggle() == 'Unsure' 82 | # though that's a good looking option 83 | with when(rex).waggle().thenReturn('Yup'), \ 84 | when(mox).waggle().thenReturn('Nope'): # noqa: E127 85 | assert rex.waggle() == 'Yup' 86 | assert mox.waggle() == 'Nope' 87 | 88 | assert rex.waggle() == 'Unsure' 89 | assert mox.waggle() == 'Unsure' 90 | 91 | def testOnlyUnstubTheExactStub(self): 92 | rex = Dog() 93 | 94 | when(rex).bark('Shhh').thenReturn('Nope') 95 | with when(rex).bark('Miau').thenReturn('Grrr'): 96 | assert rex.bark('Miau') == 'Grrr' 97 | 98 | assert rex.bark('Shhh') == 'Nope' 99 | verify(rex, times=2).bark(Ellipsis) 100 | 101 | def testOnlyUnstubTheExcatMethod(self): 102 | rex = Dog() 103 | 104 | when(rex).bark('Shhh').thenReturn('Nope') 105 | with when(rex).waggle().thenReturn('Yup'): 106 | assert rex.waggle() == 'Yup' 107 | 108 | assert rex.bark('Shhh') == 'Nope' 109 | verify(rex, times=1).bark(Ellipsis) 110 | verify(rex, times=1).waggle() 111 | 112 | def testCleanupRegistryAfterLastStub(self): 113 | rex = Dog() 114 | 115 | with when(rex).bark('Shhh').thenReturn('Nope'): 116 | with when(rex).bark('Miau').thenReturn('Grrr'): 117 | assert rex.bark('Miau') == 'Grrr' 118 | assert rex.bark('Shhh') == 'Nope' 119 | 120 | with pytest.raises(ArgumentError): 121 | verify(rex).bark(Ellipsis) 122 | 123 | 124 | class TestEnsureCleanUnstubIfMockingAGlobal: 125 | def testA(self): 126 | with when(Dog).waggle().thenReturn('Sure'): 127 | rex = Dog() 128 | assert rex.waggle() == 'Sure' 129 | 130 | verify(Dog).waggle() 131 | 132 | def testB(self): 133 | with when(Dog).waggle().thenReturn('Sure'): 134 | rex = Dog() 135 | assert rex.waggle() == 'Sure' 136 | 137 | verify(Dog).waggle() 138 | 139 | -------------------------------------------------------------------------------- /tests/verification_errors_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008-2016 Szczepan Faber, Serhiy Oplakanets, Herr Kaste 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | import pytest 22 | 23 | from mockito import (mock, when, verify, VerificationError, 24 | verifyNoMoreInteractions, verification) 25 | from mockito.verification import never 26 | 27 | 28 | class TestVerificationErrors: 29 | 30 | def testPrintsNicelyNothingIfNeverUsed(self): 31 | theMock = mock() 32 | with pytest.raises(VerificationError) as exc: 33 | verify(theMock).foo() 34 | assert str(exc.value) == ''' 35 | Wanted but not invoked: 36 | 37 | foo() 38 | 39 | ''' 40 | 41 | def testPrintsNicely(self): 42 | theMock = mock() 43 | theMock.foo('bar') 44 | theMock.foo(12, baz='boz') 45 | with pytest.raises(VerificationError) as exc: 46 | verify(theMock).foo(True, None) 47 | assert str(exc.value) == ''' 48 | Wanted but not invoked: 49 | 50 | foo(True, None) 51 | 52 | Instead got: 53 | 54 | foo('bar') 55 | foo(12, baz='boz') 56 | 57 | ''' 58 | 59 | def testPrintOnlySameMethodInvocationsIfAny(self): 60 | theMock = mock() 61 | theMock.foo('bar') 62 | theMock.bar('foo') 63 | 64 | with pytest.raises(VerificationError) as exc: 65 | verify(theMock).bar('fox') 66 | 67 | assert str(exc.value) == ''' 68 | Wanted but not invoked: 69 | 70 | bar('fox') 71 | 72 | Instead got: 73 | 74 | bar('foo') 75 | 76 | ''' 77 | 78 | def testPrintAllInvocationsIfNoInvocationWithSameMethodName(self): 79 | theMock = mock() 80 | theMock.foo('bar') 81 | theMock.bar('foo') 82 | 83 | with pytest.raises(VerificationError) as exc: 84 | verify(theMock).box('fox') 85 | 86 | assert str(exc.value) == ''' 87 | Wanted but not invoked: 88 | 89 | box('fox') 90 | 91 | Instead got: 92 | 93 | foo('bar') 94 | bar('foo') 95 | 96 | ''' 97 | 98 | 99 | def testPrintKeywordArgumentsNicely(self): 100 | theMock = mock() 101 | with pytest.raises(VerificationError) as exc: 102 | verify(theMock).foo(foo='foo', one=1) 103 | message = str(exc.value) 104 | # We do not want to guarantee any order of arguments here 105 | assert "foo='foo'" in message 106 | assert "one=1" in message 107 | 108 | def testPrintsOutThatTheActualAndExpectedInvocationCountDiffers(self): 109 | theMock = mock() 110 | when(theMock).foo().thenReturn(0) 111 | 112 | theMock.foo() 113 | theMock.foo() 114 | 115 | with pytest.raises(VerificationError) as exc: 116 | verify(theMock).foo() 117 | assert "\nWanted times: 1, actual times: 2" == str(exc.value) 118 | 119 | def testPrintsUnwantedInteraction(self): 120 | theMock = mock() 121 | theMock.foo(1, 'foo') 122 | with pytest.raises(VerificationError) as exc: 123 | verifyNoMoreInteractions(theMock) 124 | assert "\nUnwanted interaction: foo(1, 'foo')" == str(exc.value) 125 | 126 | def testPrintsNeverWantedInteractionsNicely(self): 127 | theMock = mock() 128 | theMock.foo() 129 | with pytest.raises(VerificationError) as exc: 130 | verify(theMock, never).foo() 131 | assert "\nUnwanted invocation of foo(), times: 1" == str(exc.value) 132 | 133 | 134 | class TestReprOfVerificationClasses: 135 | def testTimes(self): 136 | times = verification.Times(1) 137 | assert repr(times) == "" 138 | 139 | def testAtLeast(self): 140 | atleast = verification.AtLeast(2) 141 | assert repr(atleast) == "" 142 | 143 | def testAtMost(self): 144 | atmost = verification.AtMost(3) 145 | assert repr(atmost) == "" 146 | 147 | def testBetween(self): 148 | between = verification.Between(1, 2) 149 | assert repr(between) == "" 150 | -------------------------------------------------------------------------------- /tests/when2_test.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | 4 | from mockito import mock, when2, patch, spy2, verify 5 | from mockito.utils import newmethod 6 | 7 | 8 | import os 9 | 10 | 11 | pytestmark = pytest.mark.usefixtures("unstub") 12 | 13 | 14 | class Dog(object): 15 | def bark(self, sound): 16 | return sound 17 | 18 | def bark_hard(self, sound): 19 | return sound + '!' 20 | 21 | 22 | class TestMockito2: 23 | def testWhen2(self): 24 | rex = Dog() 25 | when2(rex.bark, 'Miau').thenReturn('Wuff') 26 | when2(rex.bark, 'Miau').thenReturn('Grrr') 27 | assert rex.bark('Miau') == 'Grrr' 28 | 29 | 30 | def testPatch(self): 31 | rex = Dog() 32 | patch(rex.bark, lambda sound: sound + '!') 33 | assert rex.bark('Miau') == 'Miau!' 34 | 35 | 36 | def testPatch2(self): 37 | rex = Dog() 38 | patch(rex.bark, rex.bark_hard) 39 | assert rex.bark('Miau') == 'Miau!' 40 | 41 | def testPatch3(self): 42 | rex = Dog() 43 | 44 | def f(self, sound): 45 | return self.bark_hard(sound) 46 | 47 | f = newmethod(f, rex) 48 | patch(rex.bark, f) 49 | 50 | assert rex.bark('Miau') == 'Miau!' 51 | 52 | def testAddFnWithPatch(self): 53 | rex = Dog() 54 | 55 | patch(rex, 'newfn', lambda s: s) 56 | assert rex.newfn('Hi') == 'Hi' 57 | 58 | 59 | class TestFancyObjResolver: 60 | def testWhen2WithArguments(self): 61 | # This test is a bit flaky bc pytest does not like a patched 62 | # `os.path.exists` module. 63 | when2(os.path.commonprefix, '/Foo').thenReturn(False) 64 | when2(os.path.commonprefix, '/Foo').thenReturn(True) 65 | when2(os.path.exists, '/Foo').thenReturn(True) 66 | 67 | assert os.path.commonprefix('/Foo') 68 | assert os.path.exists('/Foo') 69 | 70 | def testWhen2WithoutArguments(self): 71 | import time 72 | when2(time.time).thenReturn('None') 73 | assert time.time() == 'None' 74 | 75 | def testWhenSplitOnNextLine(self): 76 | # fmt: off 77 | when2( 78 | os.path.commonprefix, '/Foo').thenReturn(True) 79 | # fmt: on 80 | assert os.path.commonprefix('/Foo') 81 | 82 | def testEnsureWithWhen2SameLine(self): 83 | with when2(os.path.commonprefix, '/Foo'): 84 | pass 85 | 86 | def testEnsureWithWhen2SplitLine(self): 87 | # fmt: off 88 | with when2( 89 | os.path.commonprefix, '/Foo'): 90 | pass 91 | # fmt: on 92 | 93 | def testEnsureToResolveMethodOnClass(self): 94 | class A(object): 95 | class B(object): 96 | def c(self): 97 | pass 98 | 99 | when2(A.B.c) 100 | 101 | def testEnsureToResolveClass(self): 102 | class A(object): 103 | class B(object): 104 | pass 105 | 106 | when2(A.B, 'Hi').thenReturn('Ho') 107 | assert A.B('Hi') == 'Ho' 108 | 109 | 110 | def testPatch(self): 111 | patch(os.path.commonprefix, lambda m: 'yup') 112 | patch(os.path.commonprefix, lambda m: 'yep') 113 | 114 | assert os.path.commonprefix(Ellipsis) == 'yep' 115 | 116 | def testWithPatchGivenTwoArgs(self): 117 | with patch(os.path.exists, lambda m: 'yup'): 118 | assert os.path.exists('foo') == 'yup' 119 | 120 | assert not os.path.exists('foo') 121 | 122 | def testWithPatchGivenThreeArgs(self): 123 | with patch(os.path, 'exists', lambda m: 'yup'): 124 | assert os.path.exists('foo') == 'yup' 125 | 126 | assert not os.path.exists('foo') 127 | 128 | def testSpy2(self): 129 | spy2(os.path.exists) 130 | 131 | assert os.path.exists('/Foo') is False 132 | 133 | verify(os.path).exists('/Foo') 134 | 135 | class TestRejections: 136 | def testA(self): 137 | with pytest.raises(TypeError) as exc: 138 | when2(os) 139 | assert str(exc.value) == "can't guess origin of 'os'" 140 | 141 | cp = os.path.commonprefix 142 | with pytest.raises(TypeError) as exc: 143 | spy2(cp) 144 | assert str(exc.value) == "can't guess origin of 'cp'" 145 | 146 | ptch = patch 147 | with pytest.raises(TypeError) as exc: 148 | ptch(os.path.exists, lambda: 'boo') 149 | assert str(exc.value) == "could not destructure first argument" 150 | 151 | 152 | class TestFixIssue90: 153 | def testAddAttributeToDumbMock(self): 154 | m = mock() 155 | when2(m.foo).thenReturn('bar') 156 | assert m.foo() == 'bar' 157 | 158 | @pytest.mark.xfail(reason=( 159 | "strict mocks throw when accessing unconfigured attributes by design" 160 | )) 161 | def testAddAttributeToStrictMock(self): 162 | m = mock(strict=True) 163 | when2(m.foo).thenReturn('bar') 164 | assert m.foo() == 'bar' 165 | 166 | -------------------------------------------------------------------------------- /tests/when_interface_test.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | 4 | from mockito import (when, when2, expect, verify, patch, mock, spy2, 5 | verifyNoUnwantedInteractions) 6 | from mockito.invocation import InvocationError 7 | 8 | class Dog(object): 9 | def bark(self): 10 | pass 11 | 12 | 13 | class Unhashable(object): 14 | def update(self, **kwargs): 15 | pass 16 | 17 | def __hash__(self): 18 | raise TypeError("I'm immutable") 19 | 20 | 21 | @pytest.mark.usefixtures('unstub') 22 | class TestEnsureEmptyInterfacesAreReturned: 23 | 24 | def testWhen(self): 25 | whening = when(Dog) 26 | assert whening.__dict__ == {} 27 | 28 | def testExpect(self): 29 | expecting = expect(Dog) 30 | assert expecting.__dict__ == {} 31 | 32 | def testVerify(self): 33 | dummy = mock() 34 | verifying = verify(dummy) 35 | assert verifying.__dict__ == {} 36 | 37 | 38 | def testEnsureUnhashableObjectCanBeMocked(): 39 | obj = Unhashable() 40 | when(obj).update().thenReturn(None) 41 | 42 | 43 | class TestAnswerShortcuts: 44 | def testAssumeReturnNoneIfOmitted(self): 45 | dog = Dog() 46 | when(dog).bark().thenReturn().thenReturn(42) 47 | assert dog.bark() is None 48 | assert dog.bark() == 42 49 | 50 | def testRaiseIfAnswerIsOmitted(self): 51 | dog = Dog() 52 | with pytest.raises(TypeError) as exc: 53 | when(dog).bark().thenAnswer() 54 | assert str(exc.value) == "No answer function provided" 55 | 56 | def testAssumeRaiseExceptionIfOmitted(self): 57 | dog = Dog() 58 | when(dog).bark().thenRaise().thenReturn(42) 59 | with pytest.raises(Exception): 60 | dog.bark() 61 | assert dog.bark() == 42 62 | 63 | 64 | @pytest.mark.usefixtures('unstub') 65 | class TestPassAroundStrictness: 66 | 67 | def testReconfigureStrictMock(self): 68 | when(Dog).bark() # important first call, inits theMock 69 | 70 | when(Dog, strict=False).waggle().thenReturn('Sure') 71 | expect(Dog, strict=False).weggle().thenReturn('Sure') 72 | 73 | 74 | with pytest.raises(InvocationError): 75 | when(Dog).wuggle() 76 | 77 | with pytest.raises(InvocationError): 78 | when(Dog).woggle() 79 | 80 | rex = Dog() 81 | assert rex.waggle() == 'Sure' 82 | assert rex.weggle() == 'Sure' 83 | 84 | # For documentation; the initial strict value of the mock will be used 85 | # here. So the above when(..., strict=False) just assures we can 86 | # actually *add* an attribute to the mocked object 87 | with pytest.raises(InvocationError): 88 | rex.waggle(1) 89 | 90 | verify(Dog).waggle() 91 | verify(Dog).weggle() 92 | 93 | def testReconfigureLooseMock(self): 94 | when(Dog, strict=False).bark() # important first call, inits theMock 95 | 96 | when(Dog, strict=False).waggle().thenReturn('Sure') 97 | expect(Dog, strict=False).weggle().thenReturn('Sure') 98 | 99 | with pytest.raises(InvocationError): 100 | when(Dog).wuggle() 101 | 102 | with pytest.raises(InvocationError): 103 | when(Dog).woggle() 104 | 105 | rex = Dog() 106 | assert rex.waggle() == 'Sure' 107 | assert rex.weggle() == 'Sure' 108 | 109 | # For documentation; see test above. strict is inherited from the 110 | # initial mock. So we return `None` 111 | assert rex.waggle(1) is None 112 | 113 | verify(Dog).waggle() 114 | verify(Dog).weggle() 115 | 116 | 117 | class TestEnsureAddedAttributesGetRemovedOnUnstub: 118 | def testWhenPatchingTheClass(self): 119 | with when(Dog, strict=False).wggle(): 120 | pass 121 | 122 | with pytest.raises(AttributeError): 123 | Dog.wggle 124 | 125 | def testWhenPatchingAnInstance(self): 126 | dog = Dog() 127 | with when(dog, strict=False).wggle(): 128 | pass 129 | 130 | with pytest.raises(AttributeError): 131 | dog.wggle 132 | 133 | 134 | @pytest.mark.usefixtures('unstub') 135 | class TestDottedPaths: 136 | 137 | def testWhen(self): 138 | when('os.path').exists('/Foo').thenReturn(True) 139 | 140 | import os.path 141 | assert os.path.exists('/Foo') 142 | 143 | def testWhen2(self): 144 | when2('os.path.exists', '/Foo').thenReturn(True) 145 | 146 | import os.path 147 | assert os.path.exists('/Foo') 148 | 149 | def testExpect(self): 150 | expect('os.path', times=2).exists('/Foo').thenReturn(True) 151 | 152 | import os.path 153 | os.path.exists('/Foo') 154 | assert os.path.exists('/Foo') 155 | verifyNoUnwantedInteractions() 156 | 157 | def testPatch(self): 158 | dummy = mock() 159 | patch('os.path.exists', dummy) 160 | 161 | import os.path 162 | assert os.path.exists('/Foo') is None 163 | 164 | verify(dummy).__call__('/Foo') 165 | 166 | def testVerify(self): 167 | when('os.path').exists('/Foo').thenReturn(True) 168 | 169 | import os.path 170 | os.path.exists('/Foo') 171 | 172 | verify('os.path', times=1).exists('/Foo') 173 | 174 | def testSpy2(self): 175 | spy2('os.path.exists') 176 | 177 | import os.path 178 | assert not os.path.exists('/Foo') 179 | 180 | verify('os.path', times=1).exists('/Foo') 181 | --------------------------------------------------------------------------------