Skip to content

Commit 2f3f1f4

Browse files
committed
doc: extract howto examples to tests
The main reason for doing this is to test the examples. Some examples has been also fixed in this commit because of this.
1 parent 702b89e commit 2f3f1f4

20 files changed

Lines changed: 258 additions & 233 deletions

doc/background.rst

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,15 @@ using the library is not complicated.
4040

4141
Example:
4242

43-
.. code-block:: python
44-
45-
def test_query_params(httpserver):
46-
httpserver.expect_request("/foo", query_string={"user": "user1"}).respond_with_data(
47-
"OK"
48-
)
43+
.. literalinclude :: ../tests/examples/test_example_query_params1.py
44+
:language: python
4945
5046
It is simple in the most simple cases, but once the expectation is more
5147
specific, the line can grow significantly, so here the user is expected to put
5248
the literals into variables:
5349

54-
.. code-block:: python
55-
56-
def test_query_params(httpserver):
57-
httpserver.expect_request("/foo", query_string=expected_query).respond_with_data(
58-
"OK"
59-
)
60-
50+
.. literalinclude :: ../tests/examples/test_example_query_params2.py
51+
:language: python
6152
6253
If the user wants something more complex, classes are available for this which
6354
can be instantiated and then specified for the parameters normally accepting

doc/howto.rst

Lines changed: 30 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,13 @@ Matching query parameters
1515
To match query parameters, you must not included them to the URI, as this will
1616
not work:
1717

18-
.. code-block:: python
19-
20-
def test_query_params(httpserver):
21-
httpserver.expect_request("/foo?user=bar") # never do this
18+
.. literalinclude :: ../tests/examples/test_howto_query_params_never_do_this.py
19+
:language: python
2220
2321
There's an explicit place where the query string should go:
2422

25-
.. code-block:: python
26-
27-
def test_query_params(httpserver):
28-
httpserver.expect_request("/foo", query_string="user=bar")
23+
.. literalinclude :: ../tests/examples/test_howto_query_params_proper_use.py
24+
:language: python
2925
3026
The ``query_string`` is the parameter which does not contain the leading
3127
question mark ``?``.
@@ -43,15 +39,9 @@ can specify a dict for the ``query_string`` parameter (the naming may look a bit
4339
strange but we wanted to keep API compatibility and this dict matching feature
4440
was added later).
4541

46-
.. code-block:: python
47-
48-
def test_query_params(httpserver):
49-
httpserver.expect_request(
50-
"/foo", query_string={"user": "user1", "group": "group1"}
51-
).respond_with_data("OK")
5242

53-
assert requests.get("/foo?user=user1&group=group1").status_code == 200
54-
assert requests.get("/foo?group=group1&user=user1").status_code == 200
43+
.. literalinclude :: ../tests/examples/test_howto_query_params_dict.py
44+
:language: python
5545
5646
In the example above, both requests pass the test as we specified the expected
5747
query string as a dictionary.
@@ -70,9 +60,8 @@ request will not be handled.
7060
If this is not desired, you can specify a regexp object (returned by the
7161
``re.compile()`` call).
7262

73-
.. code:: python
74-
75-
httpserver.expect_request(re.compile("^/foo"), method="GET")
63+
.. literalinclude :: ../tests/examples/test_howto_regexp.py
64+
:language: python
7665
7766
The above will match every URI starting with "/foo".
7867

@@ -82,18 +71,9 @@ method which will receive the URI. All you need is to subclass from the
8271
string and should return a boolean value.
8372

8473

85-
.. code:: python
86-
87-
class PrefixMatch(URIPattern):
88-
def __init__(self, prefix: str):
89-
self.prefix = prefix
90-
91-
def match(self, uri):
92-
return uri.startswith(self.prefix)
93-
74+
.. literalinclude :: ../tests/examples/test_howto_url_matcher.py
75+
:language: python
9476
95-
def test_uripattern_object(httpserver: HTTPServer):
96-
httpserver.expect_request(PrefixMatch("/foo")).respond_with_json({"foo": "bar"})
9777
9878
Authentication
9979
--------------
@@ -121,50 +101,8 @@ parameters in the ``Authorization`` header value is arbitrary.
121101
By default, pytest-httpserver includes an Authorization header parser so the
122102
order of the parameters in the ``Authorization`` header does not matter.
123103

