Search code examples
unit-testingmockingpython-3.7

mocking 2 classes from 2 different files


I have an example of class mocking :

worker.py

import os

class Helper:

    def __init__(self, path):
        self.path = path

    def get_folder(self):
        base_path = os.getcwd()
        return os.path.join(base_path, self.path)

    def get_path(self):
        return self.path


class Worker:

    def __init__(self):
        self.helper = Helper('db')

    def work(self):
        path = self.helper.get_path()
        print(f'Working on {path}')
        return path

And a test file : test_worker.py

from unittest import TestCase, mock

from worker import Worker, Helper

class TestWorkerModule(TestCase):

    def test_patching_class(self):
        with mock.patch('worker.Helper') as MockHelper:
            MockHelper.return_value.get_path.return_value = 'testing'
            worker = Worker()
            self.assertEqual(worker.work(), 'testing')

The test returns ok as long as the 2 classes are in the same file. Separating them into 2 files worker_1.py for class Helper and worker_2.py for class Worker, the test fails with :

AssertionError: 'db' != 'testing'

Why ? And how can I correct this behaviour ?

Thanks


Solution

  • Lets say we have two files, worker.py and helper.py

    worker.py:

    from helper import Helper
    
    class Worker:
        def __init__(self):
            self.helper = Helper('db')
    
        def work(self):
            path = self.helper.get_path()
            print('Working on {path}')
            return path
    

    helper.py:

    import os
    
    class Helper:
        def __init__(self, path):
            self.path = path
    
        def get_folder(self):
            base_path = os.getcwd()
            return os.path.join(base_path, self.path)
    
        def get_path(self):
            return self.path
    

    What you did here is to import Helper class into worker module, so when you trying to do that at your test_worker.py:

    from unittest import TestCase, mock
    
    from worker import Worker
    
    class TestWorkerModule(TestCase):
        def test_patching_class(self):
            with mock.patch('helper.Helper') as MockHelper:
                MockHelper.return_value.get_path.return_value = 'testing'
                worker = Worker()
                self.assertEqual(worker.work(), 'testing')
    

    result:

    AssertionError: 'db' != 'testing'
    - db
    + testing
    

    you are patching the Helper class in helper.py module, but you already imported the Helper class into worker module, because of that you need to patch the Helper class in worker module, for example:

    from unittest import TestCase, mock
    
    from worker import Worker
    
    class TestWorkerModule(TestCase):
        def test_patching_class(self):
            with mock.patch('worker.Helper') as MockHelper:
                MockHelper.return_value.get_path.return_value = 'testing'
                worker = Worker()
                self.assertEqual(worker.work(), 'testing')
    

    result:

    Ran 1 test in 0.002s
    
    OK
    

    Another way to make it work for general use cases (lets say you want to patch Helper class in all modules that use it), it is to change worker.py to that:

    import helper
    
    class Worker:
        def __init__(self):
            self.helper = helper.Helper('db')
    
        def work(self):
            path = self.helper.get_path()
            print('Working on {path}')
            return path
    

    here you are importing helper module, and use Helper class under helper module, so now your test.py should seem like that:

    from unittest import TestCase, mock
    
    from worker import Worker
    
    class TestWorkerModule(TestCase):
        def test_patching_class(self):
            with mock.patch('helper.Helper') as MockHelper:
                MockHelper.return_value.get_path.return_value = 'testing'
                worker = Worker()
                self.assertEqual(worker.work(), 'testing')
    

    result:

    Ran 1 test in 0.001s
    
    OK
    

    Thats worked because instead of importing the Helper class into worker module, we have imported the helper module, so patching "helper.Helper" will now really work correctly.