0

I am currently attempting to write unit tests for my Main.py's main() function

Here is a simplified version of my Main.py:

from Configuration import Configuration # Configuration.py is a file in the same dir

def main():
  try:
    Configuration('settings.ini')
  except:
    sys.exit(1) # Test path1
  sys.exit(0) # Test path2

if __name__ == '__main__':
    main()

In my Unit Tests\MainUnitTests.py I want to import ..\Main.py and fake the Configuration class in such a way that I can hit Test path1 and Test path2

I found that i can assert sys.exit() with the following:

with self.assertRaises(SystemExit) as cm:
  main()
self.assertEqual(cm.exception.code, 1)

but I am having trouble overriding the from Configuration import Configuration

Thoughts?

So far I have tried the following within Unit Tests\MainUnitTests.py:

class FakeFactory(object):
  def __init__(self, *a):
    pass

sys.modules['Configuration'] = __import__('FakeFactory')

class Configuration(FakeFactory):
  pass

Another example for demonstration:

foo.py:

from bar import a,b

x = a()

class a(object):
  def __init__(self):
    self.q = 2

y = a()

print x.q, y.q # prints '1 2' as intended

b() # I want this to print 2 without modifying bar.py

bar.py:

class a(object):
  def __init__(self):
    self.q = 1

def b():
  t = a()
  print t.q
CaffeineAddiction
  • 803
  • 1
  • 14
  • 29
  • look at http://stackoverflow.com/questions/5626193/what-is-a-monkey-patch In fact it's answer to your question including examples – farincz Jun 17 '16 at 13:48
  • @farincz monkey patching doesn't seem to be working. In the MainUnitTest.py I am importing `Configuration.py` and then creating a `class Configuration(FakeFactory)` after it ... however `Main.py's main()` is still using the Configuration class in `Configuration.py` it seems like the two files are not sharing the same global name space – CaffeineAddiction Jun 17 '16 at 14:13
  • you must import Configuration and patch Configuration attribute on it and you must do it before main is first importet! than it should work – farincz Jun 17 '16 at 14:17
  • @farincz still not working ... see `Another example for demonstration` in my question – CaffeineAddiction Jun 17 '16 at 14:24
  • When you use the import `from bar import a` it imports `a` directly into the namespace so monkeypatching `bar` won't help, you need to override `main.a` instead. – Tadhg McDonald-Jensen Jun 17 '16 at 14:31
  • @TadhgMcDonald-Jensen could you give me an example w/ the foo.py and bar.py – CaffeineAddiction Jun 17 '16 at 14:35
  • I'm trying to find out how to do it with unittest before I post my answer, trying to find an old answer of mine that I think would exactly describe the issue and how to fix it (and how to do it cleanly with unittest) but I think the question may have been deleted :( – Tadhg McDonald-Jensen Jun 17 '16 at 14:37
  • Haha [found it](http://stackoverflow.com/a/36380658/5827215) ! Ok will write answer for your specific case. – Tadhg McDonald-Jensen Jun 17 '16 at 14:39

1 Answers1

2

when you use the import

from bar import a

it import the name directly into the module, so monkeypatching bar won't help, you need to override a directly in the main file:

def fake_a():
    print("OVERRIDEN!")

main.a = fake_a 

Do know that unittest has helper functions for this in the mock subpackage, I believe you could do something like:

from unittest.mock import patch

...
with patch("main.a", fake_a) as mock_obj: #there are additional things you can do with the mock_obj
    do_stuff()

This would work in your first example with configuration since the class that needs to be patched is not used in the global scope although foo uses bar.a as soon as it is loaded so you would need to patch it before even loading foo:

from unittest.mock import patch

...
with patch("bar.a", fake_a) as mock_obj: #there are additional things you can do with the mock_obj
    import foo #now when it loads it will be loaded with the patched name

However in this case foo.a would not be reverted at the end of the with block because it can't be caught by unittest... I really hope your actual use case doesn't use the stuff to be patched at module level.

Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59