# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['pytest_call_checker']

package_data = \
{'': ['*']}

install_requires = \
['pytest>=7.1.3,<8.0.0']

entry_points = \
{'pytest11': ['pytest_call_checker = pytest_call_checker']}

setup_kwargs = {
    'name': 'pytest-call-checker',
    'version': '1.0.4',
    'description': 'Small pytest utility to easily create test doubles',
    'long_description': '# pytest-call-checker\n\n[![Deployed to PyPI](https://img.shields.io/pypi/pyversions/pytest-call-checker?logo=pypi&logoColor=white)](https://pypi.org/pypi/pytest-call-checker)\n[![GitHub Repository](https://img.shields.io/github/stars/ewjoachim/pytest-call-checker?logo=github)](https://github.com/ewjoachim/pytest-call-checker/)\n[![Continuous Integration](https://img.shields.io/github/workflow/status/ewjoachim/pytest-call-checker/CI?logo=github)](https://github.com/ewjoachim/pytest-call-checker/actions?workflow=CI)\n[![Coverage](https://raw.githubusercontent.com/ewjoachim/pytest-call-checker/python-coverage-comment-action-data/badge.svg)](https://github.com/ewjoachim/pytest-call-checker/tree/python-coverage-comment-action-data)\n[![MIT License](https://img.shields.io/github/license/ewjoachim/pytest-call-checker?logo=open-source-initiative&logoColor=white)](https://github.com/ewjoachim/pytest-call-checker/blob/main/LICENSE)\n[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v1.4%20adopted-ff69b4.svg)](https://github.com/ewjoachim/pytest-call-checker/blob/main/LICENSE/CODE_OF_CONDUCT.md)\n\n\n`pytest-call-checker` is a pytest plugin providing a `checker` fixture\nthat allows creating test doubles with interesting properties.\n\n## Stating the problem\n\nImagine you\'re testing a library that makes HTTP calls to an API. If you follow\nusual practices of separating I/O from logic (dependency injection), chances\nare that the code functions you will test will receive a HTTP client object and\nwill route HTTP calls to this object. The idea being that in real context, they\nwill receive a real HTTP client, and in test code, they will receive a fake\nclient that will not actually perform HTTP calls.\n\nThis fake client is a test double. What you\'ll usually want to check is that:\n\n- The function made exactly as many calls as expected;\n- The arguments of each call are correct.\n\nAdditionally, you want to be able to control the output of each call given the\ninput and/or the order of the call.\n\nThe usual way to do this is with mocks or specialized libs such as\n[requests-mock](https://requests-mock.readthedocs.io/en/latest/),\n[responses](https://github.com/getsentry/responses), etc.\n\nThis library provides another way, that can also work in other contexts: for\nexample, maybe you call subprocesses and you would like to not call them\nin your unit tests.\n\n## The idea\n\nThis library provides a low-level fixture named `checker` that you can use\nas a foundation for your own specialized fixtures.\n\nWhen defining your `checker` fixture, you will tell it what the calls you\'re\nexpecting look like, and how to create appropriate responses.\n\nIn your tests, you\'ll register one or more expected calls using\n`checker.register`, each with the corresponding response.\n\nWhen you call the function under test, you\'ll pass it the `checker` instance.\nEach time the instance is called, we\'ll check that the call arguments match one\nof the expected calls you registered, and answer with the corresponding response.\n\nAt the end of the test, if all the expected calls were not received, we will\nensure the test fails.\n\nYou\'ll find concrete examples below.\n\n## Code\n\n### Installation\n\n```console\n$ pip install pytest-call-checker\n```\n\n### Simple usage: Http client\n\nIn this example, we create a test double for `httpx.Client`.\nIn the test, we register a call to the `get` method of the\nclient.\nWe then run our function. We can be sure that:\n- Our function called the get method\n- With the right arguments\n- And nothing else on the client\n- And when it called the method, it received a fake response that looked like\n  what we wanted.\n\n```python\nimport httpx\nimport pytest\n\ndef get_example_text(client: httpx.Client):\n    return client.get("https://example.com").text\n\n@pytest.fixture\ndef http_client(checker):\n    class FakeHttpClient(checker.Checker):\n\n    return checker(checker.Checker(\n        call=httpx.Client.request,\n        response=httpx.Response\n    ))\n\ndef test_get_example_text(http_client):\n    http_client.register.get("https://example.com")(text="foo")\n\n    assert get_example_text(client=http_client) == "foo"\n\n```\n\n### More advanced usage: Subprocess\n\n\nIn this example, we create a test double not for an object but for a callable\n(`subprocess.run`). This usage is slightly more advanced because in order to\ninstantiate our response object, `subprocess.CompletedProcess` object, we need\nto know the command `args` that were passed to the `subprocess.run` call. This\ncould be slightly annoying if we needed to repeat the `args` both in the call\nand the `response` so we\'ll introduce a technique here that will let us keep\nour tests as simple as possible.\n\n\n```python\n\ndef get_date(run: Callable):\n    return run(["date"]).stdout\n\n\n@pytest.fixture\ndef subprocess_run(checker):\n    class SubprocessRun(checker.Checker):\n        call = subprocess.run\n\n        def response(self, returncode, stdout=None, stderr=None):\n            # When the response is implemented as a method of a `checker.Checker`\n            # subclass, you can access `self.match`. This lets you construct\n            # the output object using elements from the input kwargs, see\n            # the `args` argument below.\n            return subprocess.CompletedProcess(\n                args=self.match.match_kwargs["popenargs"],\n                returncode=returncode,\n                stdout=stdout,\n                stderr=stderr,\n            )\n\n    return checker(SubprocessRun())\n\n\ndef test_get_date(subprocess_run):\n    subprocess_run.register(["date"])(returncode=0, stdout="2022-01-01")\n\n    assert get_date(run=subprocess_run) == "2022-01-01"\n\n```\n\nAs you can see, in the code above, there are two ways to create you\n`checker.Checker` instance:\n\n- You can create an instance directly\n- Or subclass `checker.Checker`. In this case, instead of passing `call` and\n  `response` in the constructor, you can also define them as methods.\n\nIn case you go the subclass route, this lets you access additional elements\nthrough `self` if you need it. This is an advanced usage, we\'ll do our best to\navoid breaking this, but it touches the inner workings of our objects so if you\nreally do complicated things, it might break.\n\nThe most useful attributes of `checker.Checker` that you can access in\n`def reponse(self, ...)` should be:\n\n- `self.match`: The `Match` object that was associated with the response we\'re\n  building.\n- `self.request_kwargs`: The keyword arguments with which the test double\n  was called\n\n### Other interesting features\n\n#### Matching with functions\n\nSometimes, you can\'t perform an exact match on the input parameters, but you\nstill want to check some properties in order to perform the match.\n\nIn this case, use a callable instead of the value for the argument you want\nto check.\n\n```python\n\nimport uuid\n\ndef create_object(client: ApiClient) -> ApiResponse:\n    # Create object with a random ID\n    return client.create(id=uuid.uuid4())\n\n\n@pytest.fixture\ndef api_client(checker):\n    class FakeAPIClient(checker.Checker):\n\n    return checker(checker.Checker(\n        call=ApiClient,\n        response=ApiResponse\n    ))\n\n\ndef test_get_date(api_client):\n    def is_uuid(value):\n        return isinstance(value, uuid.UUID)\n\n    api_client.register(id=is_uuid)()\n\n    assert create_object(client=api_client) == ApiResponse()\n\n```\n\n\n#### Allowing calls in different order\n\nBy default, it\'s expected that the calls will be done in the same order as\nthey were registered. You can actually change that by passing `ordered=False`\nwhen creating the fixture.\n\n```python\n\nimport uuid\n\ndef fetch_objects(client: ApiClient, ids: set[int]) -> set[ApiResponse]:\n    # Because it\'s in a set, we can\'t be sure of the call order\n    return {\n        client.get(id=id)\n        for id in ids\n    }\n\n\n@pytest.fixture\ndef api_client(checker):\n    class FakeAPIClient(checker.Checker):\n\n    return checker(checker.Checker(\n        call=ApiClient,\n        response=ApiResponse,\n        ordered=False,\n    ))\n\n\ndef test_get_date(api_client):\n\n    api_client.register(id=1)(id=1)\n    api_client.register(id=2)(id=2)\n\n    responses = fetch_objects(client=api_client, ids={1, 2})\n    assert responses == {ApiResponse(id=1), ApiResponse(id=2)}\n\n```\n\n## Caveats\n\nSome things are not ideal, and could be improved:\n\n- There is no way to mark one call as optional. It\'s assumed that if the\n  tests are reproducible, then we should always know whether they\'ll do\n  calls or not.\n- It\'s probably not possible yet to create test doubles for modules. The usual\n  way of doing dependency injection is through functions or class instances.\n',
    'author': 'Joachim Jablon',
    'author_email': 'ewjoachim@gmail.com',
    'maintainer': 'None',
    'maintainer_email': 'None',
    'url': 'https://github.com/ewjoachim/pytest-call-checker',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'entry_points': entry_points,
    'python_requires': '>=3.7,<4.0',
}


setup(**setup_kwargs)
