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

How should I assert no requests made to httpserver #35

Open
rvkhakhkhar opened this issue Aug 3, 2020 · 14 comments
Open

How should I assert no requests made to httpserver #35

rvkhakhkhar opened this issue Aug 3, 2020 · 14 comments

Comments

@rvkhakhkhar
Copy link

No description provided.

@csernazs
Copy link
Owner

csernazs commented Aug 3, 2020

You can check the log attribute of the httpserver object. This contains all the request - response pairs (in a 2-element tuple) which were processed.
If that's empty, it means that no requests have been made:

def test_norequests(httpserver):
    # code which may have made requests or not
    assert len(httpserver.log) > 0, "At least one request should be made"

or:

def test_norequests(httpserver):
    # code which may have made requests or not
    assert httpserver.log, "At least one request should be made"

@csernazs csernazs closed this as completed Aug 3, 2020
@rvkhakhkhar
Copy link
Author

Is it good idea to have some method like expect_no_requeast on httpserver?

@csernazs
Copy link
Owner

csernazs commented Aug 3, 2020

If this function is useful for you, you can implement it in conftest.py or at other places, so it will be available.

Personally, I think checking that there was at least one http request made is error prone, as it does not say anything about what kind of http request was made (provided you have more than one handlers), and the original idea of this library was to check the client code by asserting to some value which is known only to the server.

Eg:

def test_expected_request_json(httpserver: HTTPServer):
    httpserver.expect_request("/foobar").respond_with_json({"foo": "bar"})
    assert requests.get(httpserver.url_for("/foobar")).json() == {'foo': 'bar'}

Here, the client (which is the requests library in this case) should return the {"foo": "bar"}" dict, and this dict is only known to the server. So we can be entirely sure that the client made a http request by checking the data it returned. If by some accident the requests` was written in a way that it would return the dictionary in question without making any requests, then the test should be adjusted to avoid this situation by using different data.

@csernazs csernazs reopened this Aug 3, 2020
@rvkhakhkhar
Copy link
Author

rvkhakhkhar commented Aug 3, 2020

I can understand the case for at least one request made. I'm talking about a case where I want to assert that given set of conditions I want httpserver to receive no requests at all. I can certainly add that case in conftest.py but if the package itself has some good way to tell that no requests received

@csernazs
Copy link
Owner

csernazs commented Aug 3, 2020

I see.
This is the default. If you have no handlers and your client makes any request, then it is an error.
You need to call the check_assertions() method at the end, this will raise AssertionError.

def test_expected_request_json(httpserver):
###    httpserver.expect_request("/foobar").respond_with_json({"foo": "bar"})
    requests.get(httpserver.url_for("/foobar")) # this will get http status 500 describing the issue
    httpserver.check_assertions()

@csernazs csernazs closed this as completed Aug 3, 2020
@rvkhakhkhar
Copy link
Author

Thanks @csernazs

@jgehrcke
Copy link

Should expect_oneshot_request() expect that precisely one request was made to it? I was expecting that. Of course it's fine to add assert len(httpserver.log) == 1. But yeah, I was kind of surprised that it's not the default to error out if a oneshot handler didn't see a request.

@csernazs
Copy link
Owner

Yes, that's a good question. Originally it was implemented to ensure that the client is doing no more than a single request to that endpoint. It is similar to the ordered handlers except the order does not matter for the oneshot.

If I was implementing this from zero, probably consider any leftover oneshot (and ordered) handlers as an error, but now it is a change which would break the tests already using this.

You can also check for the leftover handlers: ordered_handlers and oneshot_handlers attributes containing the handlers and once the handler is used it will be removed from these.

If it would make your life easier a new method can be added to httpserver to check these.

@csernazs csernazs reopened this Apr 21, 2023
@GKTheOne
Copy link

Hi @csernazs, (Thanks for pytest-httpserver 👍)

I found this looking for how to check for unused handlers, and assert httpserver.ordered_handlers == [] and/or assert httpserver.oneshot_handlers == [] does the job quite nicely for me.
I'm butting into the conversation to say that what would make life easier for me would be a RequestHandler representation, (probably just dispatching to the the RequestHandler.matcher (RequestMatcher) repr).
I think this would make it easier for me to see where the expected/actual requests diverged.
Currently the pytest output only lets me know the number of leftover request handlers.

>       assert httpserver.oneshot_handlers == []
E       assert [<pytest_httpserver.httpserver.RequestHandler object at 0x000001F46F9765D0>] == []
E         Left contains one more item: <pytest_httpserver.httpserver.RequestHandler object at 0x000001F46F9765D0>
E         Full diff:
E         - []
E         + [<pytest_httpserver.httpserver.RequestHandler object at 0x000001F46F9765D0>]

@csernazs
Copy link
Owner

hi @GKTheOne ,

I think this is a very good idea. We just need to decide what to include to the __repr__.

Something like that:

<RequestHandler GET /foobar>

(so it would be the method and the URI)

We would not include the json or data as it can be arbitrary length. Same for headers and query string, but actually query string could be possibly included.

<RequestHandler GET /foobar?param1=value1&param2=value2>

Something like this...?

We could optionally include the response status or some other data from the response, but I think we should keep this short and brief (response status code is just "200 OK", that is short, the response body is long).

What do you think?

@csernazs
Copy link
Owner

I found that requestmatcher already have a __repr__ defined like this:

def __repr__(self):
"""
Returns the string representation of the object, with the known parameters.
"""
class_name = self.__class__.__name__
retval = "<{} ".format(class_name)
retval += (
"uri={uri!r} method={method!r} query_string={query_string!r} "
"headers={headers!r} data={data!r} json={json!r}>"
).format_map(self.__dict__)
return retval

We could re-use this code, but I think it is way too verbose, especially if you have json or data specified for the matcher.

So we can either re-use this or write a new one which is more brief. What do you think?

@GKTheOne
Copy link

I'll point to re-using the RequestMatcher code. I hear you, in that the string representation could be overly long, but I think that's ok.
I know that I'm using request matchers where the only difference is a single query string parameter, and I would want to be able to detect that difference when I'm looking at a list of leftover request handlers.
Also, I think it would be less effort to have the RequestHandler __repr__ stringify, at least it's matcher property, in the same manner as other __repr__ methods (ie, the RequestMatcher one above), (which would then implicitly delegate to RequestMatcher.__repr__).

@csernazs
Copy link
Owner

hi @GKTheOne , #273 has been released with version 1.0.9 to pypi with the additional repr.

@GKTheOne
Copy link

Thanks @csernazs. You are awesome 👍

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

No branches or pull requests

4 participants