Http server test python

Start a temporary http server locally with Pytest

By setting up a local server when executing a unit test, it is possible to perform tests that do not depend on the outside.

Convenient for API and crawler development.

—Describe http server start / end processing as fixture in conftest.py —Call a fixture in a test function

Implementation

conftest.py

 import pytest from http.server import ( HTTPServer as SuperHTTPServer, SimpleHTTPRequestHandler ) import threading class HTTPServer(SuperHTTPServer): """ Class for wrapper to run SimpleHTTPServer on Thread. Ctrl +Only Thread remains dead when terminated with C. Keyboard Interrupt passes. """ def run(self): try: self.serve_forever() except KeyboardInterrupt: pass finally: self.server_close() @pytest.fixture() def http_server(): host, port = '127.0.0.1', 8888 url = f'http://:/index.html' # serve_Run forever under thread server = HTTPServer((host, port), SimpleHTTPRequestHandler) thread = threading.Thread(None, server.run) thread.start() yield url #Transition to test here #End thread server.shutdown() thread.join() 

Transfer control to the test function with the yield statement. The setUp and tearDown in the unit test are before and after the yield statement, respectively.

Place the content in the execution directory.

index.html

Use fixture ( http_server ) in test function

test_httpserver.py

 import requests def test_index(http_server): url = http_server response = requests.get(url) assert response.text == 'Hello pytest!' 

Execution result

$ pytest --setup-show test_httpserver.py ========================================= test session starts =========================================platform linux -- Python 3.8.1, pytest-5.3.3, py-1.8.1, pluggy-0.13.1 rootdir: /home/skokado/workspace/sandbox collected 1 item test_httpserver.py SETUP F http_server test_httpserver.py::test_index (fixtures used: http_server). TEARDOWN F http_server ========================================== 1 passed in 0.60s ========================================== 

You can trace the generation of fixtures with the —setup-show option. You can see that the test_index function uses the fixture http_server .

Читайте также:  Exception in thread main java lang noclassdeffounderror org apache logging log4j logmanager

Other

Bankushi (@vaaaaanquish), I used it as a reference.

Источник

pytest-httpserver 1.0.8

This library is designed to help to test http clients without contacting the real http server. In other words, it is a fake http server which is accessible via localhost can be started with the pre-defined expected http requests and their responses.

Example

Handling a simple GET request

   You can also use the library without pytest. There's a with statement to ensure that the server is stopped.
  Please find the API documentation at https://pytest-httpserver.readthedocs.io/en/latest/.

Features

You can set up a dozen of expectations for the requests, and also what response should be sent by the server to the client.

Requests

There are three different types:

  • permanent: this will be always served when there’s match for this request, you can make as many HTTP requests as you want
  • oneshot: this will be served only once when there’s a match for this request, you can only make 1 HTTP request
  • ordered: same as oneshot but the order must be strictly matched to the order of setting up

You can also fine-tune the expected request. The following can be specified:

  • URI (this is a must)
  • HTTP method
  • headers
  • query string
  • data (HTTP body of the request)
  • JSON (HTTP body loaded as JSON)

Responses

Once you have the expectations for the request set up, you should also define the response you want to send back. The following is supported currently:

  • respond arbitrary data (string or bytearray)
  • respond a json (a python dict converted in-place to json)
  • respond a Response object of werkzeug
  • use your own function

Similar to requests, you can fine-tune what response you want to send:

Behave support

Using the BlockingHTTPServer class, the assertion for a request and the response can be performed in real order. For more info, see the test, the howto and the API documentation.

Missing features

Donation

If you want to donate to this project, you can find the donate button at the top of the README.

Currently, this project is based heavily on werkzeug. Werkzeug does all the heavy lifting behind the scenes, parsing HTTP request and defining Request and Response objects, which are currently transparent in the API.

Источник

Testing python BaseHttpServer

While the development of https://www.agalera.eu/standalone-app-raspberry-pi/ I needed to use python’s BaseHttpServer and inject some dependencies into it.

It turns out, there’s no easy way of doing that. Moreover, I wanted to achieve 100% code coverage testing, so I should found a way of testing that code.

Here’s the code I need to test:

import socketserver from http import server class DogFeederServer(server.BaseHTTPRequestHandler): def __init__(self, camera_output, call_dog, servo, *args, **kwargs): self.camera_output = camera_output self.call_dog = call_dog self.servo = servo # BaseHTTPRequestHandler calls do_GET **inside** __init__ . # So we have to call super().__init__ after setting attributes. super().__init__(*args, **kwargs) def do_GET(self): if self.path == "/stream.mjpg": self.send_response(200) # do some magic with HTTP Streaming else: self.send_error(404) self.end_headers() def do_POST(self): if self.path == "/api/call": if self.call_dog(): self.send_response(200) else: self.send_response(500) elif self.path == "/api/treat": self.servo.open_and_close() self.send_response(200) else: self.send_error(404) self.end_headers() class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer): allow_reuse_address = True daemon_threads = True 

As you can see, the code is really simple.

The problem comes when you realise there are no easy way of calling the constructor of the server and pass the dependencies

Passing dependencies on the constructor

Hopefully I discovered this StackOverflow post where someone has experience the same issue: https://stackoverflow.com/questions/21631799/how-can-i-pass-parameters-to-a-requesthandler

I really like the approach of the “partial” application: we pass the arguments before and once the app is created with the arguments, is passed to the server:

address = ("", 8000) handler = partial( DogFeederServer, camera_output, call_dog, servo, ) server = StreamingServer(address, handler) server.serve_forever() 

Once we have the “partial” approach, we could easily provide mocks for the dependencies in the tests

Test the server

The only way of testing the base HTTP server I found is to create some sort of “integration testing”: provide mocks to the server but actually start the HTTP server. To test the whole logic, we could use requests library to do the HTTP calls:

import socket from functools import partial from threading import Thread from unittest import TestCase from unittest.mock import MagicMock import requests from dogfeeder.server import DogFeederServer, StreamingServer class ServerTest(TestCase): def setUp(self): super(ServerTest, self).setUp() self.get_free_port() self.camera_output_mock = MagicMock() self.call_dog_mock = MagicMock() self.servo_mock = MagicMock() address = ("", self.mock_server_port) handler = partial( DogFeederServer, self.camera_output_mock, self.call_dog_mock, self.servo_mock, ) self.mock_server = StreamingServer(address, handler) # Start running mock server in a separate thread. # Daemon threads automatically shut down when the main process exits. self.mock_server_thread = Thread(target=self.mock_server.serve_forever) self.mock_server_thread.setDaemon(True) self.mock_server_thread.start() def test_servo_open_close(self): url = f"http://localhost:self.mock_server_port>/api/treat" response = requests.post(url) self.servo_mock.open_and_close.assert_called_once() assert response.status_code == 200 def test_invalid_path(self): url = f"http://localhost:self.mock_server_port>/unknown" response = requests.post(url) assert response.status_code == 404 response = requests.get(url) assert response.status_code == 404 def tearDown(self): super(ServerTest, self).tearDown() def get_free_port(self): s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM) s.bind(("localhost", 0)) __, port = s.getsockname() s.close() self.mock_server_port = port 

The key here is to start a daemon thread (that will die when the test ends) to start the HTTP server

Источник

Оцените статью