Search code examples
pythonunit-testingflasknose

unit testing flask app with login_required


I have the following test case for my flask app setup

class AboutViewTest(BaseTestCase):
    def setUp(self):
        self.url = url_for('pages_app.about',_external=True)
        self.url_login_redirect = url_for('accounts_app.login')

    def test_render(self):
        # without login
        resp = self.client.get(self.url)
        self.assertRedirects(resp, self.url_login_redirect)

        # after login
        with self.app.test_client() as c:
            login_successful = self.login(username='user1', password="123456", client=c)
            self.assertTrue(login_successful)
            resp = c.get(self.url)
            self.assertStatus(resp, 200) # this fails

To test a view with @login_required

@pages_app.route('/about/')
@login_required
def about():
    return render_template('pages/about.html')


def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if g.user is None:
            return redirect(url_for('accounts_app.login', next=request.url))
        return f(*args, **kwargs)
    return decorated_function

The second test case fails (login required) as it gets redirected to login 302 != 200

How can I test this view with to pass both the cases(with and without login)? Wht are the best practices to do this?


Solution

  • The best approach is probably to populate your g.user variable as suggested in the Flask docs:

    def get_user():
        user = getattr(g, 'user', None)
        if user is None:
            user = fetch_current_user_from_database()
            g.user = user
        return user
    

    Obviously, you have to define fetch_current_user_from_database(), where you'll instantiate a user (you have a User class, I assume) and return it. Then just call get_user() to instantly have a user logged in when needed.

    As an alternative, you can add a flag to your settings to disable login and use it in your login_required method when testing.

    For example:

    from flask import current_app
    
    def login_required(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            if not current_app.config.get('LOGIN_DISABLED', False) and g.user is None:
                return redirect(url_for('accounts_app.login', next=request.url))
            return f(*args, **kwargs)
        return decorated_function
    

    Then in your test:

    def test_render(self):
        # without login
        resp = self.client.get(self.url)
        self.assertRedirects(resp, self.url_login_redirect)
    
        self.app.config['LOGIN_DISABLED'] = True
    
        resp = c.get(self.url)
        self.assertStatus(resp, 200)