0

I have a parent object, which receives a connection object.

I run a method on this connection object to generate a config object. When I mock this, instead of Foo I get:

<Mock name='mock().get_properties().property_c' id='1910891784064'>

Code:

# Real

class ClassC:
    property_c = "Foo"

class ClassB:
    def __init__(self):
        pass

    def login(self):
        print("Logged in...")

    def get_properties(self):
        return ClassC()

class ClassA:
    def __init__(self, conn):
        self.conn = conn
        self.conn.login()

a = ClassA(conn=ClassB()) >>> Logged in...
result = a.conn.get_properties()
print(result.property_c) >>> Foo


# Mocked

from unittest.mock import Mock
mock_b = Mock()
mock_c = Mock()
mock_c.property_c.return_value = "Foo_Mock"
mock_b.get_properties.return_value = mock_c

a = ClassA(conn=mock_b())
result = a.conn.get_properties()
print(result.property_c) >>> Output shown above

How do I mock this properly?

Edit1 - The suggested duplicate S.O answer only partially answers the question.

Edit2 - I forgot to include the mock login behaviour

mock_b.login.return_value = print("Logged in...") awesoon's answer still works with this modification:

    mock_b = Mock()
    mock_c = Mock()
    type(mock_c).property_c = PropertyMock(return_value="Foo_Mock")
    mock_b.get_properties.return_value = mock_c
    mock_b.login.return_value = print("Logged in...")
Community
  • 1
  • 1
GlaceCelery
  • 921
  • 1
  • 13
  • 30
  • Possible duplicate of [Mock attributes in Python mock?](https://stackoverflow.com/questions/16867509/mock-attributes-in-python-mock) – awesoon May 06 '19 at 10:31
  • I see how PropertyMock can be used for ClassC, but how do I inject it into ClassB? – GlaceCelery May 06 '19 at 10:53
  • `type(mock_c).property_c = PropertyMock(return_value="Foo_Mock")` and `mock_b.get_properties().property_c` shall do the trick. Note I did not call `mock_b()` – awesoon May 06 '19 at 10:59
  • I still get `` – GlaceCelery May 06 '19 at 11:09
  • 1
    Check this one: https://ideone.com/aCy9Ye – awesoon May 06 '19 at 11:13
  • `The suggested duplicate only partially answers the question` - I also explicitly listed all changes I made in the comment above. Read the note: `I did not call mock_b()` and you should not call it too because calling creates another mock which does not have `get_properties`. – awesoon May 06 '19 at 14:30
  • I was referring to the flag at the top of this post by someone claiming that my question is a duplicate. If you paste your solution (that I upvoted) into an answer, then I can accept it and you'll get credit – GlaceCelery May 06 '19 at 21:00
  • It was actually me, I still think this is a duplicate, because you were trying to use `return_value` to mock attribute. Also `mock_b.login.return_value = print("Logged in...")` is the same as printing `Logged in...` just before `mock_b.login.return_value = None`. `print("Logged in...")` will be executed immediately and it returns `None`. You may want to use `side_effect` with lambda instead. – awesoon May 07 '19 at 05:17

2 Answers2

1

There are two issues in your code:

  1. Do not call mock_b when passing to ClassA (a = ClassA(conn=mock_b()). Calling a mock will create another mock and it will not contain changes made to mock_b.

  2. Use PropertyMock to mimic attribute:

    type(mock_c).property_c = PropertyMock(return_value="Foo_Mock")
    

    You can also set attribute explicitly, but PropertyMock is more flexible.

The final code is:

from unittest.mock import Mock, PropertyMock
mock_b = Mock()
mock_c = Mock()
type(mock_c).property_c = PropertyMock(return_value="Foo_Mock")
mock_b.get_properties.return_value = mock_c

print(mock_b.get_properties().property_c)

Outputs:

Foo_Mock
awesoon
  • 32,469
  • 11
  • 74
  • 99
0

I think problem is with this line:

a = ClassA(conn=mock_b())

You are creating here new mock. If you try below code:

mock_b = Mock(name='mock_b')
a = ClassA(conn=mock_b())
print(mock_b)
print(a.conn)

You will see that those, are two different mocks, with different IDs (for example):

<Mock name='mock_b' id='48709360'>
<Mock name='mock_b()' id='48709528'>

So frits of all, you need to replace conn=mock_b() by conn=mock_b.

If you fix that then you could call:

print(result.property_c())  # notice extra parathesis.

It would look better if you add get_property method to ClassC, and then mock method get_property.

Relandom
  • 1,029
  • 2
  • 9
  • 16
  • I cannot structure it your way. I'm bound by the api – GlaceCelery May 06 '19 at 11:21
  • Ok, so you can simply change your 2 lines in your test: `mock_c.property_c.return_value = "Foo_Mock"` >> `mock_c.property_c = "Foo_Mock"` and: `a = ClassA(conn=mock_b())` >> `a = ClassA(conn=mock_b)` – Relandom May 06 '19 at 11:52