Intentando simular datetime.date.today (), pero no funciona

¿Alguien puede decirme por qué esto no está funcionando?

>>> 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) 

Tal vez alguien podría sugerir una mejor manera?

Hay algunos problemas.

En primer lugar, la forma en que usas mock.patch no es del todo correcta. Cuando se usa como decorador, reemplaza la función / clase dada (en este caso, datetime.date.today ) con un objeto Mock solo dentro de la función decorada . Por lo tanto, solo dentro de su today() datetime.date.today será una función diferente, que no parece ser lo que desea.

Lo que realmente quieres parece ser más así:

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

Desafortunadamente, esto no funcionará:

 >>> test() Traceback (most recent call last): File "", line 1, in  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' 

Esto falla porque los tipos incorporados de Python son inmutables; consulte esta respuesta para obtener más detalles.

En este caso, crearía una subclase datetime.date y crearía la función correcta:

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

Y ahora podrías hacer:

 >>> datetime.date.today() NewDate(2010, 1, 1) 

Otra opción es usar https://github.com/spulec/freezegun/

Instalarlo:

 pip install freezegun 

Y úsalo:

 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 

También afecta a otras llamadas de fecha y hora en las llamadas de método de otros módulos:

otro_módulo.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() 

Y finalmente:

 $ python main.py # 2012-01-01 

Para lo que vale la pena, los documentos simulados hablan de datetime.date.today específicamente, y es posible hacerlo sin tener que crear una clase ficticia:

http://www.voidspace.org.uk/python/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) ... 

Creo que llegué un poco tarde para esto, pero creo que el principal problema aquí es que está actualizando datetime.date.today directamente y, según la documentación, esto está mal.

Debe parchear la referencia importada en el archivo donde se encuentra la función probada, por ejemplo.

Digamos que tienes un archivo functions.py donde tienes lo siguiente:

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

Entonces, en tu prueba, deberías tener algo como esto.

 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... 

Espero que esto ayude un poco.

Para agregar a la solución 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) 

Esto crea una clase que, cuando se crea una instancia, devolverá un objeto datetime.date normal, pero que también se puede cambiar.

 @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) 

Puede utilizar el siguiente enfoque, basado en la solución Daniel G. Esta tiene la ventaja de no interrumpir la comprobación de tipos con 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) 

Básicamente, reemplazamos la clase datetime.date basada en C con nuestra propia subclase de python, que produce instancias originales de datetime.date y responde a isinstance() de isinstance() exactamente como datetime.date nativo.

Úsalo como administrador de contexto en tus pruebas:

 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) 

Se puede utilizar un enfoque similar para datetime.datetime.now() función datetime.datetime.now() .

Hace un par de días enfrenté la misma situación y mi solución fue definir una función en el módulo para probar y simplemente burlarme de eso:

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

Hoy me enteré de FreezeGun , y parece que cubre este caso a la perfección .

 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) 

La forma más fácil para mí es hacer esto:

 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() 

PRECAUCIÓN para esta solución: todas las funciones del datetime module del target_module dejarán de funcionar.

En términos generales, usted tendría datetime o quizás datetime.date importados en un módulo en algún lugar. Una forma más efectiva de burlarse del método sería parchearlo en el módulo que lo está importando. Ejemplo:

a.py

 from datetime import date def my_method(): return date.today() 

Luego, para su prueba, el objeto simulado se pasaría como un argumento al método de prueba. Debería configurar el simulacro con el valor de resultado que desea y luego llamar a su método a prueba. Entonces afirmarías que tu método hizo lo que quieres.

 >>> 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 

Una palabra de advertencia. Es ciertamente posible ir por la borda con burla. Cuando lo hace, hace que sus pruebas sean más largas, más difíciles de entender e imposibles de mantener. Antes de burlarte de un método tan simple como datetime.date.today , pregúntate si realmente necesitas burlarte de él. Si su prueba es corta y al punto y funciona bien sin burlarse de la función, puede que solo esté mirando un detalle interno del código que está probando en lugar de un objeto que necesita burlarse.

Se analizan varias soluciones en http://blog.xelnor.net/python-mocking-datetime/ . En resumen:

Objeto simulado: simple y eficiente, pero rompe las verificaciones de instancias ():

 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()) 

Clase de simulacro

 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) 

Usar como:

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

Tal vez podría usar su propio método “today ()” que aplicará cuando sea necesario. Se puede encontrar un ejemplo con utcnow () mocking aquí: https://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at=default

Implementé el método @ user3016183 usando un decorador personalizado:

 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 

Pensé que eso podría ayudar a alguien algún día …

Es posible side_effects funciones desde el módulo datetime sin agregar 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() 

Aquí hay otra forma de simular datetime.date.today() con una ventaja adicional de que el rest de las funciones de datetime continúen funcionando, ya que el objeto simulado está configurado para envolver el módulo original de datetime :

 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.datetime(2019, 3, 15) # other calls to datetime functions will be forwarded to original datetime 

Tenga en cuenta el argumento wraps=datetime para mock.patch() : cuando foo_module utiliza otras funciones de datetime además de date.today() , se reenviarán al módulo datetime envuelto original.