124-
.. code:: python
125-
126-
def test_authorization_headers(httpserver: HTTPServer):
127-
headers_with_values_in_direct_order = {
128-
"Authorization": (
129-
'Digest username="Mufasa",'
130-
'realm="testrealm@host.com",'
131-
'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",'
132-
'uri="/dir/index.html",'
133-
"qop=auth,"
134-
"nc=00000001,"
135-
'cnonce="0a4f113b",'
136-
'response="6629fae49393a05397450978507c4ef1",'
137-
'opaque="5ccc069c403ebaf9f0171e9517f40e41"'
138-
)
139-
}
140-
httpserver.expect_request(
141-
uri="/", headers=headers_with_values_in_direct_order
142-
).respond_with_data("OK")
143-
response = requests.get(
144-
httpserver.url_for("/"), headers=headers_with_values_in_direct_order
145-
)
146-
assert response.status_code == 200
147-
assert response.text == "OK"
148-
149-
headers_with_values_in_modified_order = {
150-
"Authorization": (
151-
"Digest qop=auth,"
152-
'username="Mufasa",'
153-
'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",'
154-
'uri="/dir/index.html",'
155-
"nc=00000001,"
156-
'realm="testrealm@host.com",'
157-
'response="6629fae49393a05397450978507c4ef1",'
158-
'cnonce="0a4f113b",'
159-
'opaque="5ccc069c403ebaf9f0171e9517f40e41"'
160-
)
161-
}
162-
response = requests.get(
163-
httpserver.url_for("/"), headers=headers_with_values_in_modified_order
164-
)
165-
assert response.status_code == 200
166-
assert response.text == "OK"
167-
104+
.. literalinclude :: ../tests/examples/test_howto_authorization_headers.py
105+
:language: python
168106
169107
JSON matching
170108
-------------
@@ -185,16 +123,8 @@ will fail and *pytest-httpserver* will proceed with the next registered matcher.
185123

186124
Example:
187125

188-
.. code:: python
189-
190-
def test_json_matcher(httpserver: HTTPServer):
191-
httpserver.expect_request("/foo", json={"foo": "bar"}).respond_with_data(
192-
"Hello world!"
193-
)
194-
resp = requests.get(httpserver.url_for("/foo"), json={"foo": "bar"})
195-
assert resp.status_code == 200
196-
assert resp.text == "Hello world!"
197-
126+
.. literalinclude :: ../tests/examples/test_howto_json_matcher.py
127+
:language: python
198128
199129
.. note::
200130
JSON requests usually come with ``Content-Type: application/json`` header.
@@ -222,29 +152,8 @@ value, and will be able to determine the matching.
222152

223153
You need to implement such a function and then use it:
224154

225-
.. code:: python
226-
227-
def case_insensitive_matcher(header_name: str, actual: str, expected: str) -> bool:
228-
if header_name == "X-Foo":
229-
return actual.lower() == expected.lower()
230-
else:
231-
return actual == expected
232-
233-
234-
def test_case_insensitive_matching(httpserver: HTTPServer):
235-
httpserver.expect_request(
236-
"/", header_value_matcher=case_insensitive_matcher, headers={"X-Foo": "bar"}
237-
).respond_with_data("OK")
238-
239-
assert (
240-
requests.get(httpserver.url_for("/"), headers={"X-Foo": "bar"}).status_code
241-
== 200
242-
)
243-
assert (
244-
requests.get(httpserver.url_for("/"), headers={"X-Foo": "BAR"}).status_code
245-
== 200
246-
)
247-
155+
.. literalinclude :: ../tests/examples/test_howto_case_insensitive_matcher.py
156+
:language: python
248157
249158
.. note::
250159
Header value matcher is the basis of the ``Authorization`` header parsing.
@@ -285,49 +194,18 @@ to the ``HeaderValueMatcher.DEFAULT_MATCHERS`` dict.
285194
In case you don't want to change the defaults, you can provide the
286195
``HeaderValueMatcher`` object itself.
287196

288-
.. code:: python
289-
290-
from pytest_httpserver import HeaderValueMatcher
291-
292-
293-
def case_insensitive_compare(actual: str, expected: str) -> bool:
294-
return actual.lower() == expected.lower()
295-
296-
297-
def test_own_matcher_object(httpserver: HTTPServer):
298-
matcher = HeaderValueMatcher({"X-Bar": case_insensitive_compare})
299-
300-
httpserver.expect_request(
301-
"/", headers={"X-Bar": "bar"}, header_value_matcher=matcher
302-
).respond_with_data("OK")
303-
304-
assert (
305-
requests.get(httpserver.url_for("/"), headers={"X-Bar": "bar"}).status_code
306-
== 200
307-
)
308-
assert (
309-
requests.get(httpserver.url_for("/"), headers={"X-Bar": "BAR"}).status_code
310-
== 200
311-
)
197+
.. literalinclude :: ../tests/examples/test_howto_header_value_matcher.py
198+
:language: python
312199
313200
Using custom request handler
314201
----------------------------
315202
In the case the response is not static, for example it depends on the request,
316203
you can pass a function to the ``respond_with_handler`` function. This function
317204
will be called with a request object and it should return a Response object.
318205

319-
.. code:: python
320-
321-
from werkzeug.wrappers import Request, Response
322-
from random import randint
323-
324-
325-
def test_expected_request_handler(httpserver: HTTPServer):
326-
def handler(request: Request):
327-
return Response(str(randint(1, 10)))
328-
329-
httpserver.expect_request("/foobar").respond_with_handler(handler)
330206

207+
.. literalinclude :: ../tests/examples/test_howto_custom_handler.py
208+
:language: python
331209
332210
The above code implements a handler which returns a random number between 1 and
333211
10. Not particularly useful but shows that the handler can return any computed
@@ -344,57 +222,15 @@ unhandled, you can call the ``check_handler_errors()`` method of the httpserver.
344222

