KGB33
KGB33

Reputation: 55

Mocking Password hash for testing

I'm trying to speed up testing for my flask app by mocking the password hashing for the majority of the tests. I'm using pytest and Python 3.8 on windows.

I've tried the following:

import pytest
import werkzeug.security

@pytest.fixture(autouse=True)
def mock_password_hash(monkeypatch, request):
    if "no_mock_password_hash" in request.keywords:
        pass
    else:
        def mock_hash(*args, **kwargs):
            return "123456"

         monkeypatch.setattr(werkzeug.security, 'generate_password_hash', mock_hash)

and

import pytest
import werkzeug.security
from unittest import mock


@pytest.fixture(autouse=True)
def mock_password_hash(request):
    """
    Mocks the built in method hashlib.pbkdf2 to decrease test runtime.
    """
    if "no_mock_password_hash" in request.keywords:
        yield None
    else:
        with mock.patch.object(
            werkzeug.security, 'generate_password_hash', return_value="123456"
        ) as _mocked_hash:
            yield _mocked_hash

I've tested both of the above fixtures using this test:

def test_hash_autouse():
    g = Guest(0, 'username', 'password', 'name', 'email')
    assert g.password == '123456'

and both times fails without an error:

    def test_hash_autouse():
        g = Guest(0, 'username', 'password', 'name', 'email')
>       assert g.password == '123456'
E       AssertionError: assert 'pbkdf2:sha25...b4faf022af81c' == '123456'
E         - pbkdf2:sha256:150000$M1zININg$559128a3793e155eb472d1c1e7f686b7c8cb802a296d6a8b38ab4faf022af81c
E         + 123456

and my guest model:

from dataclasses import dataclass

from flask_login import UserMixin
from flask_pymongo import ObjectId
from werkzeug.security import check_password_hash, generate_password_hash

@dataclass
class Guest(UserMixin):
    _id: ObjectId
    username: str
    _password: str
    name: str
    email: str

    def __post_init__(self):
        # Sets password hash on user registration
        # When class is reinstantiated from DB password is not re-hashed
        if "pbkdf2:sha256:" not in self._password:
            self._password = generate_password_hash(self._password)

    @property
    def password(self):
        return self._password

    @password.setter
    def password(self, val):
        self._password = generate_password_hash(val)

    @property
    def id(self):
        return self._id

    @id.setter
    def id(self, val):
        self._id = val

    def check_password(self, password):
        return check_password_hash(self._password, password)

Any Ideas? Thanks.

Upvotes: 1

Views: 1466

Answers (1)

anthony sottile
anthony sottile

Reputation: 70097

Since monkeypatch.setattr works similarly to unittest.mock.patch.object the same advice applies

from that:

[monkeypatch.setattr] works by (temporarily) changing the object that a name points to with another one. There can be many names pointing to any individual object, so for patching to work you must ensure that you patch the name used by the system under test.

In this case, a from import introduces a new name pointing at the object that you're trying to patch so you need to be sure to patch it in that module

You have two choices for fixing this, either use import werkzeug.security in your application and keep your test code the same or adjust your patch to patch the functions out of the model module instead

Note that a from import is ~essentially equivalent to:

  1. make the module globally available in sys.modules
  2. assign asname = getattr(sys.modules[module_name], imported_name) for from module_name import imported_name [as asname]

Upvotes: 2

Related Questions