1

Long story short, I'm perfectly able to mock class method, when it's just that method that's replaced by mock object, but I'm unable to mock that method when I'm trying to replace the whole class by the mock object

The @mock.patch.object successfully mocks the scan method but @mock.patch fails to do so. I've followed the example at https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch but apparently I'm doing something wrong.

I'm mocking the lexicon module in the same namespace in both cases (it's imported by import lexicon in the sentence_parser) but the mock_lexicon is lexicon.lexicon check fails

#!python
import sys;
sys.path.append('D:\python\lexicon');
import lexicon;

import sentence_parser;
import unittest2 as unittest;
import mock;

class ParserTestCases(unittest.TestCase) :

    def setUp(self) :
        self.Parser = sentence_parser.Parser();

    @mock.patch('lexicon.lexicon')
    def test_categorizedWordsAreAssigned_v1(self, mock_lexicon) :

        print "mock is lexicon:";
        print mock_lexicon is lexicon.lexicon + "\n";

        instance = mock_lexicon.return_value;
        instance.scan.return_value = "anything";    

        self.Parser.categorize_words_in_sentence("sentence");
        instance.scan.assert_called_once_with("sentence");

    @mock.patch.object(lexicon.lexicon, 'scan')
    def test_categorizedWordsAreAssigned_v2(self, mock_scan) :

        mock_scan.return_value = "anything";    

        self.Parser.categorize_words_in_sentence("sentence");
        mock_scan.assert_called_once_with("sentence");

if (__name__ == '__main__') :
    unittest.main()

Output :

mock is lexicon:
False

======================================================================
FAIL: test_categorizedWordsAreAssigned_v1 (__main__.ParserTestCases)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\python\get_img\getImage_env\lib\site-packages\mock\mock.py", line 1305, in patched
    return func(*args, **keywargs)
  File "./test_sentence_parser.py", line 26, in test_categorizedWordsAreAssigned_v1
    instance.scan.assert_called_once_with("sentence");
  File "D:\python\get_img\getImage_env\lib\site-packages\mock\mock.py", line 947, in assert_called_once_with
    raise AssertionError(msg)
AssertionError: Expected 'scan' to be called once. Called 0 times.

----------------------------------------------------------------------
Ran 2 tests in 0.009s

FAILED (failures=1)

EDIT :

To clarify, the Parser is defined as follows

#!python

import sys;
sys.path.append('D:\python\lexicon');
import lexicon;

class Parser(object) :

    my_lexicon = lexicon.lexicon()

    def __init__(self) :
        self.categorized_words = ['test'];

    def categorize_words_in_sentence(self, sentence) :
        self.categorized_words = self.my_lexicon.scan(sentence);


if (__name__ == '__main__') :
    instance = Parser();
    instance.categorize_words_in_sentence("bear");
    print instance.categorized_words;
krzym1
  • 35
  • 1
  • 4
  • Three question: 1) I took a look to `lexicon` module at https://github.com/bitprophet/lexicon/tree/master/lexicon and seams to me the class is `Lexicon` instead `lexicon`; 2) my guess is that you have another `lexicon` module and not just the one in `D:\python\lexicon`; 3) why do you need `;` at the end of the lines ? – Michele d'Amico Nov 12 '15 at 13:06
  • 1) `lexicon` is my own module that just happens to have the same name as the one you linked; 2) I have only two files in `D:\python\lexicon` one is `lexicon.py` and second one is `test_lexicon.py` containing unittests; 3) The `;` are just something I got used to in other languages, but that's not really relevant here – krzym1 Nov 12 '15 at 18:37

1 Answers1

1

What is real relevant here is how categorize_words_in_sentence Parser's method use lexicon. But first of all we should remove the noise:

print mock_lexicon is lexicon.lexicon + "\n"

Is what can lead us to the wrong direction: try to replace it by

self.assertIs(mock_lexicon, lexicon.lexicon)

and you will understand that you are printing False because mock_lexicon is not lexicon.lexicon + "\n" but just lexicon.lexicon.

Now I cannot tell you why the first test doesn't work because the answer is in categorize_words_in_sentence method or more probably in sentence_parser module where I can guess you can have something like

from lexicon import lexicon

In both case take a look to Where to Patch documentation that can enlighten you on what can be the cause and what you really need to patch in your case.

The second version works just because you are patching the object and not the reference (that should be different).

Finally the more concise and general version can be:

@mock.patch('lexicon.lexicon.scan', return_value="anything")
def test_categorizedWordsAreAssigned_v3(self, mock_scan) :
    self.Parser.categorize_words_in_sentence("sentence")
    mock_scan.assert_called_once_with("sentence")

One more thing: remove unittest2 at least you're not using python 2.4 and you are interested on backported unittest features.

[EDIT]

Now I can stop to guess and point to you why the first version doesn't work and will never work:

class Parser(object) :
    my_lexicon = lexicon.lexicon()

Parser.my_lexicon attribute is evaluated at the load time. That means when you import sentence_parser a lexicon is created and the reference associated to Parser.my_lexicon. When you patch lexicon.lexicon you leave this reference untouched and your parser object still use the original reference created when is imported.

What you can do is to patch the reference in Parser class by

@patch("sentence_parser.Parser.my_lexicon")

You can use create_autospect if you want give to your mock the same lexicon's signature.

@patch("sentence_parser.Parser.my_lexicon", create_autospec("lexicon.lexicon", instance=True))
Michele d'Amico
  • 22,111
  • 8
  • 69
  • 76
  • I've stated in the original post that the lexicon is imported simply by `import lexicon;`. I've added the full `Parser` code to the original post. I've seen the Where to Patch. I know that could mock `scan` method only, but it annoys me that mocking an entire class doesn't work when theres no reason for it not to – krzym1 Nov 12 '15 at 21:15
  • Updated... `Parser`'s class code was what is really relevant here. – Michele d'Amico Nov 12 '15 at 21:53
  • Thank you! It finally works. I need to read up on references in python, I hoped that such nuisances would be mentioned in Where to Patch documentation. – krzym1 Nov 12 '15 at 22:14
  • I don't think that Where to Patch can cover these cases: that is how Python works and you should try to understand what happen on import and understand what is execute and what not. Personally I avoid to execute any kind of function/creation in the static part of the module/class. – Michele d'Amico Nov 12 '15 at 22:20