2011-10-05 15 views
41

Ho una classe che sto testando che ha come dipendenza un'altra classe (un'istanza di cui viene passato al metodo init di CUT). Voglio prendere in giro questa classe usando la libreria Python Mock.Oggetto Mock Python con metodo chiamato più volte

Quello che ho è qualcosa di simile:

mockobj = Mock(spec=MyDependencyClass) 
mockobj.methodfromdepclass.return_value = "the value I want the mock to return" 
assertTrue(mockobj.methodfromdepclass(42), "the value I want the mock to return") 

cutobj = ClassUnderTest(mockobj) 

che va bene, ma "methodfromdepclass" è un metodo con parametri, e come tale voglio creare un singolo oggetto fittizio in cui a seconda di quali argomenti sono passati al methodfromdepclass restituisce valori diversi.

La ragione per cui desidero questo comportamento parametrizzato è che voglio creare più istanze di ClassUnderTest che contengono valori diversi (i cui valori sono prodotti da ciò che viene restituito da mockobj).

Kinda Quello che sto pensando (questo naturalmente non funziona):

mockobj = Mock(spec=MyDependencyClass) 
mockobj.methodfromdepclass.ifcalledwith(42).return_value = "you called me with arg 42" 
mockobj.methodfromdepclass.ifcalledwith(99).return_value = "you called me with arg 99" 

assertTrue(mockobj.methodfromdepclass(42), "you called me with arg 42") 
assertTrue(mockobj.methodfromdepclass(99), "you called me with arg 99") 

cutinst1 = ClassUnderTest(mockobj, 42) 
cutinst2 = ClassUnderTest(mockobj, 99) 

# now cutinst1 & cutinst2 contain different values 

Come faccio a ottenere questo tipo "ifcalledwith" semantica?

risposta

60

Prova side_effect

def my_side_effect(*args, **kwargs): 
    if args[0] == 42: 
     return "Called with 42" 
    elif args[0] == 43: 
     return "Called with 43" 
    elif kwarg['foo'] == 7: 
     return "Foo is seven" 

mockobj.mockmethod.side_effect = my_side_effect 
+4

Impressionante, esattamente quello di cui avevo bisogno. Non è un fan della sintassi, ma funziona benissimo. Grazie! –

+1

Un avvertimento che vorrei aggiungere qui in cui ho eseguito l'utilizzo di questa soluzione, che funziona molto bene, btw, è che se si intende avere eccezioni come effetti collaterali è necessario aumentarli, piuttosto che restituirli. La libreria Mock è utile per consentire di assegnare un'eccezione a side_effect e capirlo, ma con questo metodo devi fare il fai-da-te. – ThatsAMorais

8

Ho incontrato questo quando stavo facendo il mio test. Se non si cura di catturare le chiamate al tuo methodfromdepclass(), ma solo bisogno di tornare qualcosa, allora quanto segue può essere sufficiente:

def makeFakeMethod(mapping={}): 
    def fakeMethod(inputParam): 
     return mapping[inputParam] if inputParam in mapping else MagicMock() 
    return fakeMethod 

mapping = {42:"Called with 42", 59:"Called with 59"} 
mockobj.methodfromdepclass = makeFakeMethod(mapping) 

Ecco una versione con parametri:

def makeFakeMethod(): 
    def fakeMethod(param): 
     return "Called with " + str(param) 
    return fakeMethod 
+1

+1: sintassi più stretta del modello if-block con 'side_effect', forse restituisce Mock() invece di none in modo da mantenere il comportamento predefinito? –

+0

Grazie per il suggerimento. Lo aggiornerà per restituire MagicMock() invece. – Addison

39

Un po 'più dolce :

mockobj.method.side_effect = lambda x: {123: 100, 234: 10000}[x] 

o più argomenti:

mockobj.method.side_effect = lambda *x: {(123, 234): 100, (234, 345): 10000}[x] 

o con un valore di default:

mockobj.method.side_effect = lambda x: {123: 100, 234: 10000}.get(x, 20000) 

o una combinazione di entrambi:

mockobj.method.side_effect = lambda *x: {(123, 234): 100, (234, 345): 10000}.get(x, 20000) 

e allegramente in alto andiamo.

+0

Poco conciso, ma mi piace. Cosa accadrebbe se il 'lambda' restituisse un'istanza' Mock' in modo che i comportamenti non contati tornassero al normale comportamento della libreria 'mock'? –

Problemi correlati