7

I wish to mock a class with the following requirements:

  • The class has public read/write properties, defined in its __init__() method
  • The class has public attribute which is auto-incremented on object creation
  • I wish to use autospec=True, so the class's API will be strictly checks on calls

A simplified class sample:

class MyClass():
    id = 0

    def __init__(self, x=0.0, y=1.0):
        self.x = x
        self.y = y
        self.id = MyClass._id
        MyClass.id +=1

    def calc_x_times_y(self):
        return self.x*self.y

    def calc_x_div_y(self, raise_if_y_not_zero=True):
        try:
            return self.x/self.y
        except ZeroDivisionError:
            if raise_if_y_not_zero:
                raise ZeroDivisionError
            else:
                return float('nan')

I need for the mock object to behave as the the original object, as far as properties are concerned:

  • It should auto-increment the id assigned to each newly-created mock object
  • It should allow access to its x,y properties But the mock method calls should be intercepted by the mock, and have its call signature validated

What's the best way to go on about this?

EDIT

I've already tried several approaches, including subclassing the Mock class, use attach_mock(), and mock_add_spec(), but always ran into some dead end.

I'm using the standard mock library.

bavaza
  • 10,319
  • 10
  • 64
  • 103

2 Answers2

3

Since no answers are coming in, I'll post what worked for me (not necessarily the best approach, but here goes):

I've created a mock factory which creates a Mock() object, sets its id property using the syntax described here, and returns the object:

 class MyClassMockFactory():
     _id = 0

     def get_mock_object(self, *args,**kwargs):
        mock = Mock(MyClass, autospec = True)
        self._attach_mock_property(mock , 'x', kwargs['x'])
        self._attach_mock_property(mock , 'y', kwargs['y'])
        self._attach_mock_property(mock , 'id', MyClassMockFactory._id)
        MyClassMockFactory._id += 1
        return mock

     def _attach_mock_property(self, mock_object, name, value):
         p = PropertyMock(return_value=value)
         setattr(type(mock_object), name, p)

Now, I can patch the MyClass() constructor for my tests:

class TestMyClass(TestCase):
     mock_factory = MyClassMockFactory()

     @patch('MyClass',side_effect=mock_factory.get_mock_object)
     test_my_class(self,*args):
         obj0 = MyClass()
         obj1 = MyClass(1.0,2.2)
         obj0.calc_x_times_y()
         # Assertions
         obj0.calc_x_times_y.assert_called_once_with()
         self.assertEqaul(obj0.id, 0)
         self.assertEqaul(obj1.id, 1)
bavaza
  • 10,319
  • 10
  • 64
  • 103
0

Sorry to dig up an old post, but something that would allow you to do precisely what you would like to achieve is to patch calc_x_times_y and calc_x_div_y and set autospec=True there, as opposed to Mocking the creation of the entire class.

Something like:

@patch('MyClass.calc_x_times_y')
@patch('MyClass.calc_x_div_y')
test_foo(patched_div, patched_times):
my_class = MyClass() #using real class to define attributes
# ...rest of test
Brent Hronik
  • 2,357
  • 1
  • 27
  • 43