2

I need to write test cases for the module

to_be_tested.py

from module_x import X

_x = X() # creating X instance in test environment will raise error

#.....

In the test case,

from unittest import TestCase, mock

class Test1(TestCase):

    @mock.patch('...to_be_tested._x')
    @mock.patch('...to_be_tested.X.func1')
    def test_1(self, mock_func1, mock_x):
        ...

However, this will not prevent the import from creating the instance. Is it a way to workaround it and write the test cases for the module? Or is it a way to refactory to_be_tested to be testable?

Maybe write in to_be_tested.py, just _x = None if detected test environment?

2
  • If to_be_tested.py is under your control, modify it not to create an instance at import time but to delay that until first use. If to_be_tested.py is out of your control, see the solution here: Mocking a module import in pytest Commented Oct 2, 2020 at 22:33
  • yes, I have full control of the source code now. I'm going to encapsulate the variable in a function: _x = None / def get_x(): global _x / if _x == None: _x = X() / return _x. Then other functions access _x using the function. Is this a good way? Commented Oct 3, 2020 at 5:43

2 Answers 2

3

The instantiation of X at the global level seems problematic, but I don't have the full picture so I can't definitively say "don't do that". If you can refactor it so that the X() instance is created as needed or something along those lines, that would be ideal.

That said, here's a way to prevent module_x from being imported during the test. I'm basing that on the assumption that X() is used throughout the module_x module, so there's really no need for anything in that module and you only want to mock it.

import sys
import unittest

from unittest import TestCase, mock

class Test1(TestCase):

    @classmethod
    def setUpClass(cls):
        sys.modules['module_x'] = mock.Mock()

    @mock.patch('to_be_tested._x')
    @mock.patch('to_be_tested.X.func1')
    def test_1(self, mock_func1, mock_x):
        from to_be_tested import _x
        print(_x)

You can see that _x is a mock now, but note that you can't have the import outside of your tests (like at the top of the test module, like most imports usually are) because sys.modules['module_x'] hasn't been subbed out yet.

Sign up to request clarification or add additional context in comments.

Comments

0

One possibility is to guard the creation of _x with an environment variable so that you can disable its initialization in test mode. For example,

import os
from module import X

_x = None
if 'TEST' not in os.environ:
    _x = X()

Now, you just need to ensure that TEST is set in your environment before importing to_be_tested. This is proabably something you would do in with your test runner, but it's also possible to do directly in your test module.

from unittest import TestCase, mock
import os


os.environ['TEST'] = ''

import to_be_tested

class Test1(TestCase):
    ...

5 Comments

You probably meant if 'TEST' not in os.environ:. This is a really ugly solution btw, and the module will likely already be imported one way or the other during the test discovery phase, so it may be too late to set in os.environ from within a test module.
Test discovery is usually done by file name or from a directory of specific files, though like any convention there could be exceptions that make this approach fragile.
It would be cleaner to have X instantiated by a module-level function that needs to be called explicitly, but forcing the production user to call a function that the test code does not seems backwards.
In the end, this is more of a design issue than something to work around in testing.
If you instantiate X with a function call in production code like @chepner suggests, you can mock that function in the tests.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.