Intentando burlarse de datetime.date.today (), pero no funciona

Respuestas:

125

Hay algunos problemas

En primer lugar, la forma en que está utilizando mock.patchno es del todo correcta. Cuando se usa como decorador, reemplaza la función / clase dada (en este caso datetime.date.today) con un Mockobjeto solo dentro de la función decorada . Entonces, solo dentro de usted today()habrá datetime.date.todayuna 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 "<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'

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

En este caso, subclase datetime.date yo mismo y cree la función correcta:

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

Y ahora puedes hacer:

>>> datetime.date.today()
NewDate(2010, 1, 1)
Daniel G
fuente
13
Una buena solución, pero desafortunadamente causa problemas con el decapado.
Baczek
14
Si bien esta respuesta es buena, es posible burlarse de la fecha y hora sin crear una clase: stackoverflow.com/a/25652721/117268
Emil Stenström el
¿Cómo restauraría la datetimeinstancia a su valor original? con deepcoppy?
Oleg Belousov
55
Mucho más fácil de hacer:patch('mymodule.datetime', Mock(today=lambda: date(2017, 11, 29)))
Victor Gavro
1
Más mucho más fácil de hacer @patch('module_you_want_to_test.date', Mock( today=Mock(return_value=datetime.date(2017, 11, 29)))).
Jonhy Beebop
163

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 llamadas a métodos de otros módulos:

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

Y finalmente:

$ python main.py
# 2012-01-01
Mehdi Behrooz
fuente
13
Una biblioteca muy muy útil
Shaun
3
También puedes probar python-libfaketime si notas que tus pruebas de congelación se ejecutan lentamente.
Simon Weber
Gran biblioteca, pero desafortunadamente no funciona bien con Google App Engine NDB / Datastore.
Brandones
Me encanta que "freezegun" sea el nombre de una biblioteca. ¡Realmente amo a los desarrolladores de Python! :-D
MikeyE
Funciona, pero freezegun parece ser lento, especialmente si tiene una lógica complicada con múltiples llamadas para la hora actual.
Andrey Belyak
115

Por lo que vale, los documentos simulados hablan específicamente de datetime.date.today, y es posible hacer esto sin tener que crear una clase ficticia:

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)
...
kpup
fuente
2
Esto realmente no funcionó para mí. Aunque aprecio el esfuerzo en localizar la entrada.
Pradyot
8
¿Qué significa "mymodule" en la función parche?
Seufagner
44
Encontré el enlace aquí en "Burla parcial"
Leo C Han
3
@seufagner mymodule se explica de manera bastante confusa en voidspace.org.uk/python/mock/patch.html#where-to-patch . Parece que si su módulo usa, from datetime import dateentonces es el nombre del módulo donde aparece from datetime import datey la llamada adate.today()
danio
1
Gracias. ¡Trabajó! Ejemplo: con mock.patch ('tests.views.datetime') como mock_date: mock_date.today.return_value = datetime.datetime (2016, 9, 18) mock_date.side_effect = lambda * args, ** kw: date (* args , ** kw)
Latrova
36

Supongo que llegué un poco tarde para esto, pero creo que el problema principal aquí es que estás parcheando 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.

Supongamos que tiene un archivo functions.py donde tiene 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.

iferminm
fuente
Esto parece muy convincente, pero no puedo hacer que esto se ejecute (arroja a NameError: name 'datetime' is not defined). ¿De dónde viene la datetime.strptimereferencia Mock(return_value=...)si no está importando datetimeen su archivo de prueba? ACTUALIZACIÓN: Está bien, simplemente seguí adelante e importé el datetimemódulo en el archivo de prueba. Pensé que el truco era cómo ocultas la datetimereferencia del archivo de prueba.
Imrek
@DrunkenMaster Tendría que ver un ejemplo de lo que estaba haciendo y de qué referencia se estaba burlando. estabas haciendo import datetimeo from datetime import strptime? si estuviera haciendo el primero, tendría que burlarse datetimey hacer mocked_datetime.strptime.return_value = whatever, es el último, tendría que burlarse directamente de la referencia de strptime en el archivo donde vive el método probado.
iferminm
@israelord Lo que quería decir es que a su último fragmento de código (el archivo de prueba) le falta una importación para la referencia de fecha y hora para que Mock(return_value=datetime...)funcione.
Imrek
32

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 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)
eternicode
fuente
2
Tenga mucho cuidado aquí: debe usar la versión from, de lo contrario, puede obtener rarezas si usa datetime.date (o datetime u otros). IE: profundidad de pila alcanzada cuando tus nuevas llamadas falsas se llaman a sí mismas.
Danny Staple
No tendrá ese problema si el objeto falso está en su propio módulo: dpaste.com/790309 . Sin embargo, incluso si está en el mismo módulo que la función simulada, no se importa date/ datetimeutiliza, utiliza la variable disponible globalmente, por lo que no debería haber ningún problema: dpaste.com/790310
eternicode
Una explicación menos breve se puede encontrar aquí: williamjohnbert.com/2011/07/…
ezdazuzena
9

Me enfrenté a la misma situación hace un par de días, 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 cubrir este caso maravillosamente

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)
Hito_kun
fuente
9

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

import datetime
from unittest.mock import Mock, patch

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

PRECAUCIÓN para esta solución: toda la funcionalidad datetime modulede la target_moduledejará de funcionar.

