98

In my attempt to learn TDD, trying to learn unit testing and using mock with python. Slowly getting the hang of it, but unsure if I'm doing this correctly. Forewarned: I'm stucking using python 2.4 because the vendor API's come as pre-compiled 2.4 pyc files, so I'm using mock 0.8.0 and unittest ( not unittest2 )

Given this example code in 'mymodule.py'

import ldap

class MyCustomException(Exception):
    pass

class MyClass:
    def __init__(self, server, user, passwd):
        self.ldap = ldap.initialize(server)
        self.user = user
        self.passwd = passwd

    def connect(self):
        try:
            self.ldap.simple_bind_s(self.user, self.passwd)
        except ldap.INVALID_CREDENTIALS:
            # do some stuff
            raise MyCustomException

Now in my test case file 'test_myclass.py', I want to mock the ldap object out. ldap.initialize returns the ldap.ldapobject.SimpleLDAPObject, so I figured that'd be the method I'd have to mock out.

import unittest
from ldap import INVALID_CREDENTIALS
from mock import patch, MagicMock
from mymodule import MyClass

class LDAPConnTests(unittest.TestCase):
    @patch('ldap.initialize')
    def setUp(self, mock_obj):
        self.ldapserver = MyClass('myserver','myuser','mypass')
        self.mocked_inst = mock_obj.return_value

    def testRaisesMyCustomException(self):
        self.mocked_inst.simple_bind_s = MagicMock()
        # set our side effect to the ldap exception to raise
        self.mocked_inst.simple_bind_s.side_effect = INVALID_CREDENTIALS
        self.assertRaises(mymodule.MyCustomException, self.ldapserver.connect)

    def testMyNextTestCase(self):
        # blah blah

Leads me to a couple of questions:

  1. Does that look right? :)
  2. Is that the proper way to try and mock an object that gets instantiated within the class I'm testing?
  3. Is it ok to be calling the @patch decorator on setUp or is this going to cause weird side effects?
  4. Is there anyway to get mock to raise the ldap.INVALID_CREDENTIALS exception without having to import the exception into my testcase file?
  5. Should I be using patch.object() instead and if so, how?

Thanks.

2
  • 1
    1-3) Seems fine to me... 4) import ldap instead and set side_effect = ldap.INVALID_CREDENTIALS? Commented Apr 4, 2013 at 22:28
  • You can always make the same test but with simplier objects made by yourself... Commented May 11, 2014 at 20:09

6 Answers 6

99

You can use patch() as a class decorator, not just as a function decorator. You can then pass in the mocked function as before:

@patch('mymodule.SomeClass')
class MyTest(TestCase):

    def test_one(self, MockSomeClass):
        self.assertIs(mymodule.SomeClass, MockSomeClass)

See: Applying the same patch to every test method (which also lists alternatives)

It makes more sense to set up the patcher this way on setUp if you want the patching to be done for all the test methods.

Sign up to request clarification or add additional context in comments.

6 Comments

I just ran into an issue where I had a class-level mock on a TestCase class and assumed it would already be in place when making a call in the setUp() method. THIS IS NOT THE CASE; class-level mocks are not applied in time for use in setUp(). I solved the issue by, instead, creating a helper method that I use within all my tests. Not sure this is the best approach, but it works.
@berto If you expand your comment in an Answer, I think it will be helpful. It's a different and probably easier solution than the others here.
So... Is this answer just plain wrong or not? I'm now more confused than before I googled this.
Best way to achieve mock running on every test without code repetition.
@berto you can solve this easily by using the same patch decorator on both the class and the setUp() method. It's not elegant having repeated code, but IMHO this is the quickest/simplest way around this challenge.
|
35

I'll start by answering your questions, and then I'll give a detailed example of how patch() and setUp() interact.

  1. I don't think it looks right, see my answer to question #3 in this list for details.
  2. Yes, the actual call to patch looks like it should mock the object you want.
  3. No, you almost never want to use the @patch() decorator on setUp(). You got lucky, because the object is created in setUp() and never gets created during the test method.
  4. I don't know of any way to make a mock object raise an exception without importing that exception into your test case file.
  5. I don't see any need for patch.object() here. It just lets you patch attributes of an object instead of specifying the target as a string.

To expand on my answer to question #3, the problem is that the patch() decorator only applies while the decorated function is running. As soon as setUp() returns, the patch is removed. In your case, that works, but I bet it would confuse someone looking at this test. If you really only want the patch to happen during setUp(), I would suggest using the with statement to make it obvious that the patch is going to be removed.

