web-dev-qa-db-fra.com

Essayer de se moquer de datetime.date.today (), mais ne fonctionne pas

Quelqu'un peut-il me dire pourquoi cela ne fonctionne pas?

>>> import mock
>>> @mock.patch('datetime.date.today')
... def today(cls):
...  return date(2010, 1, 1)
...
>>> from datetime import date
>>> date.today()
datetime.date(2010, 12, 19)

Peut-être que quelqu'un pourrait suggérer un meilleur moyen?

130
Belmin Fernandez

Il y a quelques problèmes.

Tout d'abord, la façon dont vous utilisez mock.patch N'est pas tout à fait correcte. Utilisé comme décorateur, il remplace la fonction/classe donnée (dans ce cas, datetime.date.today) Par un objet Mock uniquement dans la fonction décorée . Donc, seulement dans votre today(), datetime.date.today Sera une fonction différente, ce qui ne semble pas être ce que vous voulez.

Ce que vous voulez vraiment semble plus ressembler à ceci:

@mock.patch('datetime.date.today')
def test():
    datetime.date.today.return_value = date(2010, 1, 1)
    print datetime.date.today()

Malheureusement, cela ne fonctionnera pas:

>>> test()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "build/bdist.macosx-10.6-universal/Egg/mock.py", line 557, in patched
  File "build/bdist.macosx-10.6-universal/Egg/mock.py", line 620, in __enter__
TypeError: can't set attributes of built-in/extension type 'datetime.date'

Cela échoue car Python sont immuables - voir cette réponse pour plus de détails.

Dans ce cas, je sous-classerais datetime.date et créerais la fonction appropriée:

import datetime
class NewDate(datetime.date):
    @classmethod
    def today(cls):
        return cls(2010, 1, 1)
datetime.date = NewDate

Et maintenant tu pourrais faire:

>>> datetime.date.today()
NewDate(2010, 1, 1)
105
Daniel G

Une autre option consiste à utiliser https://github.com/spulec/freezegun/

Installez-le:

pip install freezegun

Et utilisez-le:

from freezegun import freeze_time

@freeze_time("2012-01-01")
def test_something():

    from datetime import datetime
    print(datetime.now()) #  2012-01-01 00:00:00

    from datetime import date
    print(date.today()) #  2012-01-01

Cela affecte également les autres appels datetime dans les appels de méthode d'autres modules:

autre_module.py:

from datetime import datetime

def other_method():
    print(datetime.now())    

main.py:

from freezegun import freeze_time

@freeze_time("2012-01-01")
def test_something():

    import other_module
    other_module.other_method()

Et enfin:

$ python main.py
# 2012-01-01
136
Mehdi Behrooz

Pour ce qui en vaut la peine, les documents Mock parlent spécifiquement de datetime.date.today, et il est possible de le faire sans avoir à créer une classe fictive:

https://docs.python.org/3/library/unittest.mock-examples.html#partial-mocking

>>> from datetime import date
>>> with patch('mymodule.date') as mock_date:
...     mock_date.today.return_value = date(2010, 10, 8)
...     mock_date.side_effect = lambda *args, **kw: date(*args, **kw)
...
...     assert mymodule.date.today() == date(2010, 10, 8)
...     assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)
...
87
kpup

Je suppose que je suis arrivé un peu en retard pour cela, mais je pense que le principal problème ici est que vous corrigez directement datetime.date.today et que, d’après la documentation, cela est faux.

Vous devez corriger la référence importée dans le fichier contenant la fonction testée, par exemple.

Disons que vous avez un fichier functions.py où vous avez les éléments suivants:

import datetime

def get_today():
    return datetime.date.today()

alors, dans votre test, vous devriez avoir quelque chose comme ça

import datetime
import unittest

from functions import get_today
from mock import patch, Mock

class GetTodayTest(unittest.TestCase):

    @patch('functions.datetime')
    def test_get_today(self, datetime_mock):
        datetime_mock.date.today = Mock(return_value=datetime.strptime('Jun 1 2005', '%b %d %Y'))
        value = get_today()
        # then assert your thing...

J'espère que cela aide un peu.

32
iferminm

Pour ajouter à solution de Daniel G :

from datetime import date

