Reputation: 55
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
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:
sys.modules
asname = getattr(sys.modules[module_name], imported_name)
for from module_name import imported_name [as asname]
Upvotes: 2