2012-10-28 6 views
33

나는 장식을 통해 연결된 signal_handler 같은 뭔가를이 하나의 매우 간단한 :어떻게 장고 시그널 핸들러를 조롱합니까?

@receiver(post_save, sender=User, 
      dispatch_uid='myfile.signal_handler_post_save_user') 
def signal_handler_post_save_user(sender, *args, **kwargs): 
    # do stuff 

내가 뭘 원하는 것은 그것에게 시험의 모의 라이브러리 http://www.voidspace.org.uk/python/mock/을 조롱하는이, 얼마나 많은 시간을 확인 장고가 그것을 부릅니다. 순간 내 코드는 뭔가 같은 :

def test_cache(): 
    with mock.patch('myapp.myfile.signal_handler_post_save_user') as mocked_handler: 
     # do stuff that will call the post_save of User 
    self.assert_equal(mocked_handler.call_count, 1) 

여기서 문제는 조롱 경우 원래의 신호 처리기가 @receiver 장식이 어딘가에 신호 처리기의 사본을 저장하기 때문에 가장 가능성도라는 것을, 그래서 ' 잘못된 코드를 조롱 했어.

그래서 질문 : 신호 처리기를 모의 테스트하여 어떻게 작동합니까?

def _support_function(*args, **kwargs): 
    # do stuff 

@receiver(post_save, sender=User, 
      dispatch_uid='myfile.signal_handler_post_save_user') 
def signal_handler_post_save_user(sender, *args, **kwargs): 
    _support_function(*args, **kwargs) 

내가 대신 _support_function을 조롱 예상대로 모든 작품 : 나는 내 신호 처리기를 변경하면 것을

참고.

답변

14

그래서 신호에 대한 지원을하고있다 : 신호 처리기는 단순히 신호로 모의 자체를 연결하는 의미 조롱, 그래서 이것은 정확히 내가 무슨 짓을 :

mock.patch에서 autospec=True 달리 몇 가지 예외를 올릴 것이다 장고, post_save.connect 올바르게 MagicMock에서 작동 할 수 있도록하기 위해 필요하고 연결이 실패 할 는
def test_cache(): 
    with mock.patch('myapp.myfile.signal_handler_post_save_user', autospec=True) as mocked_handler: 
     post_save.connect(mocked_handler, sender=User, dispatch_uid='test_cache_mocked_handler') 
     # do stuff that will call the post_save of User 
    self.assertEquals(mocked_handler.call_count, 1) # standard django 
    # self.assert_equal(mocked_handler.call_count, 1) # when using django-nose 

알 수 있습니다.

+1

'assert_equal'은 ' assertEquals (...)'? –

+2

사용중인 테스트 스위트에 따라 다릅니다. django는 기본적으로'unittest'를 사용하는데, 여러분이 말한 것처럼'assertEquals'를 가지고 있습니다; 저는 항상 '코'를 사용합니다. 제 의견으로는 여러 측면에서 우월합니다. 코에는 'assert_equal'이 있습니다. 프로덕션 코드에서 복사/붙여 넣기 한 대답을 쓰면 그 곳에서'assert_equal'을 보게됩니다. 난 장고 기본값을 존중하는 답변을 편집, 이것을 지적 해 주셔서 감사합니다 – StefanoP

+0

감사합니다! 나는 또한 코를 사용하지만 깨닫지 못했다. assert_equal은 존재했다. –

2

작은 클래스로 장고 신호를 모방 할 수있는 방법이 있습니다.

이것은 원래 함수가 아닌 장고 신호 처리기로만 기능을 조롱한다는 점에 유의해야합니다. 예를 들어, m2mchange가 처리기를 직접 호출하는 함수를 호 출하는 경우 mock.call_count가 증가하지 않습니다. 이러한 호출을 추적하려면 별도의 모의가 필요합니다.

