2

I'd like to test a method, whether it calls a specific method of a temporary internal object or not. (ConfigParser.read)

So the object is created inside, and it's not accessible from the outside after the method exits.

Using python 2.7

In foobar.py

 import ConfigParser

 class FooBar:
     def method(self, filename):
         config=ConfigParser.ConfigParser()
         config.read(filename)
         do_some_stuff()

I'd like to test whether config.read was called.

As I understand, the patch decorator was made for this, but unfortunately the MagicMock object the testcase receives is not the same that is created inside, and I can't get near the object that lives inside the method.

I tried like this:

class TestFooBar(TestCase):

    def setUp(self):
         self.myfoobar = FooBar()

    @mock.patch('foobar.ConfigParser')
    def test_read(self,mock_foobar):
        self.myfoobar.method("configuration.ini")
        assert mock_foobar.called # THIS IS OKAY
        assert mock_foobar.read.called # THIS FAILS
        mock_foobar.read.assert_called_with("configuration.ini") # FAILS TOO

The problem is: - mock_foobar is created before the self.myfoobar.method creates the ConfigReader inside. - when debugging mock_foobar has internal data about the previous calls, but no "read" property (the inner MagicMock for mocking the read method)

Of course one way out is refactoring and giving the .read() or the init() a ConfigReader object, but it's not always possible to change the code, and I'd like to grasp the internal objects of the method without touching the module under test.

PetrosHu
  • 303
  • 2
  • 9

1 Answers1

4

You're so close! The issue is that you are mocking the class, but then your test checks that read() is called on that mock class - but you actually expect read() to be called on the instance that is returned when you call the class. The following works - I find the second test more readable than the first, but they both work:

import ConfigParser
from unittest import TestCase

from mock import create_autospec, patch, Mock 


class FooBar(object):
    def method(self, filename):
        config=ConfigParser.ConfigParser()
        config.read(filename)


class TestFooBar(TestCase):

    def setUp(self):
         self.myfoobar = FooBar()

    @patch('ConfigParser.ConfigParser')
    def test_method(self, config_parser_class_mock):
        config_parser_mock = config_parser_class_mock.return_value

        self.myfoobar.method("configuration.ini")

        config_parser_class_mock.assert_called_once_with()
        config_parser_mock.read.assert_called_once_with("configuration.ini")

    def test_method_better(self):
        config_parser_mock = create_autospec(ConfigParser.ConfigParser, instance=True)
        config_parser_class_mock = Mock(return_value=config_parser_mock)

        with patch('ConfigParser.ConfigParser', config_parser_class_mock):
            self.myfoobar.method("configuration.ini")

        config_parser_class_mock.assert_called_once_with()
        config_parser_mock.read.assert_called_once_with("configuration.ini")
Tom Dalton
  • 6,122
  • 24
  • 35
  • Thank you! Unfortunately, the first one simply does not work (assert AssertionError: Expected 'read' to be called once. Called 0 times.), the second one has syntax error in the "with @mock" line (also no "as something:" at the end). I did not mention this is python 2.7, I'll update the question with this info. – PetrosHu Aug 27 '15 at 08:19
  • Ohh sorry, I see your import is for the ConfigParser module, not the class . Will update accordingly. – Tom Dalton Aug 27 '15 at 09:23