Search code examples
pythonunit-testingmockingstub

Stubbing vs Mocking in Python


I'm want to mock or stub a function for testing. Not sure if I have the terminology right so correct me if I'm wrong, but I understand a mock as using a mocking library similar to unittest.mock to create a fake object and has expectations about what parameters it will receive and what what it will return, etc. This seems a bit of overkill to me since all I want is the mocked/stubbed method to do is return a set value.

I think of stubbing as just 'mocking without a library', like the answer to this question. From what I can see, this is exactly what I want. It's light and simple and you don't have to mess around with all the options of mocking for the simple cases.

My question is, is it safe to do this? The question above seems to be overwriting the in memory representation of a method, and it just doesn't seem right. Is this accepted by the python community? Or is it encouraged to use a proper mocking library all the time?

EDIT What kind of horrible things would happen if you didn't reassign the method in the finally block, as the linked answer states?


Solution

  • The main reason I wanted to stub the method myself was so that I wouldn't have to worry about a mock and go through setting it up. However I found this blog post which shows an excellent way to use the mock library with annotations making it a lot easier than creating an instance of Mock manually. Here is an excerpt:

    rm.py

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    import os
    import os.path
    
    def rm(filename):
        if os.path.isfile(filename):
            os.remove(filename)
    

    rmtest.py

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    from mymodule import rm
    
    import mock
    import unittest
    
    class RmTestCase(unittest.TestCase):
    
        @mock.patch('mymodule.os.path')
        @mock.patch('mymodule.os')
        def test_rm(self, mock_os, mock_path):
            # set up the mock
            mock_path.isfile.return_value = False
    
            rm("any path")
    
            # test that the remove call was NOT called.
            self.assertFalse(mock_os.remove.called, "Failed to not remove the file if not present.")
    
            # make the file 'exist'
            mock_path.isfile.return_value = True
    
            rm("any path")
    
            mock_os.remove.assert_called_with("any path")