9

I wanted to mock validate_token decorator while writing unit test for one of view

#views.py
from third_part.module import vaidate_token
from setting import config
class myViews:
     @validate_token([config['issuer'], config['secret_key']])
     def get_data():
         #Do stuff
         return json.loads(data)

Here validate_token is a thirtd_party module to authorize request and the token is issued by third party so I don't want execute validate_token decorator for my tests

below are my sample test code.

test_views.py

@patch('views.validate_token', lambda x: x)
def test_get_data(self):
    endpoint = '/app/get_data'
    res = self.client.get(endpoint)
    assert res.status_code==200

I tried to mock while running tests
But its not working as expected, , its giving 401 error.

how can I mock/patch decorator for tests anything am missing here

Thanks in advance.

sunny
  • 479
  • 1
  • 9
  • 18
  • Related, but more specific: [How can I remove “@oidc.login_required” for unit testing from a view?](https://stackoverflow.com/q/57251349/562769) – Martin Thoma Jul 29 '19 at 10:06

1 Answers1

13

Here an example which can help you. Structure of files below.

app.py

from flask import Flask
from third_part.example import validate_token

app = Flask(__name__)

@app.route('/')
@validate_token()
def index():
    return 'hi'

if __name__ == '__main__':
    app.run(debug=True)

/third_part/example.py

from functools import wraps

def validate_token():
    def validate(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            raise Exception('Token error. Just for example')
            return func(*args, **kwargs)
        return wrapper
    return validate

tests.py:

from app import app

app.testing = True

def test_index():
    with app.test_client() as client:
        client.get('/')

Run our tests.py(just make sure that decorator works):

    @wraps(func)
    def wrapper(*args, **kwargs):
>       raise Exception('Token error. Just for example')
E       Exception: Token error. Just for example

First way how to skip decorator(using patch). tests.py:

from functools import wraps
from mock import patch

def mock_decorator():
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            return f(*args, **kwargs)
        return decorated_function
    return decorator

patch('third_part.example.validate_token', mock_decorator).start()
# !important thing - import of app after patch()
from app import app

app.testing = True

def test_index():
    with app.test_client() as client:
        client.get('/')

Second way(without patch). tests.py:

from functools import wraps
from third_part import example

def mock_decorator():
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            return f(*args, **kwargs)
        return decorated_function
    return decorator
# !important thing - import of app after replace
example.validate_token = mock_decorator

from app import app
app.testing = True

def test_index():
    with app.test_client() as client:
        client.get('/')

Run our test.py(in 2 ways):

tests.py .                                                               [100%]

=========================== 1 passed in 0.09 seconds ===========================

Summarize. As you can see, very important thing is when you replace the function. By the way, you trying to patch validate_token of views module, but not third_part.module

Hope this helps.

Danila Ganchar
  • 10,266
  • 13
  • 49
  • 75
  • Thank you this was helpful. But what if the `mock_decorator` has a functionality that depends on the `app` ? In my case I want to replace my original `authorize` decorator with a fake one that returns the first user from the test db. I can't use the db without the app context. How to accomplish this using your approach? – Peter Sobhi Apr 09 '19 at 12:47
  • @PeterSobhi I understand the problem, but could you show your `mock_decorator`? I need to see a code. Just give me a link to your question. – Danila Ganchar Apr 09 '19 at 14:55
  • Here's a link to the question by my colleague: https://stackoverflow.com/questions/55597216/authenticate-flask-unit-test-client-from-another-service-microservices-architec – Peter Sobhi Apr 09 '19 at 16:25
  • 1
    Thank you so much, I was searching for hours to get this to work and you sorted it... `cache = Cache() cache.memoize = mock.patch('flask_caching.Cache.memoize', mocked_memoize).start()` then I can just pass the cache object into the class which uses the decorator... – NotoriousPyro Mar 23 '22 at 22:14