345223
Two notable examples for this:
346224

347-
.. code:: python
348-
349-
def test_check_assertions_raises_handler_assertions(httpserver: HTTPServer):
350-
def handler(_):
351-
assert 1 == 2
352-
353-
httpserver.expect_request("/foobar").respond_with_handler(handler)
354-
355-
requests.get(httpserver.url_for("/foobar"))
356-
357-
# if you leave this "with" statement out, check_assertions() will break
358-
# the test by re-raising the assertion error caused by the handler
359-
# pytest will pick this exception as it was happened in the main thread
360-
with pytest.raises(AssertionError):
361-
httpserver.check_assertions()
362-
363-
httpserver.check_handler_errors()
364-
365-
366-
def test_check_handler_errors_raises_handler_error(httpserver: HTTPServer):
367-
def handler(_):
368-
raise ValueError("should be propagated")
369-
370-
httpserver.expect_request("/foobar").respond_with_handler(handler)
371-
372-
requests.get(httpserver.url_for("/foobar"))
373-
374-
httpserver.check_assertions()
375-
376-
# if you leave this "with" statement out, check_handler_errors() will
377-
# break the test with the original exception
378-
with pytest.raises(ValueError):
379-
httpserver.check_handler_errors()
380-
225+
.. literalinclude :: ../tests/examples/test_howto_check_handler_errors.py
226+
:language: python
381227
382228
If you want to call both methods (``check_handler_errors()`` and
383229
``check_assertions()``) you can call the ``check()`` method, which will call
384230
these.
385231

386-
387-
.. code:: python
388-
389-
def test_check_assertions(httpserver: HTTPServer):
390-
def handler(_):
391-
assert 1 == 2
392-
393-
httpserver.expect_request("/foobar").respond_with_handler(handler)
394-
395-
requests.get(httpserver.url_for("/foobar"))
396-
397-
httpserver.check()
232+
.. literalinclude :: ../tests/examples/test_howto_check.py
233+
:language: python
398234
399235
.. note::
400236
The scope of the errors checked by the ``check()`` method may
@@ -478,24 +314,8 @@ Behind the scenes it synchronizes the state of the server with the main thread.
478314

479315
Last, you need to assert on the ``result`` attribute of the context object.
480316

481-
.. code-block:: python
482-
483-
def test_wait_success(httpserver: HTTPServer):
484-
waiting_timeout = 0.1
485-
486-
with httpserver.wait(stop_on_nohandler=False, timeout=waiting_timeout) as waiting:
487-
requests.get(httpserver.url_for("/foobar"))
488-
httpserver.expect_oneshot_request("/foobar").respond_with_data("OK foobar")
489-
requests.get(httpserver.url_for("/foobar"))
490-
assert waiting.result
491-
492-
httpserver.expect_oneshot_request("/foobar").respond_with_data("OK foobar")
493-
httpserver.expect_oneshot_request("/foobaz").respond_with_data("OK foobaz")
494-
with httpserver.wait(timeout=waiting_timeout) as waiting:
495-
requests.get(httpserver.url_for("/foobar"))
496-
requests.get(httpserver.url_for("/foobaz"))
497-
assert waiting.result
498-
317+
.. literalinclude :: ../tests/examples/test_howto_wait_success.py
318+
:language: python
499319
500320
In the above code, all the request.get() calls could be in a different thread,
501321
eg. running in parallel, but the exit condition of the context object is to wait
@@ -509,19 +329,10 @@ If by any chance, you want to emulate network errors such as *Connection reset
509329
by peer* or *Connection refused*, you can simply do it by connecting to a random
510330
port number where no service is listening:
511331

512-
.. code-block:: python
513-
514-
import pytest
515-
import requests
516-
517-
518-
def test_connection_refused():
519-
# assumes that there's no server listening at localhost:1234
520-
with pytest.raises(requests.exceptions.ConnectionError):
521-
requests.get("http://localhost:1234")
522-
332+
.. literalinclude :: ../tests/examples/test_howto_timeout_requests.py
333+
:language: python
523334
524-
However connecting to the port where the httpserver had been started will still
335+
However, connecting to the port where the httpserver had been started will still
525336
succeed as the server is running continuously. This is working by design as
526337
starting/stopping the server is costly.
527338

@@ -699,5 +510,5 @@ to this, feel free to open an issue.
699510

700511
Example:
701512

702-
.. literalinclude :: ../tests/test_blocking_httpserver_howto.py
513+
.. literalinclude :: ../tests/examples/test_example_blocking_httpserver.py
703514
:language: python
File renamed without changes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def test_query_params(httpserver):
2+
httpserver.expect_request("/foo", query_string={"user": "user1"}).respond_with_data("OK")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def test_query_params(httpserver):
2+
expected_query = {"user": "user1"}
3+
httpserver.expect_request("/foo", query_string=expected_query).respond_with_data("OK")

0 commit comments

Comments
 (0)