frx08
fuente
1
Esto es realmente agradable y conciso. La línea datetime_mock.now = Mock(return_value=datetime(1999, 1, 1)incluso podría acortarse datetime_mock.now.return_value = datetime(1999, 1, 1). En lugar de comenzar el parche con start(), considere usar el with patch(...):administrador de contexto para asegurarse de que se datetimecomporta regularmente (sin desbloquear) nuevamente cuando finaliza la prueba.
Dirk
Siempre a favor de la solución que hace uso de la biblioteca incorporada
Nam G VU
@ frx08 ¿Puedo saber cómo restablecer esta burla? Me refiero a cómo datetime.datetime.now()desbloquear ^^?
Nam G VU
Bueno, después de intentar usar este simulacro, una PRECAUCIÓN para esta solución es que todas las funcionalidades datetime moduledesde el target_moduledejarán de funcionar.
Nam G VU
1
De acuerdo @ frx08 con () sutile el dolor. Aunque dentro de ese bloque, por ejemplo, fecha, timedelta dejará de funcionar. ¿Qué pasa si ahora necesitamos burlarnos pero las matemáticas de fechas aún continúan? Lo sentimos, debemos haber burlado .now () solo, no todo el módulo datetime.
Nam G VU
7

Puede usar el siguiente enfoque, basado en la solución Daniel G. Este tiene la ventaja de no romper la verificació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 datetime.dateclase basada en C con nuestra propia subclase de Python, que produce datetime.dateinstancias originales y responde a las isinstance()consultas exactamente como nativasdatetime.date .

Úselo como administrador de contexto en sus 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 burlarse de la datetime.datetime.now()función.

Andrey Lebedev
fuente
No estoy seguro de que esto funcione en Python 2.7. Estoy obteniendo una profundidad de recursión máxima RuntimeError con el __instancecheck__método.
Dan Loewenherz
De hecho, esto funciona en Python 2.7, y resolvió mi problema con la verificación de tipo de instancia, ¡gracias!
Karatheodory
4

En términos generales, tendrías datetimeo quizásdatetime.date importado importado a un módulo en alguna parte. 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 bajo prueba. Luego 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 burlarse de un método tan simple como datetime.date.today, pregúntese si realmente necesita burlarse de él. Si su prueba es corta y va al grano y funciona bien sin burlarse de la función, es posible que solo esté mirando un detalle interno del código que está probando en lugar de un objeto que necesita burlarse.

jpmc26
fuente
2

Aquí hay otra forma de simular datetime.date.today()con una ventaja adicional de que el resto de datetimefunciones continúan funcionando, ya que el objeto simulado está configurado para envolver el datetimemódulo original :

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

Tenga en cuenta el wraps=datetimeargumento de mock.patch(): cuando foo_moduleutiliza otras datetimefunciones además de date.today()que se enviarán al datetimemódulo original envuelto .

mrts
fuente
1
Gran respuesta, la mayoría de las pruebas en las que necesita simular una fecha necesitará usar el módulo de fecha y hora
Antoine Vo
1

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

Objeto simulado: simple y eficiente, pero rompe las comprobaciones 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())

Clase simulada

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):
   ....
eddygeek
fuente
0

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

DainDwarf
fuente
0

Es posible simular funciones del datetimemódulo sin agregarside_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()
Daniil Mashkin
fuente
0

Para aquellos de ustedes que usan pytest con burlador, aquí es cómo me burlé, datetime.datetime.now()que es muy similar a la pregunta original.

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)

Esencialmente, el simulacro debe configurarse para devolver la fecha especificada. No puede parchear sobre el objeto de datetime directamente.

Daniel Butler
fuente
0

Hice este trabajo mediante la importación datetimecomo realdatetimey la sustitución de los métodos que se necesitan en la maqueta con los métodos reales:

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)
Adam McKenna
fuente
0

Puedes burlarte de datetimeesto:

En el modulo sources.py:

import datetime


class ShowTime:
    def current_date():
        return datetime.date.today().strftime('%Y-%m-%d')

En su tests.py:

from unittest import TestCase, mock
import datetime


class TestShowTime(TestCase):
    def setUp(self) -> None:
        self.st = sources.ShowTime()
        super().setUp()

    @mock.patch('sources.datetime.date')
    def test_current_date(self, date_mock):
        date_mock.today.return_value = datetime.datetime(year=2019, month=10, day=1)
        current_date = self.st.current_date()
        self.assertEqual(current_date, '2019-10-01')
MTMobile
fuente
¿Qué hay sourcesen tu decorador de parches?
elena
Querida @elena, es bastante difícil recordar lo que estaba pensando hace casi un año)). Supongo que me refería a cualquier módulo de nuestras fuentes de aplicaciones, solo el código de su aplicación.
MTMobile
0

CPython realmente implementa el módulo datetime utilizando tanto Python Lib / datetime.py puro como Módulos optimizados en C / _datetimemodule.c . La versión optimizada en C no se puede parchear, pero la versión pura de Python sí.

En la parte inferior de la implementación de Python puro en Lib / datetime.py se encuentra este código:

try:
    from _datetime import *  # <-- Import from C-optimized module.
except ImportError:
    pass

Este código importa todas las definiciones optimizadas en C y reemplaza efectivamente todas las definiciones de Python puro. Podemos obligar a CPython a usar la implementación de Python puro del módulo datetime haciendo lo siguiente:

import datetime
import importlib
import sys

sys.modules["_datetime"] = None
importlib.reload(datetime)

Al configurar sys.modules["_datetime"] = None, le decimos a Python que ignore el módulo C-optimizado. Luego recargamos el módulo que causa la importación desde_datetime que falle . Ahora las definiciones de Python puro permanecen y se pueden parchear normalmente.

Si estas usando Pytest , incluya el fragmento de arriba en conftest.py y puede parchear datetimeobjetos normalmente.

GrantJ
fuente