class LocalDjangoSignalsMock(): 
    def __init__(self, to_mock): 
     """ 
     Replaces registered django signals with MagicMocks 

     :param to_mock: list of signal handlers to mock 
     """ 
     self.mocks = {handler:MagicMock() for handler in to_mock} 
     self.reverse_mocks = {magicmock:mocked 
           for mocked,magicmock in self.mocks.items()} 
     django_signals = [signals.post_save, signals.m2m_changed] 
     self.registered_receivers = [signal.receivers 
            for signal in django_signals] 

    def _apply_mocks(self): 
     for receivers in self.registered_receivers: 
      for receiver_index in xrange(len(receivers)): 
       handler = receivers[receiver_index] 
       handler_function = handler[1]() 
       if handler_function in self.mocks: 
        receivers[receiver_index] = (
         handler[0], self.mocks[handler_function]) 

    def _reverse_mocks(self): 
     for receivers in self.registered_receivers: 
      for receiver_index in xrange(len(receivers)): 
       handler = receivers[receiver_index] 
       handler_function = handler[1] 
       if not isinstance(handler_function, MagicMock): 
        continue 
       receivers[receiver_index] = (
        handler[0], weakref.ref(self.reverse_mocks[handler_function])) 

    def __enter__(self): 
     self._apply_mocks() 
     return self.mocks 

    def __exit__(self, *args): 
     self._reverse_mocks() 

사용 예제 당신은 다음과 같이 django.db.models.signals.py에서 ModelSignal 클래스를 조롱하여 장고 신호를 조롱 할 수

to_mock = [my_handler] 
with LocalDjangoSignalsMock(to_mock) as mocks: 
    my_trigger() 
    for mocked in to_mock: 
     assert(mocks[mocked].call_count) 
     # 'function {0} was called {1}'.format(
     #  mocked, mocked.call_count) 
1

:

@patch("django.db.models.signals.ModelSignal.send") 
def test_overwhelming(self, mocker_signal): 
    obj = Object() 

여기

문제의 클래스입니다 그 트릭을해야합니다. 이것은 사용중인 개체에 상관없이 모든 신호를 모의합니다.만일 당신이 대신 mocker 라이브러리를 사용하는 경우

, 다음과 같이 수행 할 수 있습니다

from mocker import Mocker, ARGS, KWARGS 

def test_overwhelming(self): 
    mocker = Mocker() 
    # mock the post save signal 
    msave = mocker.replace("django.db.models.signals") 
    msave.post_save.send(KWARGS) 
    mocker.count(0, None) 

    with mocker: 
     obj = Object() 

그것은 더 선이다하지만이 꽤 잘 작동합니다 :)

9

은 아마도 더 나은 아이디어가있다 안에 기능을 조준하십시오. 영업 이익의 코드를 사용하여 :

@receiver(post_save, sender=User, dispatch_uid='myfile.signal_handler_post_save_user') 
def signal_handler_post_save_user(sender, *args, **kwargs): 
    do_stuff() # <-- mock this 

def do_stuff(): 
    ... do stuff in here 

하는 그런 do_stuff 조롱 :

with mock.patch('myapp.myfile.do_stuff') as mocked_handler: 
    self.assert_equal(mocked_handler.call_count, 1) 
1

장고 1.9 당신이이

# replace actual receivers with mocks 
mocked_receivers = [] 
for i, receiver in enumerate(your_signal.receivers): 
    mock_receiver = Mock() 
    your_signal.receivers[i] = (receiver[0], mock_receiver) 
    mocked_receivers.append(mock_receiver) 

... # whatever your test does 

# ensure that mocked receivers have been called as expected 
for mocked_receiver in mocked_receivers: 
    assert mocked_receiver.call_count == 1 
    mocked_receiver.assert_called_with(*your_args, sender="your_sender", signal=your_signal, **your_kwargs) 

같은 모든 수신기를 조롱 수있는 것은 예를 들어 망신 시켰습니다, 모든 수신기를 대체 당신이 등록한 것들, 플러그 할 수있는 것들이 등록 된 것들과 장고 자체가 등록한 것들. post_save에서 이것을 사용하면 놀라지 마십시오.

실제로 모의하고 싶은지 확인하기 위해 수신기를 검사 할 수 있습니다.

관련 문제