Source code for kyoukai.testing
"""
Testing helpers for Kyoukai.
"""
import random
from io import BytesIO
from asphalt.core import Context
from werkzeug.wrappers import Request, Response
from kyoukai.app import Kyoukai
from kyoukai.blueprint import Blueprint
from kyoukai.wsgi import to_wsgi_environment
[docs]class _TestingBpCtxManager(object):
"""
A context manager that is returned from :meth:`~.TestKyoukai.testing_bp`. When entered, this will produce a new
Blueprint object, that is then set onto the test application as the root blueprint.
After exiting, it will automatically restore the old root Blueprint onto the application, allowing complete
isolation of individual test routes away from eachother.
"""
def __init__(self, app: 'TestKyoukai'):
self.app = app
self._old_root = self.app.root
def __enter__(self) -> Blueprint:
id = str(random.randint(0, 1000))
name = "bp-{}".format(id)
b = Blueprint(name)
self.app._root_bp = b
return b
def __exit__(self, exc_type, exc_val, exc_tb):
# reset the root blueprint
del self.app._root_bp
self.app._root_bp = self._old_root
if exc_type:
return False
return True
[docs]class TestKyoukai(Kyoukai):
"""
A special subclass that allows you to easily test your Kyoukai-based app.
"""
def __init__(self, *args, base_context: Context = None, **kwargs):
"""
:param base_context: The base context to use for all request testing.
"""
super().__init__(*args, **kwargs)
self.base_context = base_context
[docs] @classmethod
def wrap_existing_app(cls, other_app: Kyoukai, base_context: Context = None):
"""
Wraps an existing app in a test frame.
This allows easy usage of writing unit tests:
.. code:: python
# main.py
kyk = Kyoukai("my_app")
# test.py
testing = TestKyoukai.wrap_existing_app(other_app)
# use testing as you would normally
:param other_app: The application object to wrap.
Internally, this creates a new instance of ourselves, then sets the ``process_request``
of the subclass to the copied object.
This means whenever ``inject_request`` is called, it will use the old app's
process_request to run with,
which will use the environment of the previous instance.
Of course, if the old app has any side effects upon process_request, these side effects
will happen when the testing application runs as well, as the old app is completely
copied over.
:param base_context: The base context to use for this.
"""
new_object = cls("test_app", base_context=base_context)
new_object.original_app = other_app
new_object.process_request = other_app.process_request
return new_object
[docs] def testing_bp(self) -> _TestingBpCtxManager:
"""
Context handler that allows ``with TestKyoukai.testing_bp() as bp:``
You can then register items onto this new root blueprint until __exit__, which will then destroy the blueprint.
"""
return _TestingBpCtxManager(self)
[docs] async def inject_request(self, headers: dict, url: str, method: str = "GET", body: str = None) -> Response:
"""
Injects a request into the test client.
This will automatically create the correct context.
:param headers: The headers to use.
:param body: The body to use.
:param url: The URL to use.
:param method: The method to use.
:return: The result.
"""
if body is not None:
body = BytesIO(body.encode())
e = to_wsgi_environment(headers, method, url, http_version="1.1", body=body)
e["SERVER_NAME"] = ""
e["SERVER_PORT"] = ""
r = Request(e)
# for testing blueprints, etc
# slow but it's a test so oh well
self.finalize()
return await self.process_request(r, self.base_context)