How to mock an uninitialized global variable? - python

I have an uninitialzed global variable in my module that is properly initialized during application startup. For typechecking I use the syntax var: Type without a value from python >= 3.6 so I do not have to complicate the typechecking for the default value (which would be None).
Now I want to unittest another function that uses this global variable but I get an error from unittest.mock.patch. Here is a simplified version of what I am doing:
File mod.py:
global_var: bool
#global_var: bool = False
def init(val: bool) -> None:
global global_var
global_var = val
def do_stuff() -> str:
return "a" if global_var else "b"
File test.py:
import unittest.mock, mod
class MockUninitializedGlobal(unittest.TestCase):
def test_it(self):
with unittest.mock.patch("mod.global_var", True):
actual = mod.do_stuff()
expected = "a"
self.assertEqual(expected, actual)
I run it with python3 -m unittest test.py.
The exception is
Traceback (most recent call last):
File "/home/luc/soquestion/test.py", line 4, in test_it
with mock.patch("mod.global_var", True):
File "/nix/store/vs4vj1yzqj1bkcqkf3b6sxm6jfy1gb4j-python3-3.7.7/lib/python3.7/unittest/mock.py", line 1323, in __enter__
original, local = self.get_original()
File "/nix/store/vs4vj1yzqj1bkcqkf3b6sxm6jfy1gb4j-python3-3.7.7/lib/python3.7/unittest/mock.py", line 1297, in get_original
"%s does not have the attribute %r" % (target, name)
AttributeError: <module 'mod' from '/home/luc/soquestion/mod.py'> does not have the attribute 'global_var'
If I comment line 1 and uncomment line 2 in mod.py the test passes.
Is there any way to make the test pass without definig a default value for the global variable in my application code?
Edit: As noted in the comments the actual variable is a parsed config file which I do not want to load during tests. It is used in a command line application and therefore the variable is always set after command line parsing is done. The real code is here and here. In the actual test I am not trying to mock the global variable as a bool directly but some attribute on the config object.

I used the setUp and tearDown methods on the unittest.TestCase class to solve this.
With my edit above about it actually being a config object the code looks like this:
class Config:
def __init__(self, filename: str):
"Load config file and set self.stuff"
self.stuff = _code_to_parse(filename)
config: Config
def do_stuff() -> str:
return "a" if config.stuff else "b"
And the test creates a mocked config object before the tests in order to mock some attribute on the config object during the test. In the teardown I also delete the object again so that nothing will spill to the next test case.
The actual mocking of the attributes I need is then done in a with block in the actual test as it is (a) cleaner and (b) I can just mock what I need in contrast to mocking all attributes on the config object in the setUp method.
import unittest, unittest.mock, mod
class MockUninitializedGlobal(unittest.TestCase):
def setUp(self):
mod.config = unittest.mock.Mock(spec=mod.Config)
def tearDown(self):
del mod.config
def test_it(self):
with unittest.mock.patch("mod.config.stuff", True):
actual = mod.do_stuff()
expected = "a"
self.assertEqual(expected, actual)

Related

Class instance fails isinstance check

Hi
I have some code on my CI failing (local runs do not fail).
The problem is that class instance fails isinstance() check.
Code:
File: main.py
class MyController(SuperController):
# Overrides default definition of get_variables_context()
from my_options import get_variables_context
File: my_options.py
...
def get_variables_context(self: SuperController, **kwargs):
from main import MyController
self: MyController
print(f"type(self) is {type(self)} (it {'IS' if (isinstance(self, MyController)) else 'IS NOT'} a subclass of MyController)")
_super = super(MyController, self).get_variables_context(**kwargs) or dict()
_super.update(result)
return _super
Got output and error:
type(self) is <class '__main__.SomeController'> (it IS NOT a subclass of SomeController
Traceback (most recent call last):
File "main.py", line 24, in <module>
SomeController.main(**params)
File "/builds/RND/my/rcv-nginx/tests/nginx_tests/flow.py", line 391, in main
_tests_suite, _, _ = self.prepare()
File "/builds/RND/my/rcv-nginx/tests/nginx_tests/flow.py", line 359, in prepare
context['variables_context'] = self.get_variables_context(**context)
File "/builds/RND/my/tests/integration/my_options.py", line 81, in get_variables_context
_super = super(SomeController, self).get_variables_context(**kwargs) or dict()
TypeError: super(type, obj): obj must be an instance or subtype of type
I've found the solution while investigating the root cause.
In the local run, ...
I actually call the python unittest which then calls main.py which then creates a class MyController which then calls my_options.py, and the class is added to the loaded module 'main'.
Then, MyController.get_variables_context asks for the module 'main', which is already loaded, then for the class MyController in that module, so the same type instance is returned and type check succeeds.
In the CI run, ...
I call directly main.py with the argument "test" (which should create a controller and run all tests from it via unittest), so the class MyController is created inside module __main__. MyController.get_variables_context still asks for the MyController class in main.py, but the module 'main' is not loaded here, so python loads it, creates new class MyController, and then returns it.
So, basically the answer is ...
to move MyController from main.py to the other file, i.e. controller.py

python unittest mocking / patching

Not wanting to test manually my code, I'm trying to write a test which mocks/patches one of my dependencies (PyInquirer is a pretty neat package which handles the CLI for me - question dicts in, answer dicts out).
However, being very new to Python, I'm having difficulties with mocking that dependency. Here's the code I'm testing:
from PyInquirer import prompt
class Foo:
def bar(self):
# this line is asking the user for inpit, and that's what I want to mock.
a = prompt({'name': 'q',
'type': 'input',
'message': 'well, foo'})
print("f is", f)
return a
And this is the test:
import unittest
from unittest.mock import patch
from Foo import Foo
class TestFoo(unittest.TestCase):
#patch('PyInquirer.prompt', return_value=24)
def test_bar(self):
f = Foo()
a = f.bar()
assert a == 24
if __name__ == '__main__':
unittest.main()
(the real code is obviously more complicated, but this is the essence of the problem). Which manifests itself as:
Error
Traceback (most recent call last):
File "/usr/local/Cellar/python/3.6.5_1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/case.py", line 59, in testPartExecutor
yield
File "/usr/local/Cellar/python/3.6.5_1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/case.py", line 605, in run
testMethod()
File "/usr/local/Cellar/python/3.6.5_1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 1179, in patched
return func(*args, **keywargs)
TypeError: test_bar() takes 1 positional argument but 2 were given
I'm quite confused.
If I omit the patch decorator, the invocation fails with an expected assertion error - the dictionary produced by prompt isn't equal to 24. But if I do provide the decorator, I get the argument mismatch above. And indeed the last line in the stacktrace does show the function "func", which I presume is what the decorator was applied to, is invoked with two arguments... but... why? Can't be the essence of a problem? That only functions with arity of two can be thus patched =)
Your class uses the name Foo.prompt (because of how you import it), so that's what you need to patch.
class TestFoo(unittest.TestCase):
#patch('Foo.prompt', return_value=24)
def test_bar(self, mock_prompt):
f = Foo()
a = f.bar()
assert a == 24
You also need to add a parameter to test_bar to receive the patched object, whether or not you plan to use it. If you don't want to do that,
you can move the call to patch inside the method, using it with a with statement.
class TestFoo(unittest.TestCase):
def test_bar(self):
with patch('Foo.prompt', return_value=24):
f = Foo()
a = f.bar()
assert a == 24

multiprocessing and modules

