krzym1
krzym1

Reputation: 35

Mocking an entire class

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;

Upvotes: 1

Views: 3014

Answers (1)

Michele d'Amico
Michele d'Amico

Reputation: 23761

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))

Upvotes: 2

Related Questions