2012-11-09 10 views
6

Ho un'app Django chiamata "editore", si collega a vari segnali nel mio progetto django e quando li riceve invia un messaggio a una coda rabbitmq. Quello che voglio fare è essere in grado di verificare che il mio codice di installazione si colleghi ai segnali corretti.Verificare di essere connesso a un particolare segnale in Django

La mia struttura app assomiglia:

publisher 
    - __init__.py 
    - signals.py 
    - tests.py 

mia __init__.py assomiglia:

import signals 

e la mia signals.py:

def receiver_function(*args, **kwargs): 
    #Does rabbitmq stuff 

my_interesting_signal.connect(receiver_function) 

I ha pensato la patch funzione del ricevitore, e controllando che il mock è stato chiamato quando ho inviato il segnale:

tests.py:

class SignalsTeste(TestCase): 

    def_test_connection(self): 

     with patch('publisher.signals.receiver_function') as receiver_mock: 
      my_interesting_signal.application_created.send(None) 
      self.assertEquals(receiver_mock.call_count, 1) 

Tuttavia poiché il modulo segnali viene importato, e quindi i collegamenti di segnale sono realizzati prima che i test vengono eseguiti, questo approccio non funziona come le connessioni sono effettuate prima che la funzione è patch ......

Qualcuno può suggerire una strategia alternativa?

+0

Non so se ho capito bene ma inseguo i miei segnali scrivendo su un registro nella funzione ricevitore def receiver_function (* args, ** kwargs): – PepperoniPizza

risposta

12

Mi sono imbattuto nello stesso problema di derisione che descrivi. La mia soluzione è di raggiungere il registro dei segnali di Django e affermare che la mia funzione è stata registrata con il segnale corretto.

Ecco la mia prova:

def test_signal_registry(self): 
    from foo.models import bar_func # The function I want to register. 
    from django.db.models import signals 
    registered_functions = [r[1]() for r in signals.pre_delete.receivers] 
    self.assertIn(bar_func, registered_functions) 

Una piccola spiegazione su quella lista di comprensione:

"pre_delete" è l'istanza di django.dispatch.dispatcher.Signal che mi interessava in questo caso. Utilizzerai il tuo "my_interesting_signal" nel tuo esempio. I segnali hanno una proprietà interna chiamata "ricevitori" che è una lista di due tuple, in cui il secondo elemento è un elemento debole della funzione che si registra (quindi r [1]). La chiamata a weakref restituisce il referente.

ho dovuto giocare con weakrefs per capire che molto fuori:

import weakref 
def foo(): 
    pass 
w = weakref.ref(foo) 
w() == foo 

Spero che questo aiuti.

+0

Non penso che sia giusto. Se bar_func è in 'models.py', allora sì, verrà automaticamente importato da Django e quindi collegato al segnale. Ma se è in un 'segnali.py', allora non necessariamente. Inoltre, l'atto di importare il tuo ricevitore nel test lo collegherà automaticamente, quindi il test passerà anche se non lo stai collegando altrove. – seddonym

1

Un modo per verificare se il segnale è collegato è scollegarlo e controllare il risultato di questa azione. La chiamata <some_signal>.disconnect(...) restituisce True se il segnale è stato disconnesso o False in caso contrario.

Ad esempio, vogliamo verificare che il segnale post_save sia collegato al nostro receiver_function.

modules.py

def receiver_function(*args, **kwargs): 
    pass 

signals.post_save.connect(receiver_function) 

test.py

class SignalsTest(TestCase): 
    def test_connection(self): 
     result = signals.post_save.disconnect(receiver_function) 

     self.assertTrue(result) 

La chiamata a disconnect deve utilizzare gli stessi argomenti che il connect chiamata (sender, dispatch_uid)

È necessario collegare nuovamente il segnale dopo la prova, se non rimarrà disconnesso

0

Questo è piuttosto complicato, perché come dici tu, se prendi in giro o importi qualcosa dal file in cui includi il ricevitore, lo colleghi automaticamente. Si tratta dell'intera suite di teste non solo del file di test in questione. Ecco uno snippet che puoi utilizzare, ma devi essere disciplinato nel seguire i commenti per evitare le importazioni nel file dei ricevitori.

from django.test import TestCase 

class ReceiverConnectionTestCase(TestCase): 
    """TestCase that allows asserting that a given receiver is connected 
    to a signal. 

    Important: this will work correctly providing you: 
     1. Do not import or patch anything in the module containing the receiver 
      in any django.test.TestCase. 
     2. Do not import (except in the context of a method) the module 
      containing the receiver in any test module. 

    This is because as soon as you import/patch, the receiver will be connected 
    by your test and will be connected for the entire test suite run. 

    If you want to test the behaviour of the receiver, you may do this 
    providing it is a unittest.TestCase, and there is no import from the 
    receiver module in that test module. 

    Usage: 

     # myapp/receivers.py 
     from django.dispatch import receiver 
     from apples.signals import apple_eaten 
     from apples.models import Apple 

     @receiver(apple_eaten, sender=Apple) 
     def my_receiver(sender, **kwargs): 
      pass 


     # tests/integration_tests.py 
     from apples.signals import apple_eaten 
     from apples.models import Apple 

     class TestMyReceiverConnection(ReceiverConnectionTestCase): 
      def test_connection(self): 
       self.assert_receiver_is_connected(
        'myapp.receivers.my_receiver', 
        signal=apple_eaten, sender=Apple) 

    """ 
    def assert_receiver_is_connected(self, receiver_string, signal, sender): 
     receivers = signal._live_receivers(sender) 
     receiver_strings = [ 
      "{}.{}".format(r.__module__, r.__name__) for r in receivers] 
     if receiver_string not in receiver_strings: 
      raise AssertionError(
       '{} is not connected to signal.'.format(receiver_string)) 

Questo funziona perché Django corre django.test.TestCase prima unittest.TestCase.

Problemi correlati