derwiki-adroll / mock

Automatically exported from code.google.com/p/mock
BSD 2-Clause "Simplified" License
0 stars 0 forks source link

call_args_list: several calls with same object as argument, but different content in object #175

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
What steps will reproduce the problem?
1. See code below

What is the expected output? What do you see instead?
I have a function that calls another function several times. The second 
function takes a dictionary as argument, and the first function manipulates 
this dictionary for each time it calls the second function.

I want to be able to check how the dictionary looked like at the time of each 
call to the second function. I tried using call_args_list, but found it to 
return the last value for all calls. 

I understand the nature of pointers, and that the call_args_list only point to 
the dictionary, which has changed when I do the check. But this limits the use 
of call_args_list a lot. Is there some other way to do this? If not, is it 
possible to implement in a future version of Mock?

What version of the product are you using? On what operating system?
Mock 0.8, Ubuntu 12.04

Please provide any additional information below.

Thanks for an otherwise great lib :) 

#####code_to_test.py#################
class ParameterClass(object):                                                   

    def __init__(self, value):                                                                                
        self.value = value                                                                                    
class ClassToTest(object):                                                      

    def function_to_test(self, parameters):                                                                   
        lst = parameters['key'].value.split('-')                                                              
        for val in lst:                                                                                       
            parameters['key'].value = val                                                                        
            print 'Calling with value %s' % parameters['key'].value                                              
            self.function_to_be_mocked(parameters)                                                                                   
    def function_to_be_mocked(self, parameters):                                                              
        print 'Called with value %s' % parameters['key'].value                                                

if __name__ == '__main__':                                                      

    parameters = {'key': ParameterClass('value1-value2')}                                                     
    cls = ClassToTest()                                                                                       
    cls.function_to_test(parameters)

#######test.py####################
import unittest2 as unittest                                                    

from mock import Mock                                                           

from code_to_test import ClassToTest                                            

class SomeTest(unittest.TestCase):                                              

    def test_something(self):                                                                                 
        cls = ClassToTest()                                                                                   
        cls.function_to_be_mocked = Mock()                                                                    
        parameter_mock = Mock()                                                                               
        parameter_mock.value = 'value1-value2'                                                                
        parameters = {'key': parameter_mock}                                                                  

        cls.function_to_test(parameters)                                                                      

        print cls.function_to_be_mocked.call_args_list[0][0][0].value                                                                
        print cls.function_to_be_mocked.call_args_list[1][0][0].value                                            

if __name__ == '__main__':                                                      

        unittest.main()  

Original issue reported on code.google.com by erling.b...@gmail.com on 11 Sep 2012 at 8:22

GoogleCodeExporter commented 9 years ago
Coping with mocks that are called with mutable arguments is a known issue. In 
the documentation I suggest a couple of different ways of dealing with it:

    http://www.voidspace.org.uk/python/mock/examples.html#coping-with-mutable-arguments

This comes up rarely enough that I don't think I'm going to build support for 
it into mock, but the documentation does suggest a few different ways you can 
deal with it.

Original comment by fuzzyman on 11 Sep 2012 at 10:18

GoogleCodeExporter commented 9 years ago
Thanks for the quick reply. The docs did help me find a workaround :)

I have had a quick look in the code of Mock. Couldn't this be fixed by adding 
the following code to the top of the function CallableMixin._mock_call?

if self.deepcopy_calllist:
    args = deepcopy(args)
    kwargs = deepcopy(kwargs)

it will also need to handle the kwarg 'deepcopy_calllist' in __init__. Then you 
should be able to use Mock(deepcopy_calllist=True) to handle this problem.

Original comment by erling.b...@gmail.com on 11 Sep 2012 at 11:33

GoogleCodeExporter commented 9 years ago

Original comment by fuzzyman on 11 Sep 2012 at 11:34

GoogleCodeExporter commented 9 years ago
I also ran into this issue today.

Here's one API suggestion for this feature.  What if Mock accepted a "save_arg" 
argument that defaulted to the identity function `lambda x: x`?

This would let people easily insert the flavor of copying that they want, which 
could be shallow or deep (e.g. lambda x: x.copy()).

The implementation should also be easy because I believe it would just be a 
matter of wrapping an expression with a call to self.save_arg() at the 
appropriate spot.  Thanks.

Original comment by chris.je...@gmail.com on 29 Oct 2014 at 1:00