[TIP] [python] Mocking and patching a class with mock.py?

Daryl Spitzer daryl.spitzer at gmail.com
Sun Jan 20 21:46:44 PST 2008


Thanks Michael.  I get it now.  I went with just setting the mock's
return value to itself.  It might be a little more confusing to the
uninitiated, but I think one can get used to that idiom quickly.  (And
I don't feel the need to use two mocks in this case.)

So the working code looks like:

-----
    mock = Mock(spec=doessomething.MyClass)
    mock.return_value = mock # so MyClass() returns a mock instance
    @patch('doessomething', 'MyClass', mock)
    def test_does_something(self):
        doessomething.does_something()
        self.assertEqual(TestDoessomething.mock.do_something.call_args_list,
                         [(('x',), {})])
-----

Though I find myself using this pattern instead:

-----
    def test_does_something(self):
        mock = Mock(spec=doessomething.MyClass)
        mock.return_value = mock
        @apply
        @patch('doessomething', 'MyClass', mock)
        def do_test():
            doessomething.does_something()
            self.assertEqual(mock.do_something.call_args_list,
                             [(('x',), {})])
-----

As the number of tests grows, it seems safer to define the mocks
inside the test methods, even if I do then have to have an internal
function in order to use the patch decorator.

I'd love to read anyone's suggestions for improvement though.

--
Daryl


On Jan 20, 2008 2:58 PM, Michael Foord <fuzzyman at voidspace.org.uk> wrote:
>
> Daryl Spitzer wrote:
> > I just started using Michael's mock.py (version 0.3.1) and I'm pleased
> > that I've been able to remove many lines from my test code and make it
> > more readable.
> >
> > But I'm stuck trying to patch a class with a mock.  I think some
> > sample code would better explain my problem:
> >
> > doessomething.py:
> > -----
> > class MyClass(object):
> >     def do_something(self, arg):
> >         print 'arg:', arg
> >
> > def does_something():
> >     obj = MyClass()
> >     obj.do_something('x')
> >
> > if __name__ == "__main__":
> >     does_something()
> > -----
> >
> > doessomething_test.py
> > -----
> > import doessomething
> >
> > import unittest
> > from mock import Mock, patch
> >
> > class TestDoessomething(unittest.TestCase):
> >     mock = Mock(spec=doessomething.MyClass)
> >     @patch('doessomething', 'MyClass', mock)
> >     def test_does_something(self):
> >         doessomething.does_something()
> >         self.assertEqual(mock.do_something.call_args_list,
> >                          [(('x',), {})])
> >
> > if __name__ == "__main__":
> >     unittest.main()
> > -----
> >
> > When I run `python doessomething.py`, I get "arg: x" as expected.
> >
> > When I run `python doessomething_test.py`, I get "AttributeError:
> > 'NoneType' object has no attribute 'do_something'" on
> > "obj.do_something('x')" in doessomething.py.
> >
> > Does what I'm trying to do (replace MyClass in doessomething.py) with
> > a mock make sense?  Can it be done with the Mock.__init__() spec
> > argument?
> >
> > I tried the following version of TestDoessomething (in
> > doessomething_test.py), but get the same error:
> > -----
> > class TestDoessomething(unittest.TestCase):
> >     def test_does_something(self):
> >         mock = Mock(spec=doessomething.MyClass)
> >         try:
> >             old_my_class = doessomething.MyClass
> >             doessomething.MyClass = mock
> >
> >             doessomething.does_something()
> >             self.assertEqual(mock.do_something.call_args_list,
> >                              [(('x',), {})])
> >         finally:
> >             doessomething.MyClass = old_my_class
> > -----
> >
> > So the problem is must be a misunderstanding of (or possibly a bug in)
> > how the spec argument works with classes.
> >
>
> The problem is that you are patching a class with a Mock instance. When
> the class is instantiated in 'does_something' the mock is called (and by
> default returns None).
>
> If you add the following line to your code it should do the right thing:
>
> mock.return_value = mock
>
> or you could create a second mock (perhaps named 'instance') and track
> how that is used.
>
> Michael
> http://www.manning.com/foord
>
>
> > --
> > Daryl Spitzer
> >
> >
>
>



More information about the testing-in-python mailing list