Skip to content

Testing

Testing can be really tricky if a library adds bunch of middle-wares and decorators in your application. However, aegis includes some testing utilities to help you to have better control in your application state.

Lets dive into some code!

First, we will create a really simple app with an authentication logic in it. In this example, user will never be able to reach to the "/" endpoint.

# examples/testing/app.py

from aiohttp import web
from aegis import login_required, BasicAuth

class BasicAuthenticator(BasicAuth):

    async def authenticate(self, request: web.Request) -> dict:
        pass


@login_required
async def protected(request):
    return web.json_response({'hello': 'user'})

def create_app():
    app = web.Application()
    app.router.add_get('/', protected)

    BasicAuthenticator.setup(app)
    return app

if __name__ == "__main__":
    app = create_app()
    web.run_app(app)

Now, lets write some test to get the authentication error response.

We will define our test case by inheriting from aiohttp's AioHTTPTestCase and overriding the get_application method.

# examples/testing/test_app.py

from aiohttp.test_utils import AioHTTPTestCase

# import the app factory
from app import create_app


class AppTestCase(AioHTTPTestCase):

    async def get_application(self):
        app = create_app()
        return app

Next, we will create a test method that sends a get request to the protected / route and validates the response's status code.

    @unittest_run_loop
    async def test_protected_route_without_credentials(self):
        # AioHTTPTestCase provides us a client to send requests. 
        resp = await self.client.request("GET", "/")

        assert resp.status == 200

If we run this with pytest, it will fail with this message.

=================================== FAILURES ===================================
_______________ AppTestCase.test_protected_route_without_credentials _______________

self = <test_app.AppTestCase testMethod=test_protected_route_without_credentials>

    @unittest_run_loop
    async def test_protected_route_without_credentials(self):
        resp = await self.client.request("GET", "/")
>       assert resp.status == 200
E       AssertionError: assert 401 == 200
E        +  where 401 = <ClientResponse(http://127.0.0.1:59273/) [401 Unauthorized]>\n<CIMultiDictProxy('Content-Type': 'application/json; charset=utf-8', 'Content-Length': '276', 'Date': 'Mon, 06 May 2019 22:50:50 GMT', 'Server': 'Python/3.7 aiohttp/3.5.4')>\n.status

Since this application does not use any real authentication logic. We have to bypass the authentication system completely. To do this, We need to mock our authenticator with aegis.test_utils.MockAuthenticator. We can do it by simply calling the MockAuthenticator.setup method with our àpp instance.

class AppTestCase(AioHTTPTestCase):

    async def get_application(self):
        app = create_app()
        MockAuthenticator.setup(app)
        return app

By calling the setup method, MockAuthenticator will mock the BasicAuthenticator that we have been using in our app. MockAuthenticator is almost the same with our previous authenticator, in addition to that, it also has some extra features to help us to bypass the authentication system.

bypass_auth(user)

We only need the bypass_auth method to control the authentication logic. It is a contextmanager. So you can disable the permission control multiple times in your test suite. To use the bypass_auth, we first need to reach to our mocked authenticator. And then, we can control the authentication by calling the mocked_authenticator.bypass_auth method with a test user.

    @unittest_run_loop
    async def test_protected_route_without_credentials(self):
        mocked_authenticator = self.app["authenticator"]
        mock_user = {"permissions": ("user",)}

        with mocked_authenticator.bypass_auth(user=mock_user):
            resp = await self.client.request("GET", "/")
            assert resp.status == 200

        resp = await self.client.request("GET", "/")
        assert resp.status == 401

Have fun!