Tentando zombar datetime.date.today (), mas não está funcionando

Alguém pode me dizer por que isso não 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) 

Talvez alguém possa sugerir um caminho melhor?

Há alguns problemas.

Primeiro de tudo, a maneira como você está usando mock.patch não está certo. Quando usado como decorador, ele substitui a function / class dada (neste caso, datetime.date.today ) por um object Mock somente dentro da function decorada . Então, somente dentro do seu today() será datetime.date.today ser uma function diferente, que não parece ser o que você deseja.

O que você realmente quer parece ser mais assim:

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

Infelizmente, isso não 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' 

Isso falha porque os tipos internos do Python são imutáveis ​​- veja esta resposta para mais detalhes.

Nesse caso, eu criaria subclass datetime.date e criaria a function correta:

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

E agora você poderia fazer:

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

Outra opção é usar https://github.com/spulec/freezegun/

Instale-o:

 pip install freezegun 

E use:

 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 

Também afeta outras chamadas de data e hora em chamadas de método de outros 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() 

E finalmente:

 $ python main.py # 2012-01-01 

Por que vale a pena, os documentos do Mock falam sobre datetime.date.today especificamente, e é possível fazer isso sem ter que criar uma class fictícia:

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

Acho que cheguei um pouco atrasado para isso, mas acho que o principal problema aqui é que você está corrigindo datetime.date.today diretamente e, de acordo com a documentação, isso está errado.

Você deve corrigir a referência importada no arquivo onde a function testada está, por exemplo.

Digamos que você tenha um arquivo functions.py onde você tem o seguinte:

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

então, no seu teste, você deveria ter algo assim

 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 isso ajude um pouco.

Para adicionar à solução 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) 

Isso cria uma class que, quando instanciada, retornará um object normal datetime.date, mas que também pode ser alterado.

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

Você pode usar a seguinte abordagem, com base na solução Daniel G. Este tem a vantagem de não quebrar a verificação de tipo com 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) 

Basicamente, substituímos a class datetime.date baseada em C pela nossa própria subclass python, que produz instâncias datetime.date originais e responde a consultas isinstance() exatamente como datetime.date nativo.

Use-o como gerenciador de contexto em seus testes:

 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) 

Uma abordagem semelhante pode ser usada para simular a function datetime.datetime.now() .

Eu enfrentei a mesma situação há alguns dias, e minha solução foi definir uma function no módulo para testar e apenas zombar disso:

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

Hoje eu descobri sobre FreezeGun , e parece cobrir este caso lindamente

 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) 

A maneira mais fácil para mim é fazer isso:

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

CUIDADO para esta solução: todas as funcionalidades do datetime module do target_module deixarão de funcionar.

De um modo geral, você teria datetime ou talvez datetime.date importado para um módulo em algum lugar. Uma maneira mais eficaz de zombar do método seria corrigi-lo no módulo que está importando. Exemplo:

a.py

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

Então, para o seu teste, o próprio object simulado seria passado como um argumento para o método de teste. Você configuraria a simulação com o valor de resultado desejado e, em seguida, chamaria seu método em teste. Então você afirmaria que seu método fez o que você quer.

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

Uma palavra de aviso. É certamente possível ir ao mar com zombaria. Quando você faz, torna seus testes mais longos, mais difíceis de entender e impossíveis de manter. Antes de zombar de um método tão simples quanto datetime.date.today , pergunte a si mesmo se realmente precisa zombar dele. Se o seu teste é curto e direto ao ponto e funciona bem sem zombar da function, você pode estar apenas olhando para um detalhe interno do código que está testando, em vez de um object que você precisa simular.

Várias soluções são discutidas em http://blog.xelnor.net/python-mocking-datetime/ . Em suma:

Mock object – Simples e eficiente, mas interrompe as verificações 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()) 

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

Use como:

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

Talvez você possa usar seu próprio método “today ()” que corrigirá onde for necessário. Exemplo com mocking utcnow () pode ser encontrado aqui: https://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at=default

Eu implementei o método @ user3016183 usando um 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 

Eu pensei que isso poderia ajudar alguém um dia …

É possível simular funções do módulo datetime sem adicionar 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() 

Aqui está outra maneira de zombar de datetime.date.today() com um bônus adicional de que o restante das funções datetime continuem a funcionar, já que o object mock está configurado para encapsular o módulo datetime 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.datetime(2019, 3, 15) # other calls to datetime functions will be forwarded to original datetime 

Observe o argumento wraps=datetime para mock.patch() – quando o foo_module usa outras funções datetime além de date.today() elas serão encaminhadas ao módulo datetime original.