class FakeDate(date):
    "A manipulable date replacement"
    def __new__(cls, *args, **kwargs):
        return date.__new__(date, *args, **kwargs)

Cela crée une classe qui, lorsqu'elle est instanciée, retournera un objet datetime.date normal, mais qui peut également être modifiée.

@mock.patch('datetime.date', FakeDate)
def test():
    from datetime import date
    FakeDate.today = classmethod(lambda cls: date(2010, 1, 1))
    return date.today()

test() # datetime.date(2010, 1, 1)
29
eternicode

J'ai fait face à la même situation il y a quelques jours et ma solution consistait à définir une fonction dans le module à tester et à simuler celle-ci:

def get_date_now():
    return datetime.datetime.now()

Aujourd'hui, j'ai découvert FreezeGun , et cela semble couvrir joliment ce cas.

from freezegun import freeze_time
import datetime
import unittest


@freeze_time("2012-01-14")
def test():
    assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)
6
Hito_kun

Vous pouvez utiliser l'approche suivante, basée sur la solution de Daniel G. Celui-ci a l'avantage de ne pas interrompre la vérification de type avec isinstance(d, datetime.date).

import mock

def fixed_today(today):
    from datetime import date

    class FakeDateType(type):
        def __instancecheck__(self, instance):
            return isinstance(instance, date)

    class FakeDate(date):
        __metaclass__ = FakeDateType

        def __new__(cls, *args, **kwargs):
            return date.__new__(date, *args, **kwargs)

        @staticmethod
        def today():
            return today

    return mock.patch("datetime.date", FakeDate)

Fondamentalement, nous remplaçons la classe datetime.date Basée sur C par notre propre sous-classe python), qui produit des instances datetime.date Originales et répond aux requêtes isinstance() exactement comme natif datetime.date.

Utilisez-le comme gestionnaire de contexte dans vos tests:

with fixed_today(datetime.date(2013, 11, 22)):
    # run the code under test
    # note, that these type checks will not break when patch is active:
    assert isinstance(datetime.date.today(), datetime.date)

Une approche similaire peut être utilisée pour se moquer de la fonction datetime.datetime.now().

5
Andrey Lebedev

Le moyen le plus simple pour moi est de faire ceci:

from unittest import patch, Mock

