still learning
still learning

Reputation: 159

Mocking attribute to change each time it is accessed

Here is the function I want to test:

def send_something():
    conn = lib.conn.SSH1
    conn.open()
    conn.send('ls\n')
    if 'home' not in conn.recbuf:
        return lib.FAIL

    conn.send('whoami\n')
    if USER not in conn.recbuf:
        return lib.FAIL
    return lib.PASS

Every time I call conn.send() the output of the call is stored in conn.recbuf. To test this I need conn.recbuf to be different each time it is called. (I know I could just change conn.recbuf to be a string that contains both home and USER but that won't work for more complicated functions)

Here is what I have come up with so far in my test:

@mock.patch.object(demo_sequence.lib, 'conn')
def test_send_something(mock_conn):
    mock_conn.SSH1.recbuf = 'home'
    assert demo_sequence.lib.PASS == demo_sequence.send_something()
    mock_conn.SSH1.send.assert_any_call('ls\n')
    mock_conn.SSH1.send.assert_any_call('whoami\n')

Obviously this fails because conn.recbuf is only 'home' and does not contain USER.

I need something like conn.recbuf.side_effect = ['home', USER] but will work when just referencing recbuf and not calling it.

Upvotes: 3

Views: 2588

Answers (3)

user2993689
user2993689

Reputation: 293

I was wrangling with a similar issue earlier and found a solution using the PropertyMock. In my case I wanted to store a value elsewhere in the test case to assert against by calling a method via a side_effect when a property was set on the class I was mocking.

I think it should work for your case as well (if I've understood what you're trying to do).

The solution is to use a PropertyMock for your property recbuf and put the side_effect in there:

from unittest.mock import MagicMock, PropertyMock

conn = MagicMock()
type(conn).recbuf = PropertyMock(side_effect=['home', USER])

The side effect will be called whenever the property recbuf is accessed, in this case iterating through ['home', USER] each time. I guess you'll need to patch that USER value too so you don't get a name error.

Hope this works / helps!

Upvotes: 0

dudenr33
dudenr33

Reputation: 1169

If you are able to change conn.recbuf to be a property, you could use a PropertyMock to achieve the desired effect.

from unittest import mock

class Dummy:

    def __init__(self, myattribute):
        self._myattribute = myattribute

    @property
    def myattribute(self):
        return self._myattribute

def test():
    with mock.patch('__main__.Dummy.myattribute', new_callable=mock.PropertyMock) as mocked_attribute:
        mocked_attribute.side_effect = [4,5,6]
        d = Dummy("foo")
        print(d.myattribute)
        print(d.myattribute)
        print(d.myattribute)

However for your actual problem I think the comments to your question seem to include reasonable approaches.

Upvotes: 2

rdas
rdas

Reputation: 21285

What you're looking for is side_effect: https://docs.python.org/3/library/unittest.mock.html

Let's say mock is your mock object. Then you can do this:

mock.side_effect = [a,b,c]

then each time you call mock, the next value from the list is returned.

mock() # -> a
mock() # -> b
mock() # -> c

Upvotes: 0

Related Questions