37

I have a large project that is unit tested using the Python unittest module.

I have one small method that controls large aspects of the system's behaviour. I need this method to return a fixed result when running under the UTs to give consistent test runs, but it would be expensive for me to mock this out for every single UT.

Is there a way that I can make this single method, unittest aware, so that it can modify its behaviour when running under the unittest?

tomdee
  • 2,319
  • 5
  • 25
  • 39

6 Answers6

24

You can check, if the unittest module is loaded. It should be loaded only, if a test runs.

>>> 'unittest' in sys.modules.keys()
False
>>> from unittest import TestCase
>>> 'unittest' in sys.modules.keys()
True
kwarnke
  • 1,424
  • 1
  • 15
  • 10
  • 4
    It should be more efficient if you skip the `.keys()` and just use `'unittest' in sys.modules`. – Troy Feb 24 '21 at 21:20
  • Unfortunately this will not work in Spyder (just found out there are a bunch of modules always pre-loaded) – CAPSLOCK Aug 23 '22 at 15:07
  • This was my solution for determining whether or not the user is running tests using `behave` or using `pytest` when accessing the shared modules. – Eitel Dagnin Jan 19 '23 at 08:04
9

My solution is to set a TEST_FLAG=true environment variable before running unittest. For example:

TEST_FLAG=true python -m unittest discover -s tests -b

Then it is just a matter of checking if the variable is set. For example:

MONGODB_URI =
    os.environ.get('MONGODB_URI') if not os.environ.get('TEST_FLAG') 
        else os.environ.get('MONGODB_TEST_URI')
Franco
  • 669
  • 2
  • 8
  • 23
7

I don't know much about the unittest module, but if you're running the file directly for the unit test, you can enclose the test code with the following if:

if __name__ == "__main__":

Any code that lies within that if statement will only be executed if your particular module is being directly invoked, and not imported into something else. According to the docs, that's how you should be calling unittest.main() in the first place.

https://docs.python.org/2/library/unittest.html

This assumes you're not running from the command line.

EDIT: you could look at the function stack to try and find the unittest.main() function.

import inspect

def in_unit_test():
  current_stack = inspect.stack()
  for stack_frame in current_stack:
    for program_line in stack_frame[4]:    # This element of the stack frame contains 
      if "unittest" in program_line:       # some contextual program lines
        return True
  return False

https://docs.python.org/2/library/inspect.html

It's kind of a hacky solution, but the inspect module has a lot of useful functions for introspection.

TheSoundDefense
  • 6,753
  • 1
  • 30
  • 42
  • my solution is to create a flag, "sys.unittesting = None", after the above "if __name__ ..." line. sys is imported in a lot of files... of course any commonly imported module would do. I find you sometimes do need to find whether you are unittesting, e.g. to suppress things which wait for user input. – mike rodent Aug 30 '15 at 08:13
  • 2
    WARNING: this function will always return `True` because the function name is `in_unittest` and will be asserted by `if "unittest" in program_line:` – EwyynTomato May 29 '18 at 02:45
  • 1
    Whoops, fixed. Thanks. – TheSoundDefense May 29 '18 at 07:34
3

With tox I set an environment variable like this:

[testenv]
setenv = TOX_TESTENV = true

and then in the code I check if the variable is set:

import os


if os.env.get('TOX_TESTENV'):
    # Code is running under test.
    # This is useful to configure log levels for example.
Agost Biro
  • 2,709
  • 1
  • 20
  • 33
1

I am sure that there are other, better, methods but you could always set a global flag from your main and not under unit test then access it in your method.

The other way of course would be to override the method as a part of the unit test set-up - if your method is called brian and you have a test_brian then simply during your pre-test setting brian = test_brian will do the job, you may need to put module names into the preceding statement.

Steve Barnes
  • 27,618
  • 6
  • 63
  • 73
1

You can modify a function at runtime just for the tests. For example:

module.py

def func():
    return random.randint()

test.py

import module

def replacement_func():
    return 4 # chosen by fair dice roll

module.func = replacement_func

# run unit tests here

Now, whenever code in module calls func(), then it will actually call back out to your replacement_func().

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285