Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow usage with httpx.MockTransport #167

Open
CarliJoy opened this issue Nov 4, 2024 · 4 comments
Open

Allow usage with httpx.MockTransport #167

CarliJoy opened this issue Nov 4, 2024 · 4 comments
Labels
question Further information is requested

Comments

@CarliJoy
Copy link

CarliJoy commented Nov 4, 2024

In certain use cases, pytest_httpx is used only to mock a single httpx client, specifically one responsible for connecting to a cluster of servers for service A. However, while mocking this specific client, we still want the flexibility to make normal, non-mocked requests to other services without impacting the entire environment.

Previously, we initialized HTTPXMock directly, but with recent changes, this is no longer supported. While the focus on clean interfaces is understandable, this change limits the ability to easily create single mocked clients while keeping other requests unaltered.

To address this, I propose adding support for a MockTransport, as outlined below:

TCallable = TypeVar("TCallable", bound=Callable)

def copy_docs(source_func: Callable) -> Callable[[TCallable], TCallable]:
    def apply_docs(target_func: TCallable) -> TCallable:
        target_func.__doc__ == source_func.__doc__

class MockedTransport(httpx.MockTransport):
    def __init__(
        self,
        assert_all_responses_were_requested: bool = True,
        assert_all_requests_were_expected: bool = True,
        can_send_already_matched_responses: bool = False,
    ):
        _pool = None  # enusre _proxy_url does not fail
        options = _HTTPXMockOptions(
            can_send_already_matched_responses=can_send_already_matched_responses,
            assert_all_responses_were_requested=assert_all_responses_were_requested,
            assert_all_requests_were_expected=assert_all_requests_were_expected,
        )
        self.mock = HTTPXMock(options)
        super().__init__(lambda request: self.mock._handle_request(self, request))

    # TODO copy call signature
    # see https://stackoverflow.com/a/71968448/3813064 or
    #     https://github.com/python/cpython/pull/121693
    @copy_docs(HTTPXMock.add_response)
    def add_response(self, *args, **kwargs) -> None:
        self.mock.add_response(*args, **kwargs)

    @copy_docs(HTTPXMock.add_callback)
    def add_callback(self, *args, **kwargs) -> None:
        self.mock.add_callback(*args, **kwargs)

    @copy_docs(HTTPXMock.add_exception)
    def add_exception(self, *args, **kwargs) -> None:
        self.mock.add_exception(*args, **kwargs)

The MockedTransport class extends integrate smoothly with HTTPXMock, allowing targeted client mocking while enabling other clients to make live requests as usual.

@Colin-b
Copy link
Owner

Colin-b commented Nov 4, 2024

Hello @CarliJoy ,

I am not sure to follow, why can't this be achieved with the should_mock option set on the full test suite?

@Colin-b Colin-b added the question Further information is requested label Nov 4, 2024
@CarliJoy
Copy link
Author

CarliJoy commented Nov 5, 2024

Hi colin-b,

Got it, we were using an older version with only the non_mocked_hosts option. Thanks for the heads-up!

We still need to patch a specific client for our use case. We provide a pytest fixture as a plugin for our service, and users often want to mock additional services with pytest_httpx.

However, using a global mocking state creates issues:

  • If should_mock is globally set, users can’t mock additional services.
  • If users define their own should_mock, it overrides our fixture, leaving service A unmocked.

Additionally, different service tests might require varying options for assert_all_responses_were_requested, assert_all_requests_were_expected, or can_send_already_matched_responses. Without the ability to mock specific httpx clients, handling these cases is challenging.

@Colin-b
Copy link
Owner

Colin-b commented Nov 5, 2024

Your should_mock callable could rely on some shared variable (a list of urls, hosts, headers, etc... that you would base yourself upon to know if you should mock or not) that your clients would be free to update (thus keeping the previous setup you want to use as common). The value is evaluated at request time so this should work just fine as in the following:

shared_mocked_hosts = ["default_mocked_host"]

def should_mock(request: httpx.Request) -> bool:
    return request.url.host in shared_mocked_hosts

The other options are evaluated only for mocked requests so it should be fine as well I assume?

@CarliJoy
Copy link
Author

CarliJoy commented Dec 23, 2024

Sorry for the late reply.

The suggested solution sounds very cumbersome for our use case.
We have multiple API client projects that offer the ability to mock request to real server using bundled pytest testing plugins.
Even finding a good place for this shared variables will be hard.
Their pytest testing plugins will be used by yet other projects that will use differents API clients in the same test.

Having a good httpx.MockTransport would allow for proper isolation of this different API client projects.

Or expressed differently: Having the possibility the transport of selected clients instead of patching transports globally would be a very nice thing for more complex API tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants