I am trying to unit test a script that reads a requirements.txt file and goes through each line installing it with pip. However it my unit test it doesn't seem to enter the loop because the self.requirements is empty. I tried setting it to self.setupvirtualenv.requirements to ['foobar'] but this doesn't work.
My question is how can I mock the self.requirements list so that it enters the for loop and the subprocess.call gets called?
def install_requirements(self):
self.requirements = open(self.requirements_path, 'r').readlines()
for req in self.requirements:
req = req.strip()
pip_command = r'pip install {}'.format(req)
subprocess.call(pip_command, shell=True)
My unit test:
import setupvirualenv
import unittest
from mock import patch, Mock
def test_install_requirements(self, mock_open, mock_subprocess_call):
self.setupvirtualenv.requirements = ['foobar']
self.setupvirtualenv.install_requirements()
mock_open.assert_called_once_with('foobar', 'r')
mock_subprocess_call.assert_called_once()
The failure message
AssertionError: Expected 'call' to have been called once. Called 0 times.
You should mock the open call, so that readlines() returns the dummy data.
It looks as though you may be missing the patch decorators from your test method. I have included what they should look like below.
from mock import patch
#patch('setupvirtualenv.subprocess.call')
#patch('setupvirtualenv.open')
def test_install_requirements(self, mock_open, mock_subprocess_call):
# Mock the call to open() and the return value from readlines()
mock_open.return_value.readlines.return_value = ['foobar\n']
self.setupvirtualenv.install_requirements()
mock_open.assert_called_once_with('foobar', 'r')
mock_subprocess_call.assert_called_once()
Related
I want to unit test a piece of code from a module using the Mock testing library.
# File_name: do.py
assert __name__ == "__main__", "This module should NOT be imported."
from disk import Disk
def bool_fun(args):
res = Disk.disks()
print(res)
if args not in res:
return -1
return args
The above file contains assert name == "__main__" which is blocking the module from getting import in test file.
# File_name: do_test.py
import unittest
from unittest import mock
import do
class DOTest(unittest.TestCase):
#mock.patch('do.Disk.disks')
def test_bool_fun(self, mock_disks):
args = 4
mock_disks.return_value = [4,3,2,3,4]
res = do.bool_fun(args)
self.assertEqual(res, args)
if __name__ == "__main__":
unittest.main()
One more helper file which is imported in do.py file,
# File_name: disk.py
import random
class Disk():
def __init__(self):
pass
def disks():
lis = []
for i in range(5):
lis.append(random.randint(1,10))
return lis
Assuming all files are kept in the same directory, I'm getting below error when trying to test it.
assert __name__ == "__main__", "This module should NOT be imported."
AssertionError: This module should NOT be imported.
However, it works great if I remove the assert code from do.py file.
Can you please tell me how to test such files containing assert name == "__main__" in it.
Doing a lot of research and following over 100+ Stackoverflow links I can not find a solution that can straight away help in importing the file to UT.
Reason
You can mock the assert or the name because it hasn't been imported yet. You have to import it to assert, which won't work because you'll hit the exception.
Solution
So a hacky way, which should be OK since this is a UT, is to do an os.system call to "sed -i '/assert __name__/d' path/to/file" before importing it. That will just remove the line from the file at the run time :)
Example
import os
os.system("sed -i '/assert __name__/d' /path/to/file")
Environment: Python 3, tornado 4.4. The normal unittests cannot be used because methods are asynchronous. There is ttp://www.tornadoweb.org/en/stable/testing.html that explains how to do unit testing for asynchronous code. But that works with tornado coroutines ONLY. The classes I want to test are using the async def statements, and they cannot be tested this way. For example, here is a test case that uses ASyncHTTPClient.fetch and its callback parameter:
class MyTestCase2(AsyncTestCase):
def test_http_fetch(self):
client = AsyncHTTPClient(self.io_loop)
client.fetch("http://www.tornadoweb.org/", self.stop)
response = self.wait()
# Test contents of response
self.assertIn("FriendFeed", response.body)
But my methods are declared like this:
class Connection:
async def get_data(url, *args):
# ....
And there is no callback. How can I "await" for this method from a test case?
UPDATE: based on Jessie's answer, I created this MWE:
import unittest
from tornado.httpclient import AsyncHTTPClient
from tornado.testing import AsyncTestCase, gen_test, main
class MyTestCase2(AsyncTestCase):
#gen_test
async def test_01(self):
await self.do_more()
async def do_more(self):
self.assertEqual(1+1, 2)
main()
The result is this:
>py -3 -m test.py
E
======================================================================
ERROR: all (unittest.loader._FailedTest)
----------------------------------------------------------------------
AttributeError: module '__main__' has no attribute 'all'
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
[E 170205 10:05:43 testing:731] FAIL
There is no traceback. But if I replace tornado.testing.main() with unittest.main() then it suddenly starts working.
But why? I guessed that for asnyc unit tests, I need to use tornado.testing.main ( http://www.tornadoweb.org/en/stable/testing.html#tornado.testing.main )
I'm confused.
UPDATE 2: It is a bug in tornado.testing. Workaround:
all = MyTestCase2
main()
Instead of using the self.wait / self.stop callbacks, you can wait for "fetch" to complete by using it in an "await" expression:
import unittest
from tornado.httpclient import AsyncHTTPClient
from tornado.testing import AsyncTestCase, gen_test
class MyTestCase2(AsyncTestCase):
#gen_test
async def test_http_fetch(self):
client = AsyncHTTPClient(self.io_loop)
response = await client.fetch("http://www.tornadoweb.org/")
# Test contents of response
self.assertIn("FriendFeed", response.body.decode())
unittest.main()
The other change I had to make in your code is to call "decode" on the body in order to compare the body (which is bytes) to "FriendFeed" which is a string.
I'm following the instructions from this post but cannot get my methods recognized globally.
The error message:
ERROR: test_suggest_performer (__builtin__.TestSearch)
----------------------------------------------------------------------
Traceback (most recent call last):
File "applications/myapp/tests/test_search.py", line 24, in test_suggest_performer
suggs = suggest_flavors("straw")
NameError: global name 'suggest_flavors' is not defined
My test file:
import unittest
from gluon.globals import Request
db = test_db
execfile("applications/myapp/controllers/search.py", globals())
class TestSearch(unittest.TestCase):
def setUp(self):
request = Request()
def test_suggest_flavors(self):
suggs = suggest_flavors("straw")
self.assertEqual(len(suggs), 1)
self.assertEqual(suggs[0][1], 'Strawberry')
My controller:
def suggest_flavors(term):
return []
Has anyone successfully completed unit testing like this in web2py?
Please see: http://web2py.com/AlterEgo/default/show/260
Note that in your example the function 'suggest_flavors' should be defined at 'applications/myapp/controllers/search.py'.
I don't have any experience with web2py, but used other frameworks a lot. And looking at your code I'm confused a bit. Is there an objective reason why execfile should be used? Isn't it better to use regular import statement. So instead of execfile you may write:
from applications.myapp.controllers.search import suggest_flavors
It's more clear code for pythoners.
Note, that you should place __init__.py in each directory along the path in this case, so that dirs will form package/module hierarchy.
I created custom django-admin commands
But, I don't know how to test it in standard django tests
If you're using some coverage tool it would be good to call it from the code with:
from django.core.management import call_command
from django.test import TestCase
class CommandsTestCase(TestCase):
def test_mycommand(self):
" Test my custom command."
args = []
opts = {}
call_command('mycommand', *args, **opts)
# Some Asserts.
From the official documentation
Management commands can be tested with the call_command() function. The output can be redirected into a StringIO instance
You should make your actual command script the minimum possible, so that it just calls a function elsewhere. The function can then be tested via unit tests or doctests as normal.
you can see in github.com example
see here
def test_command_style(self):
out = StringIO()
management.call_command('dance', style='Jive', stdout=out)
self.assertEquals(out.getvalue(),
"I don't feel like dancing Jive.")
To add to what has already been posted here. If your django-admin command passes a file as parameter, you could do something like this:
from django.test import TestCase
from django.core.management import call_command
from io import StringIO
import os
class CommandTestCase(TestCase):
def test_command_import(self):
out = StringIO()
call_command(
'my_command', os.path.join('path/to/file', 'my_file.txt'),
stdout=out
)
self.assertIn(
'Expected Value',
out.getvalue()
)
This works when your django-command is used in a manner like this:
$ python manage.py my_command my_file.txt
A simple alternative to parsing stdout is to make your management command exit with an error code if it doesn't run successfully, for example using sys.exit(1).
You can catch this in a test with:
with self.assertRaises(SystemExit):
call_command('mycommand')
I agree with Daniel that the actual command script should do the minimum possible but you can also test it directly in a Django unit test using os.popen4.
From within your unit test you can have a command like
fin, fout = os.popen4('python manage.py yourcommand')
result = fout.read()
You can then analyze the contents of result to test whether your Django command was successful.
I want to write a unit test for a Django manage.py command that does a backend operation on a database table. How would I invoke the management command directly from code?
I don't want to execute the command on the Operating System's shell from tests.py because I can't use the test environment set up using manage.py test (test database, test dummy email outbox, etc...)
The best way to test such things - extract needed functionality from command itself to standalone function or class. It helps to abstract from "command execution stuff" and write test without additional requirements.
But if you by some reason cannot decouple logic form command you can call it from any code using call_command method like this:
from django.core.management import call_command
call_command('my_command', 'foo', bar='baz')
Rather than do the call_command trick, you can run your task by doing:
from myapp.management.commands import my_management_task
cmd = my_management_task.Command()
opts = {} # kwargs for your command -- lets you override stuff for testing...
cmd.handle_noargs(**opts)
the following code:
from django.core.management import call_command
call_command('collectstatic', verbosity=3, interactive=False)
call_command('migrate', 'myapp', verbosity=3, interactive=False)
...is equal to the following commands typed in terminal:
$ ./manage.py collectstatic --noinput -v 3
$ ./manage.py migrate myapp --noinput -v 3
See running management commands from django docs.
The Django documentation on the call_command fails to mention that out must be redirected to sys.stdout. The example code should read:
from django.core.management import call_command
from django.test import TestCase
from django.utils.six import StringIO
import sys
class ClosepollTest(TestCase):
def test_command_output(self):
out = StringIO()
sys.stdout = out
call_command('closepoll', stdout=out)
self.assertIn('Expected output', out.getvalue())
Building on Nate's answer I have this:
def make_test_wrapper_for(command_module):
def _run_cmd_with(*args):
"""Run the possibly_add_alert command with the supplied arguments"""
cmd = command_module.Command()
(opts, args) = OptionParser(option_list=cmd.option_list).parse_args(list(args))
cmd.handle(*args, **vars(opts))
return _run_cmd_with
Usage:
from myapp.management import mycommand
cmd_runner = make_test_wrapper_for(mycommand)
cmd_runner("foo", "bar")
The advantage here being that if you've used additional options and OptParse, this will sort the out for you. It isn't quite perfect - and it doesn't pipe outputs yet - but it will use the test database. You can then test for database effects.
I am sure use of Micheal Foords mock module and also rewiring stdout for the duration of a test would mean you could get some more out of this technique too - test the output, exit conditions etc.
The advanced way to run manage command with a flexible arguments and captured output
argv = self.build_argv(short_dict=kwargs)
cmd = self.run_manage_command_raw(YourManageCommandClass, argv=argv)
# Output is saved cmd.stdout.getvalue() / cmd.stderr.getvalue()
Add code to your base Test class
#classmethod
def build_argv(cls, *positional, short_names=None, long_names=None, short_dict=None, **long_dict):
"""
Build argv list which can be provided for manage command "run_from_argv"
1) positional will be passed first as is
2) short_names with be passed after with one dash (-) prefix
3) long_names with be passed after with one tow dashes (--) prefix
4) short_dict with be passed after with one dash (-) prefix key and next item as value
5) long_dict with be passed after with two dashes (--) prefix key and next item as value
"""
argv = [__file__, None] + list(positional)[:]
for name in short_names or []:
argv.append(f'-{name}')
for name in long_names or []:
argv.append(f'--{name}')
for name, value in (short_dict or {}).items():
argv.append(f'-{name}')
argv.append(str(value))
for name, value in long_dict.items():
argv.append(f'--{name}')
argv.append(str(value))
return argv
#classmethod
def run_manage_command_raw(cls, cmd_class, argv):
"""run any manage.py command as python object"""
command = cmd_class(stdout=io.StringIO(), stderr=io.StringIO())
with mock.patch('django.core.management.base.connections.close_all'):
# patch to prevent closing db connecction
command.run_from_argv(argv)
return command