I am attempting to use multiprocessing to call derived class member function defined in a different module. There seem to be several questions dealing with calling class methods from the same module, but none from different modules. For example, if I have the following structure:
main.py
multi/
__init__.py (empty)
base.py
derived.py
main.py
from multi.derived import derived
from multi.base import base
if __name__ == '__main__':
base().multiFunction()
derived().multiFunction()
base.py
import multiprocessing;
# The following two functions wrap calling a class method
def wrapPoolMapArgs(classInstance, functionName, argumentLists):
className = classInstance.__class__.__name__
return zip([className] * len(argumentLists), [functionName] * len(argumentLists), [classInstance] * len(argumentLists), argumentLists)
def executeWrappedPoolMap(args, **kwargs):
classType = eval(args[0])
funcType = getattr(classType, args[1])
funcType(args[2], args[3:], **kwargs)
class base:
def multiFunction(self):
mppool = multiprocessing.Pool()
mppool.map(executeWrappedPoolMap, wrapPoolMapArgs(self, 'method', range(3)))
def method(self,args):
print "base.method: " + args.__str__()
derived.py
from base import base
class derived(base):
def method(self,args):
print "derived.method: " + args.__str__()
Output
base.method: (0,)
base.method: (1,)
base.method: (2,)
Traceback (most recent call last):
File "e:\temp\main.py", line 6, in <module>
derived().multiFunction()
File "e:\temp\multi\base.py", line 15, in multiFunction
mppool.map(executeWrappedPoolMap, wrapPoolMapArgs(self, 'method', range(3)))
File "C:\Program Files\Python27\lib\multiprocessing\pool.py", line 251, in map
return self.map_async(func, iterable, chunksize).get()
File "C:\Program Files\Python27\lib\multiprocessing\pool.py", line 567, in get
raise self._value
NameError: name 'derived' is not defined
I have tried fully qualifying the class name in the wrapPoolMethodArgs method, but that just gives the same error, saying multi is not defined.
Is there someway to achieve this, or must I restructure to have all classes in the same package if I want to use multiprocessing with inheritance?
This is almost certainly caused by the ridiculous eval based approach to dynamically invoking specific code.
In executeWrappedPoolMap (in base.py), you convert a str name of a class to the class itself with classType = eval(args[0]). But eval is executed in the scope of executeWrappedPoolMap, which is in base.py, and can't find derived (because the name doesn't exist in base.py).
Stop passing the name, and pass the class object itself, passing classInstance.__class__ instead of classInstance.__class__.__name__; multiprocessing will pickle it for you, and you can use it directly on the other end, instead of using eval (which is nearly always wrong; it's code smell of the strongest sort).
BTW, the reason the traceback isn't super helpful is that the exception is raised in the worker, caught, pickle-ed, and sent back to the main process and re-raise-ed. The traceback you see is from that re-raise, not where the NameError actually occurred (which was in the eval line).

How to run tests cross-platform/browser using decorator in Python?

I have a test suite as below:
import unittest
from selenium import webdriver
SAUCE_USERNAME = 'xxx'
SAUCE_ACCESS_KEY = 'xxx'
sauce = SauceClient(SAUCE_USERNAME, SAUCE_ACCESS_KEY)
browsers = [{"platform": "Mac OS X 10.9",
"browserName": "chrome",
"version": "31"},
{"platform": "Windows 8.1",
"browserName": "internet explorer",
"version": "11"}]
def on_platforms(platforms):
def decorator(base_class):
module = sys.modules[base_class.__module__].__dict__
for i, platform in enumerate(platforms):
d = dict(base_class.__dict__)
d['desired_capabilities'] = platform
name = "%s_%s" % (base_class.__name__, i + 1)
module[name] = new.classobj(name, (base_class,), d)
return decorator
#on_platforms(browsers)
class MyTestSuite(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.desired_capabilities['name'] = cls.id()
sauce_url = "http://%s:%s#ondemand.saucelabs.com:80/wd/hub"
cls.driver = webdriver.Remote(
desired_capabilities=cls.desired_capabilities,
command_executor=sauce_url % (SAUCE_USERNAME,SAUCE_ACCESS_KEY))
cls.driver.implicitly_wait(30)
def test_1from_sauce(self):
pass
def test_2from_sauce(self):
pass
#classmethod
def tearDownClass(cls):
cls.driver.quit()
if __name__ == "__main__":
unittest.main()
My goal is to run test_1from_sauce and test_2from_sauce for browsers/platforms in browsers and ALSO I do want to do both of them in a row on the browser that is setup on setUpClass. To explain more, I want to open a browser and do both tests then quit that driver and start another driver.
Right now when I run this code, I get this error:
TypeError: unbound method setUpClass() must be called with SeleniumSauce_2 instance as first argument (got nothing instead)
I know there should be a modification in class and subclass declaration but I do not know what should I do or what part should I change.
EDITED: I omitted the following line and it worked fine:
cls.desired_capabilities['name'] = cls.id()
The code you show in your question is missing the imports for sys and new. Secondly, the error I get when I run your code after adding the right imports is not what you report in your question but this:
EE
======================================================================
ERROR: setUpClass (__main__.MyTestSuite_1)
----------------------------------------------------------------------
Traceback (most recent call last):
File "tests.py", line 33, in setUpClass
cls.desired_capabilities['name'] = cls.id()
TypeError: unbound method id() must be called with MyTestSuite_1 instance as first argument (got nothing instead)
I've omitted a second ERROR which is the same as above except that it is for MyTestSuite_2 rather than MyTestSuite_1. The problem is pretty clear. You call the id() member of cls but id() is an instance method not a class method so it needs an instance of the class, not the class itself. I'm not sure what it is you want in the end but if you use this for your setUpClass, then the error no longer occurs and an attempt is made to connect:
#classmethod
def setUpClass(cls):
cls.desired_capabilities['name'] = cls.__name__
sauce_url = "http://%s:%s#ondemand.saucelabs.com:80/wd/hub"
cls.driver = webdriver.Remote(
desired_capabilities=cls.desired_capabilities,
command_executor=sauce_url % (SAUCE_USERNAME, SAUCE_ACCESS_KEY))
cls.driver.implicitly_wait(30)
The only modification is the first line of the method.
By the way, I don't get why you use new.classobj. For one thing, the new module is deprecated. Secondly, using the type built-in rather than new.classobj works.

Python – How to mock a single function

From the Mock docs, I wasn't able to understand how to implement the following type of pattern successfully. fetch_url does not exist inside of a class.
My function in the auth.py file:
def fetch_url(url, method=urlfetch.GET, data=''):
"""Send a HTTP request"""
result = urlfetch.fetch(url=url, method=method, payload=data,
headers={'Access-Control-Allow-Origin': '*'})
return result.content
My test:
import unittest
from mock import Mock
class TestUrlFetch(unittest.TestCase):
def test_fetch_url(self):
from console.auth import fetch_url
# Create a mock object based on the fetch_url function
mock = Mock(spec=fetch_url)
# Mock the fetch_url function
content = mock.fetch_url('https://google.com')
# Test that content is not empty
self.assertIsNotNone(content)
If what I'm doing is completely in the wrong direction, please shed some light on the correct solution.
The test is not working, and is producing the following error:
======================================================================
ERROR: test_fetch_url (console.tests.test_auth.TestUrlFetch)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/bengrunfeld/Desktop/Work/code/wf-ghconsole/console/tests/test_auth.py", line 34, in test_fetch_url
content = mock.fetch_url('https://google.com')
File "/Users/bengrunfeld/.virtualenvs/env2/lib/python2.7/site-packages/mock.py", line 658, in __getattr__
raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'fetch_url'
-------------------- >> begin captured logging << --------------------
root: DEBUG: Using threading.local
--------------------- >> end captured logging << ---------------------
----------------------------------------------------------------------
Ran 1 test in 0.277s
FAILED (errors=1)
First of all, as univerio's comment suggests you should call you mock like this:
mock('https://google.com')
Your test should pass after that fix, but probably that mock doesn't do what you really want. I've encountered a few problems with spec and autospec.
Mocks created with Mock(spec=) don't check number of arguments they are called with. I've just looked through the docs and they don't state that, but for some reason I expected it to work. Autospecced mocks do check the arguments.
By default both spec and autospec function mocks return mock objects when you call them. This may be not what you want when you mock a function that does not return anything. In this case you can set the return_value manually:
def foo():
pass
mock_foo = Mock(spec=foo, return_value=None)
mock_foo()

Categories

Resources