How to mocking created time in sqlalchemy?

2020-07-11 06:50发布

问题:

I use SQLAlchemy in my Flask application. In my tests I try mocking auto time create and update for my entries. But I don't understand how it work for SQLAlchemy. For example I try mocking datetime creating with freezgun, but is failure. For example:

class Entry(db.Model):
    __tablename__ = 'entries'
    created = db.Column(db.DateTime(), default=db.func.now())
    updated = db.Column(db.DateTime(), default=db.func.now(), onupdate=db.func.now())

class ViewTestCase(AppliactionTestCase):
    def test(self):
        with freeze_time("2014-06-01 16:00:00"):
            db.session.add(Entry())
            db.session.commit()
        entry = db.session.query(Entry).first()
        self.assertEqual(entry.created, datetime(2014, 6, 1, 16, 0, 0))

And one more question. How forced save entry without changes, if I need refresh updated for entry?

回答1:

Freezegun only patches datetime.datetime.now. You can solve it in one of two ways:

  1. Use mock.patch to patch db.func.now(), or (simpler, in my opinion),
  2. Use default=datetime.datetime.now, so freezegun will patch it correctly.


回答2:

sqlalchemy provide the hook sqlalchemy.event api to patch a query session.

prerequisite: flask-sqlalchemy, pytest, freezengun, contextlib

with the example with flask-sqlalchemy which @prokoptesev used:

class Entry(db.Model):
    __tablename__ = 'entries'
    created = db.Column(db.DateTime(), default=db.func.now())
    updated = db.Column(db.DateTime(), default=db.func.now(), onupdate=db.func.now())

Just write a pytest fixtures to patch the row create time:

import datetime
from flask_sqlalchemy import event
from contextlib import contextmanager
from freezegun import freeze_time

import Entry
# if you want the code be commonly used, change Entry to db.Model instead


@contextmanager
def patch_time(time_to_freeze, tick=True):
    with freeze_time(time_to_freeze, tick=tick) as frozen_time:
        def set_timestamp(mapper, connection, target):
           now = datetime.datetime.now()
           if hasattr(target, 'created'):
               target.created = now
           if hasattr(target, 'updated'):
               target.updated = now
        event.listen(Entry, 'before_insert', set_timestamp,
                     propagate=True)
        yield frozen_time
        event.remove(Entry, 'before_insert', set_timestamp)


@pytest.fixture(scope='function')
def patch_current_time():
    return patch_time

Then write a pytest type test case:

def test_patch_insert_time(patch_current_time, assertions):
    with patch_current_time("2014-06-01 16:00:00", tick=False):
        db.session.add(Entry())
        db.session.commit()
    entry = db.session.query(Entry).first()
    assert entry.created == datetime(2014, 6, 1, 16, 0, 0)

If somebody use unittools or other test package, you can also use the patch_time function directly:

class ViewTestCase(AppliactionTestCase):
    def test(self):
        with patch_time("2014-06-01 16:00:00", tick=False):
            db.session.add(Entry())
            db.session.commit()
        entry = db.session.query(Entry).first()
        self.assertEqual(entry.created, datetime(2014, 6, 1, 16, 0, 0))

happy coding.