Mock a token for flask unit tests - firebase-admin-python SDK
Asked Answered
T

2

7

I am using the firebase-admin-python SDK to handle authentication between an iOS app and a flask backend (python). This is my backend authentication endpoint, following the firebase guide:

from flask import request
from firebase_admin import auth


def get():
    """accessed via '/api/authtoken' """
    try:
        fir_token = request.headers["Authorization"]
        decoded_token = auth.verify_id_token(fir_token)
        fir_auth_id = decoded_token["uid"]
    except:
        ...

How do I mock the fir_token for a unit test? How do I also mock auth.verify_id_token such that I don't need to actually connect to the firebase server?

Thegn answered 9/8, 2019 at 17:0 Comment(0)
A
4

Put the logic behind an interface.

class TokenVerifier(object):
    def verify(self, token):
        raise NotImplementedError()


class FirebaseTokenVerifier(TokenVerifier):
    def verify(self, token):
        return auth.verify_id_token(token)


class MockTokenVerifier(TokenVerifier):
    def verify(self, token):
         # Return mock object that will help pass your tests.
         # Or raise an error to simulate token validation failures.

Then make sure during unit tests your code uses the MockTokenVerifier.

It is also possible to create mock ID tokens, and stub out parts of the Admin SDK so that the auth.verify_id_token() runs normally during tests (see unit tests of the SDK). But I prefer the above solution since it's easier, cleaner and doesn't require messing with the internals of the SDK.

Alkyd answered 9/8, 2019 at 17:15 Comment(2)
Does this solution imply that I need to use if config["TESTING"] == True: before decoded_token = auth.verify_id_token(fir_token) to toggle the verifier?Thegn
I don't know what config is. Generally speaking, how you swap the verifier during testing is really up to you. I'd recommend structuring your code in a way, so there's some module or class-level property that you can set to specify the verifier. Ideally, you shouldn't have to have conditionals in your get() request handler.Alkyd
L
2

I achieved this using the patch decorator from unittest.mock library.

auth.py

...
from flask_restful import Resource, reqparse
from firebase_admin import auth

class Token(Resource):
    def __init__(self):
        self.parser = reqparse.RequestParser()
        self.parser.add_argument('token', type=str, required=True)

    # route for /api/auth/token
    def post(self):
        args = self.parser.parse_args()

        try:
            firebase_user = auth.verify_id_token(args['token'])
        except Exception:
            abort(401)

        # use firebase_user

test_auth.py

from unittest import mock


mock_firebase_user = {
    'user_id': 'firebasegenerateduserid',
    'email': '[email protected]',
    # ... add more firebase return values
}

# client is from conftest.py
def test_auth(client):
    with mock.patch('auth.auth.verify_id_token') as magic_mock:
        magic_mock.return_value = mock_firebase_user
        post_data = {
            'token': 'firebaserusertokenid'
        }

        response = client.post('/api/auth/token', data=post_data)
        assert response.status_code == 200
Leighton answered 13/3, 2020 at 13:59 Comment(1)
That's great, thank you! I had to use this for the patching though: mock.patch('firebase_admin.auth.verify_id_token') Landgravine

© 2022 - 2024 — McMap. All rights reserved.