The following example has two test cases. TestPatchAsDecorator shows that decorating the class will apply the patch during the test method, but not during setUp(). TestPatchInSetUp shows how you can apply the patch so that it's in place during both setUp() and the test method. Calling self.addCleanUp() makes sure that the patch will be removed during tearDown().

import unittest
from mock import patch


@patch('__builtin__.sum', return_value=99)
class TestPatchAsDecorator(unittest.TestCase):
    def setUp(self):
        s = sum([1, 2, 3])

        self.assertEqual(6, s)

    def test_sum(self, mock_sum):
        s1 = sum([1, 2, 3])
        mock_sum.return_value = 42
        s2 = sum([1, 2, 3])

        self.assertEqual(99, s1)
        self.assertEqual(42, s2)


class TestPatchInSetUp(unittest.TestCase):
    def setUp(self):
        patcher = patch('__builtin__.sum', return_value=99)
        self.mock_sum = patcher.start()
        self.addCleanup(patcher.stop)

        s = sum([1, 2, 3])

        self.assertEqual(99, s)

    def test_sum(self):
        s1 = sum([1, 2, 3])
        self.mock_sum.return_value = 42
        s2 = sum([1, 2, 3])

        self.assertEqual(99, s1)
        self.assertEqual(42, s2)

3 Comments

I think you'd like to give a hyperlink to the answer #3 that you have mentioned since SO sorts the answers based on the points they receive.
I see what you mean, @ErdinEray, but I was actually talking about my answer to question #3 by the OP.
I really like this technique, as it allows you to create a default config for a class-level mock that works for most tests, and then tests that need the mock to behave in different ways can override that. Really quite nice.
25

I'd like to point out a variation of the accepted answer in which a new argument is passed to the patch() decorator:

from unittest.mock import patch, Mock

MockSomeClass = Mock()

@patch('mymodule.SomeClass', new=MockSomeClass)
class MyTest(TestCase):
    def test_one(self):
        # Do your test here

Note that in this case, it is no longer necessary to add the second argument, MockSomeClass, to every test method, which can save a lot of code repetition.

An explanation of this can be found at https://docs.python.org/3/library/unittest.mock.html#patch:

If patch() is used as a decorator and new is omitted, the created mock is passed in as an extra argument to the decorated function.

The answers above all omit new, but it can be convenient to include it.

3 Comments

Thanks for this! This helps a lot especially when there are many mocked classes of functions where no special return value or similar is needed. Keeps the test case function definitions cleaner.
Thanks, this helped me make my code cleaner and DRY. I don't need to annotate on top of each method as I had quite a few mock classes.
What a great example of why one should always keep scrolling on older StackOverflow questions for more insight beyond the accepted answer! Bravo :D
17

If you have many patches to apply and you want them to apply to things initialised in the setUp methods too try this:

def setUp(self):
    self.patches = {
        "sut.BaseTestRunner._acquire_slot": mock.Mock(),
        "sut.GetResource": mock.Mock(spec=GetResource),
        "sut.models": mock.Mock(spec=models),
        "sut.DbApi": make_db_api_mock()
    }

    self.applied_patches = [mock.patch(patch, data) for patch, data in self.patches.items()]
    [patch.apply for patch in self.applied_patches]
    .
    . rest of setup
    .


def tearDown(self):
    patch.stopall()

5 Comments

consider to use patch.stop_all() in tearDown().
I tried this - seems that the applied_patches also need to be started. Consider a line like: for patch in self.applied_patches: patch.start()
It's stopall, not stop_all.
In fairness - I'd now use the "self.addCleanup(patch)" method now. Time to update this answer.
self.addCleanup(patch.stopall)
2

You can create a patched inner function and call it from setUp.

If your original setUp function is:

def setUp(self):
    some_work()

Then you can patch it by changing it to:

def setUp(self):
    @patch(...)
    def mocked_func():
        some_work()

    mocked_func()

1 Comment

Why not just use patch as a context manager, then? with patch(...):
2

Another way to solve this is to define the patch once outside of the class then reuse it to decorate both the class and the setUp and tearDown methods, like this (reusing Don Kirkby's example) :

import unittest
from mock import patch

my_patch = patch('__builtin__.sum', return_value=99)

@my_patch
class TestPatchAsDecorator(unittest.TestCase):
    @my_patch
    def setUp(self):
        s = sum([1, 2, 3])

        self.assertEqual(99, s)

    def test_sum(self, mock_sum):
        s1 = sum([1, 2, 3])
        mock_sum.return_value = 42
        s2 = sum([1, 2, 3])

        self.assertEqual(99, s1)
        self.assertEqual(42, s2)

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.