def test():
    datetime_mock = Mock(wraps=datetime)
    datetime_mock.now = Mock(return_value=datetime(1999, 1, 1)
    patch('target_module.datetime', new=datetime_mock).start()

ATTENTION pour cette solution: toutes les fonctionnalités de datetime module du target_module cessera de fonctionner.

4
frx08

De manière générale, vous auriez datetime ou peut-être datetime.date importé quelque part dans un module. Un moyen plus efficace de se moquer de la méthode serait de le patcher sur le module qui l'importe. Exemple:

a.py

from datetime import date

def my_method():
    return date.today()

Ensuite, pour votre test, l'objet simulé lui-même serait transmis en tant qu'argument à la méthode de test. Vous pouvez configurer la maquette avec la valeur de résultat souhaitée, puis appeler votre méthode sous test. Ensuite, vous affirmeriez que votre méthode a fait ce que vous voulez.

>>> import mock
>>> import a
>>> @mock.patch('a.date')
... def test_my_method(date_mock):
...     date_mock.today.return_value = mock.sentinel.today
...     result = a.my_method()
...     print result
...     date_mock.today.assert_called_once_with()
...     assert mock.sentinel.today == result
...
>>> test_my_method()
sentinel.today

Un mot d'avertissement. Il est certainement possible d'aller à la mer avec des moqueries. Lorsque vous le faites, vos tests sont plus longs, plus difficiles à comprendre et impossibles à maintenir. Avant de vous moquer d'une méthode aussi simple que datetime.date.today, demandez-vous si vous avez vraiment besoin de vous moquer de lui. Si votre test est court et précis et fonctionne correctement sans se moquer de la fonction, vous pouvez simplement regarder un détail interne du code que vous testez plutôt qu'un objet que vous devez simuler.

3
jpmc26

Plusieurs solutions sont discutées dans http://blog.xelnor.net/python-mocking-datetime/ . En résumé:

Objet fictif - Simple et efficace mais rompt les vérifications isinstance ():

target = datetime.datetime(2009, 1, 1)
with mock.patch.object(datetime, 'datetime', mock.Mock(wraps=datetime.datetime)) as patched:
    patched.now.return_value = target
    print(datetime.datetime.now())

Mock class

import datetime
import mock

real_datetime_class = datetime.datetime

def mock_datetime_now(target, dt):
    class DatetimeSubclassMeta(type):
        @classmethod
        def __instancecheck__(mcs, obj):
            return isinstance(obj, real_datetime_class)

    class BaseMockedDatetime(real_datetime_class):
        @classmethod
        def now(cls, tz=None):
            return target.replace(tzinfo=tz)

        @classmethod
        def utcnow(cls):
            return target

    # Python2 & Python3 compatible metaclass
    MockedDatetime = DatetimeSubclassMeta('datetime', (BaseMockedDatetime,), {})

    return mock.patch.object(dt, 'datetime', MockedDatetime)

Utilisé comme:

with mock_datetime_now(target, datetime):
   ....
1
eddygeek

Pour ceux qui utilisent pytest with mocker, voici comment je me suis moqué de datetime.datetime.now(), qui est très similaire à la question initiale.

test_get_now(mocker):
    datetime_mock = mocker.patch("blackline_accounts_import.datetime",)
    datetime_mock.datetime.now.return_value=datetime.datetime(2019,3,11,6,2,0,0)

    now == function_being_tested()  # run function

    assert now == datetime.datetime(2019,3,11,6,2,0,0)

La maquette doit essentiellement être configurée pour renvoyer la date spécifiée. Vous ne pouvez pas appliquer directement sur l'objet datetime.

0
Daniel Butler

J'ai implémenté la méthode @ user3016183 à l'aide d'un décorateur personnalisé:

def changeNow(func, newNow = datetime(2015, 11, 23, 12, 00, 00)):
    """decorator used to change datetime.datetime.now() in the tested function."""
    def retfunc(self):                             
        with mock.patch('mymodule.datetime') as mock_date:                         
            mock_date.now.return_value = newNow
            mock_date.side_effect = lambda *args, **kw: datetime(*args, **kw)
            func(self)
    return retfunc

Je pensais que cela pourrait aider quelqu'un un jour ...

0
DainDwarf

J'ai réalisé ce travail en important datetime en tant que realdatetime et en remplaçant les méthodes dont j'avais besoin dans la maquette par les méthodes réelles:

import datetime as realdatetime

@mock.patch('datetime')
def test_method(self, mock_datetime):
    mock_datetime.today = realdatetime.today
    mock_datetime.now.return_value = realdatetime.datetime(2019, 8, 23, 14, 34, 8, 0)
0
Adam McKenna

Voici un autre moyen de simuler datetime.date.today() avec un bonus supplémentaire: le reste des fonctions datetime continue de fonctionner, car l'objet simulé est configuré pour envelopper le module datetime d'origine:

from unittest import mock, TestCase

import foo_module

class FooTest(TestCase):

    @mock.patch(f'{foo_module.__name__}.datetime', wraps=datetime)
    def test_something(self, mock_datetime):
        # mock only datetime.date.today()
        mock_datetime.date.today.return_value = datetime.date(2019, 3, 15)
        # other calls to datetime functions will be forwarded to original datetime

Notez l'argument wraps=datetime À mock.patch() - lorsque foo_module Utilise d'autres fonctions datetime à côté de date.today(), elles seront transmises à l'original. enveloppé datetime module.

0
mrts

Vous pourriez peut-être utiliser votre propre méthode "today ()" que vous corrigerez si nécessaire. Exemple avec moqueur utcnow () peut être trouvé ici: https://bitbucket.org/k_bx/blog/src/tip/source/fr_posts/2012-07-13-double-call-hack.rst?at = par défaut

0
Kostiantyn Rybnikov

Il est possible de simuler des fonctions à partir du module datetime sans ajouter side_effects

import mock
from datetime import datetime
from where_datetime_used import do

initial_date = datetime.strptime('2018-09-27', "%Y-%m-%d")
with mock.patch('where_datetime_used.datetime') as mocked_dt:
    mocked_dt.now.return_value = initial_date
    do()
0
Daniil Mashkin