Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

override python function-local variable in unittest

I have a method in python (2.7) that does foo, and gives up after 5 minutes if foo didn't work.

def keep_trying(self):     timeout = 300  #empirically derived, appropriate timeout     end_time = time.time() + timeout     while (time.time() < end_time):         result = self.foo()         if (result == 'success'):             break         time.sleep(2)     else:         raise MyException('useful msg here') 

I know some possible results from foo(), so I am using mock to fake those return values. The problem is, I don't want the test to run 5 minutes before it gets to see the exception.

Is there a way to override that local value of timeout? I'd like that to be just a few seconds so that I can see the loop try a couple times, then give up and raise.

The following does not work:

@patch.object(myClass.keep_trying, 'timeout') @patch.object(myClass, 'foo') def test_keep_trying(self, mock_foo, mock_timeout):     mock_foo.return_value = 'failed'     mock_timeout.return_value = 10 # raises AttributeError     mock_timeout = 10 # raises AttributeError     ... 
like image 662
anregen Avatar asked Feb 24 '15 04:02

anregen


2 Answers

You can't mock a function's local variable. To make your code easier to test, change it to, e.g:

def keep_trying(self, timeout=300):     end_time = time.time() + timeout     # etc, as above 

so it becomes trivial for tests to run it with a shorter timeout!

like image 110
Alex Martelli Avatar answered Sep 21 '22 10:09

Alex Martelli


Rather than trying to mock the value if timeout, you'll want to mock the return value of time.time().

e.g.

@patch.object(time, 'time') def test_keep_trying(self, mock_time):     mock_time.side_effect = iter([100, 200, 300, 400, 500, 600, 700, 800])     ... 

Now the first time time.time() is called, you'll get the value of 100, so it should timeout when after a few turns of your while loop. You can also mock time.sleep and just count how many times it gets called to make sure that part of the code is working properly.


Another approach (which isn't completely orthogonal to the one above) is to allow the user to pass an optional timeout keyword to the function:

def keep_trying(self, timeout=300):     ... 

This allows you to specify whatever timeout you want in the tests (and in future code which doesn't want to wait 5 minutes ;-).

like image 37
mgilson Avatar answered Sep 20 '22 10:09

mgilson