2012-08-15 18 views
6

Ich habe einen Funktionsaufruf, der ein Objekt zurückgibt:Mocking verschachtelte Objekte mit Mock

r = Foo(x,y) 

wo r einen umfangreichen Satz von verschachtelten Eigenschaften hat. Zum Beispiel kann ich auf r.prop_a.prop_b.prop_c zugreifen. Ich möchte Foo verspotten, so dass eine spezifische Blatt Eigenschaft r geändert wird, das heißt, dass r.prop_a.prop_b.prop_c kehrt ein Wert unter meiner Kontrolle:

>> r = Foo(x,y) 
>> r.prop_a.prop_b.prop_c 
'fish' 
>> # some mock magic patching of Foo is taking place here 
>> r = Foo(x,y) 
>> r.prop_a.prop_b.prop_c 
'my_fish' 

ich nicht über Zwischen Eigenschaften egal viel.

Gibt es eine elegante Möglichkeit, geschachtelte Eigenschaften mit mock zu verhöhnen?

+0

Es ist sehr gut, dass bestimmte Dinge gelassen werden so wie sie sind. Beendet mit dem echten Ding statt zu spotten. – Oleksiy

Antwort

10

Ersetzen Sie das Mock-Attribut Aufruf des Objekts, wie man erwarten würde:

>> r1 = r_original(x, y) 
>> r1.prop_a.prop_b.prop_c 
'fish' 

>> returner = mock.MagicMock() 
>> returner.prop_a.prop_b.prop_c = 'fish' 
>> r_mocked = mock.MagicMock(spec_set=r_original, return_value=returner) 
>> r2 = r_mocked(x, y) 
>> r2.prop_a.prop_b 
MagicMock name='returner.prop_a.prop_b' id='87412560'> 
>> r2.prop_a.prop_b.prop_c 
'fish' 

Dies Sie die volle Leistung des spöttischen ermöglicht, während einen bestimmten Wert zu definieren.

+0

Ist r2 eine Instanz der Klasse r_original? – Oleksiy

+0

Nein, es ist ein MagicMock-Objekt. – dbn

3

Wenn Sie die ursprünglichen Eigenschaften an anderer Stelle verfügbar machen möchten, können Sie eine Wrapper-Klasse definieren:

class OverrideAttributePath(object): 
    """A proxy class where we override a specific attribute path with the 
    value given. For any other attribute path, we just return 
    attributes on the wrapped object. 

    """ 
    def __init__(self, thing, path, value): 
     self._thing = thing 
     self._path = path 
     self._value = value 

    def __dir__(self): 
     return dir(self._thing) 

    def __len__(self): 
     return len(self._thing) 

    def __getitem__(self, index): 
     if self._path == [index]: 
      return self._value 
     elif self._path[0] == index: 
      return OverrideAttributePath(
       self._thing[index], self._path[1:], self._value) 
     else: 
      return self._thing[index] 

    def __getattr__(self, key): 
     if self._path == [key]: 
      return self._value 
     elif self._path[0] == key: 
      return OverrideAttributePath(
       getattr(self._thing, key), self._path[1:], self._value) 
     else: 
      return getattr(self._thing, key) 

Die Verwendung ist dann wie folgt:

>>> r = Foo(x,y) 
>>> r2 = OverrideAttributePath(r, ['prop_a', 'prop_b', 'prop_c'], 'my_fish') 
>>> r2.prop_a.prop_b.prop_c 
'my_fish'