Most advice on Python mocking is couched in short snippets outside of the unit test framework. This one works find, I'm trying to follow this advice, but it's not successful as soon as I embed it in a proper unit test. For example, this code which produces the output in the comment at the end:
# foo.py
def some_fn():
return 'some_fn'
class Foo( object ):
def method_1( self ):
return some_fn()
# bar.py (depends on foo.py)
import foo
class Bar( object ):
def method_2( self ):
tmp = foo.Foo()
return tmp.method_1()
# test.py (tests bar.py)
import unittest
import bar
from mock import patch
class Test( unittest.TestCase ):
def setUp( self ):
pass
def tearDown( self ):
pass
#patch( 'foo.some_fn' )
def test_bar( self, mock_some_fn ):
mock_some_fn.return_value = 'test-val-1'
tmp = bar.Bar()
print tmp.method_2()
self.assertEqual( tmp.method_2(), 'test-val-1' ) # line 32
mock_some_fn.return_value = 'test-val-2'
self.assertEqual( tmp.method_2(), 'test-val-2' )
if __name__ == "__main__":
unittest.main()
Which I run in PyDev and see:
Finding files... done.
Importing test modules ... done.
some_fn
======================================================================
FAIL: test_bar (test.foo.all.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/python2.7/site-packages/mock.py", line 1201, in patched
return func(*args, **keywargs)
File "/home/russ/dev/workspace/python-mocking/test/foo/all.py", line 32, in test_bar
self.assertEqual( tmp.method_2(), 'test-val-1' )
AssertionError: 'some_fn' != 'test-val-1'
----------------------------------------------------------------------
Ran 1 test in 0.002s
FAILED (failures=1)
Remove the unit test framework and this code runs fine (here, just the test.py part of the whole file):
...
# test.py (tests bar.py)
import bar
from mock import patch
#patch( 'foo.some_fn' )
def test_bar( mock_some_fn ):
mock_some_fn.return_value = 'test-val-1'
tmp = bar.Bar()
print tmp.method_2()
assert tmp.method_2() == 'test-val-1'
mock_some_fn.return_value = 'test-val-2'
assert tmp.method_2() == 'test-val-2'
which successfully produces, when run:
~/dev/workspace/python-mocking/test/foo $ python
Python 2.7.5 (default, Nov 3 2014, 14:26:24)
...
>>> import all0
>>> all0.test_bar()
test-val-1
What additional must I do to make this behave in the unit test framework?
The answer for me, a Python and PyDev neophyte, is that this is a PyDev problem. PyDev appears very touchy about how a project's unit testing is set up. I succeeded in making this code work by knitting together a new project set up exactly as this, including separate files.
python-mocking
+-- mocking_example
+-- test
| +-- __init__.py
| `-- test.py
+-- __init__.py
+-- bar.py
`-- foo.py
It might be worth noting that the structure above makes it so that, in test.py, bar must be imported thus:
import mocking_example.bar
and consumed that way, i.e.:
tmp = mocking_example.bar.Bar()
Related
Here is my structure:
directory/
__init__.py (blank)
myClass.py
test/
UnitTest.py
IntegrationTest.py
__init__.py (blank)
Let's use UnitTest as the example (both of them result in 0 tests). Feel free to critique my imports, I was fiddling around with it forever to import the class correctly so I can execute the UnitTest script. Also yes, I did copy a lot of code from other stack exchange questions in my search.
Before I moved to this structure, I had everything in one directory, so I know the test file works (outside of the imports)
import sys
from pathlib import Path
if __name__ == '__main__' and __package__ is None:
file = Path(__file__).resolve()
parent, top = file.parent, file.parents[2]
sys.path.append(str(top))
try:
sys.path.remove(str(parent))
except ValueError: # Already removed
pass
import directory.test
__package__ = 'directory.test'
from myClass import myClass
import unittest
from unittest import mock
print("Running Unit Tests...")
mc = myClass(params)
def mockingResponse(*args, **kwargs):
class MockResponse:
def __init__(self, json_data, status_code):
self.json_data = json_data
self.status_code = status_code
def json(self):
return self.json_data
#if/elses that return value
#mock.patch('myClass.requests.get', side_effect = mockingResponse)
class unitTest(unittest.TestCase):
print("this should run before test call")
def testsomefunc():
response = mc.somefunc()
differenceSet = set(response) ^ set(mockrespose from if/elses)
assert len(differenceSet) == 0
def testotherfunc():
#7 tests in total, same layout, different mockresponse
print("is this actually running")
unittest.main()
Then I get this in terminal:
privacy:~/git/directory$ python3 -m test.UnitTest.py
Running Unit Tests...
this should run before test call
is this actually running
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
Checking online, I know it's not the issue of not having my names start with test and it's not because I have it indented wrong. Can't find much else in my search.
My only other thought is that it might be because I ran it as a module, but if I don't I have more problems with importing myClass. Of the many solutions I've tried for importing, this is the only one that has worked thus far.
I have a bash script to execute my python tests and I would like to filter all test cases that have NOT_DONE in them
This is what I tried
python3 -m unittest discover -s ${FOLDER} -p 'test_((?!NOT_DONE).)*_ALL.py'
Input example :
test_word_NOT_DONE_more_words_alot_more_words_ALL.py <- This test shouldn't be executed
But this one should :
test_word_more_words_alot_more_words_ALL.py
Path Solution
Directory Structure
unittesting/
launcher.py
tests/
__init__.py
test_finished.py
test_NOT_DONE.py
folder/
__init__.py
test_finished2.py
test_NOT_DONE2.py
Inside each test file is print(__file__), nested under a TestCase method. Therefore, only if the module is imported and the test cases run, will it be executed.
Code:
import importlib
import os
import sys
import unittest
HOME = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, 'tests')
def check_file(file):
'''Check if file is test'''
return all((
'NOT_DONE' not in file,
file.endswith('.py'),
file != '__init__.py'
))
def find_paths(home=HOME):
'''Find all paths'''
os.chdir(HOME)
for root, dirs, files in os.walk('tests'):
for file in files:
if check_file(file):
if root != 'tests':
yield os.path.join(root[len('tests/'):], file)
else:
yield file
def normalize(path):
'''Normalize path to dotted name'''
path = os.path.splitext(path)[0]
unix = path.replace('/', '.')
return unix.replace('\\', '.')
def tests(paths=None):
'''Load and run tests'''
if paths is None:
paths = map(normalize, find_paths())
modules = (importlib.import_module(i) for i in paths)
suite = unittest.TestSuite()
loader = unittest.TestLoader()
for module in modules:
tests = loader.loadTestsFromModule(module)
suite.addTests(tests)
runner = unittest.TextTestRunner()
runner.run(suite)
if __name__ == '__main__':
tests()
As you can see, this gets unwieldly quickly, and very hard to manage. There's a simpler way. It runs, however.
$ python /home/alex/git/unittesting/launcher.py
tests/test_finished.pyc
.tests/folder/test_finished2.pyc
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
Pythonic Solution
Inside each of my files that is not complete, I put the variable NOT_DONE = True, and each of my classes has a decorator skipif.
Directory Structure
unittesting/
launcher.py
tests/
__init__.py
test1.py
test2.py
folder/
__init__.py
test3.py
test4.py
In this example, test2 and test4 have NOT_DONE = True, while test1 and test3 have NOT_DONE = False.
An example file is as follows:
import unittest
NOT_DONE = False
# CASES
# -----
#unittest.skipIf(NOT_DONE, 'Reason')
class TestPrint(unittest.TestCase):
def test_print(self):
print(__file__)
if __name__ == '__main__':
unittest.main()
Now, to run I simply do:
$ python -m unittest discover tests
tests/test1.py
tests/folder/test3.py
.
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK (skipped=2)
Best Approach
Unfinished unittests should have a unittest.skipIf(True, 'Unfinished') line, so you get control not only at the module level, but also at the class or even method level. In the following example, I have one, finished unittest and one unfinished unittest. Running the example skips the first unittest, but runs the rest of the module.
import unittest
# CASES
# -----
#unittest.skipIf(True, 'Not Finished')
class TestPrint(unittest.TestCase):
def test_print(self):
print(__file__)
class TestPrinting(unittest.TestCase):
def test_print(self):
print(__file__)
if __name__ == '__main__':
unittest.main()
Summary: Using PyDev, I can't get one Python module (FooTest) to import another (FooMock) in the same (test) source folder.
I have two source folders and four Python modules, with the directory structure below. The minimal example code is at the end of the question. Although I have provided a lot of information, please don't let this intimidate you - it's a simple problem scenario.
PyDevProject/
src/ (source folder for prod code)
foo/
__init__.py
Bar.py
Foo.py
test/ (source folder for tests)
foo/
__init__.py
FooMock.py
FooTest.py
The issue here is that when I attempt to import FooMock class in the FooTest module, I get the following error (running PyUnit to run the FooTest tests):
Finding files... done.
Importing test modules ... Traceback (most recent call last):
File "/Applications/Eclipse.app/Contents/Eclipse/plugins/org.python.pydev_4.5.4.201601292234/pysrc/_pydev_runfiles/pydev_runfiles.py", line 468, in __get_module_from_str
mod = __import__(modname)
File "/Users/me/Development/krtiWorkspace/ImportTest/test/foo/FooTest.py", line 4, in <module>
from foo.FooMock import FooMock
ImportError: No module named FooMock
ERROR: Module: FooTest could not be imported (file: /Users/me/Development/krtiWorkspace/ImportTest/test/foo/FooTest.py).
done.
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
Things I've tried:
Even though FooMock and FooTest are both in the same Python package, FooMock can't be imported.
Moving FooMock to under the src/ directory works, even if placed under a unique Python package (as long as it's under src/). But this isn't acceptable for me (to have test code in production source folders).
I've ensured the PYTHONPATH settings in PyDev are set to specify both folders are source folders.
Copying and pasting the FooMock code into the FooTest code works. But this isn't acceptable for me (since I'd like to reference FooMock elsewhere, in multiple test files and I don't want multiple copies of FooMock floating around consequently).
Is there anything I can do about this?
Code:
# Code for Bar.py -----------------------------------------------------
class Bar(object):
def __init__(self, foo):
self.foo = foo
def next(self):
self.foo.incr()
# Code for Foo.py -----------------------------------------------------
class Foo(object):
def __init__(self):
self.num = 0
def incr(self):
self.num = self.num + 1
# Code for FooMock.py -------------------------------------------------
class FooMock(object):
def mock_incr(self):
self.hit = True
# Code for FooTest.py -------------------------------------------------
import unittest
from foo.Foo import Foo
from foo.Bar import Bar
from foo.FooMock import FooMock
class Test(unittest.TestCase):
def test_Foo(self):
foo = Foo()
foo.incr()
self.assertEquals(1, foo.num, "should be 1")
def test_Bar(self):
foo = FooMock()
bar = Bar(foo)
bar.next()
if __name__ == "__main__":
unittest.main()
Screenshot of directory:
Image showing directory structure and PYTHONPATH as previously explained
You have a module named foo in your PYTHONPATH twice. In the first directory, where the package foo is found, there is indeed no module named FooMock.
Can you rename the module foo under the test source directory to something else? Like footests instead of foo?
I have two files
spike.py
class T1(object):
def foo(self, afd):
return "foo"
def get_foo(self):
return self.foo(1)
def bar():
return "bar"
test_spike.py:
from unittest import TestCase
import unittest
from mock import patch, MagicMock
from spike import T1, bar
class TestStuff(TestCase):
#patch('spike.T1.foo', MagicMock(return_value='patched'))
def test_foo(self):
foo = T1().get_foo()
self.assertEqual('patched', foo)
#patch('spike.bar')
def test_bar(self, mock_obj):
mock_obj.return_value = 'patched'
bar = bar()
self.assertEqual('patched', bar)
if __name__ == "__main__":
unittest.main()
When I run python test_spike.py, the first test case would pass, but the second would fail.
and I switch to use nosetests test_spike.py, then both two are failed.
I don't understand how this happened? These cases supposed to pass all.
Access bar using spike.bar. Imported bar is not affected by mock.patch.
from unittest import TestCase
import unittest
from mock import patch, MagicMock
from spike import T1
import spike # <----
class TestShit(TestCase):
#patch('spike.T1.foo', MagicMock(return_value='patched'))
def test_foo(self):
foo = T1().get_foo()
self.assertEqual('patched', foo)
#patch('spike.bar')
def test_bar(self, mock_obj):
mock_obj.return_value = 'patched'
bar = spike.bar() # <-----
self.assertEqual('patched', bar)
if __name__ == "__main__":
unittest.main()
For test_foo you are not using patch correctly. You should be using it like this:
class TestFoo(TestCase):
#patch.object(T1, 'foo', MagicMock(return_value='patched'))
def test_foo(self):
foo = T1().get_foo()
self.assertEqual('patched', foo)
that gives me:
nosetests test_spike.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Now the second example does not work because you import bar function (get a reference to it) and then try to mock it. When you mock something you can't change what your variables hold (reference to original function). To fix this you should use #falsetru suggested method like:
from unittest import TestCase
import unittest
from mock import patch
import spike
class TestFoo(TestCase):
#patch('spike.bar')
def test_bar(self, mock_obj):
mock_obj.return_value = 'patched'
value = spike.bar()
self.assertEqual('patched', value)
if __name__ == "__main__":
unittest.main()
this gives me:
python test_spike.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
But when I try to run it with nose I get:
nosetests test_spike.py
F
======================================================================
FAIL: test_bar (src.test_spike.TestFoo)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/zilva/envs/test/local/lib/python2.7/site-packages/mock/mock.py", line 1305, in patched
return func(*args, **keywargs)
File "/home/zilva/git/test/src/test_spike.py", line 11, in test_bar
self.assertEqual('patched', value)
AssertionError: 'patched' != 'bar'
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
This happends because I am patching not in the right place. My directory structure is:
test/
└── src/
├── spike.py
├── test_spike.py
└── __init__.py
and I run tests from src directory so I should be patching using path from project root directory like:
#patch('src.spike.bar')
and this would give me:
nosetests test_spike.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
or if I am at test directory:
nosetests src/test_spike.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
To elaborate on the very helpful top answer, let me paraphrase the official documentation for unittest.mock.
a.py
class SomeClass:
...
b.py
import a
from a import SomeClass
def some_function():
a.SomeClass()
SomeClass()
If you write mock.patch('a.SomeClass'), this will affect the first line of some_function. If you write mock.patch('b.SomeClass'), this will affect the second line.
I wrote a little function that dynamically defines unittest.TestCase classes (trivial version below).
When I moved it out of the same source file into its own module, I can't figure out how to get unittest to discover the new classes. Calling unittest.main() from either file doesn't execute any tests.
factory.py:
import unittest
_testnum = 0
def test_factory(a, b):
global _testnum
testname = 'dyntest' + str(_testnum)
globals()[testname] = type(testname, (unittest.TestCase,), {'testme': lambda self: self.assertEqual(a, b)})
_testnum += 1
def finish():
unittest.main()
someotherfile.py:
from factory import test_factory, finish
test_factory(1, 1)
test_factory(1, 2)
if __name__ == '__main__':
finish()
Output:
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
So it doesn't execute any tests.
Note that keeping it all in the same file works as expected:
import unittest
_testnum = 0
def test_factory(a, b):
global _testnum
testname = 'dyntest' + str(_testnum)
globals()[testname] = type(testname, (unittest.TestCase,), {'testme': lambda self: self.assertEqual(a, b)})
_testnum += 1
test_factory(1, 1)
test_factory(1, 2)
if __name__ == '__main__':
unittest.main()
Output (as expected):
.F
======================================================================
FAIL: testme (__main__.dyntest1)
----------------------------------------------------------------------
Traceback (most recent call last):
File "partb.py", line 11, in <lambda>
globals()[testname] = type(testname, (unittest.TestCase,), {'testme': lambda self: self.assertEqual(a, b)})
AssertionError: 1 != 2
----------------------------------------------------------------------
Ran 2 tests in 0.008s
FAILED (failures=1)
How I use my test_factory() function such that I can execute all of the TestCase objects it defines from a separate source file?
The general idea (what unittest.main does for you) is:
suite = unittest.TestLoader().loadTestsFromTestCase(SomeTestCase)
unittest.TextTestRunner(verbosity=2).run(suite)
as per http://docs.python.org/library/unittest.html?highlight=unittest#module-unittest . Your test cases are hidden in globals() by the test_factory function, so just do a dir(), find the globals that are instances of unittest.TestCase (or ones with names starting with 'dyntest', etc), and just build your suite that way and run it.
By default, unittest.main() looks for unit TestCase objects in the main module. The test_factory creates the TestCase objects in its own module. That's why moving it outside of the main module causes the behavior you see.
Try:
def finish():
unittest.main(module=__name__)