Trying to figure out how to overwrite a variable during unit testing:
#mainscript.py
import json
org_file = 'unitfile.json'
def load_json (file_name):
with open ('{0}'.format(org_file)) as f:
json_object = json.load(f)
return json_object
new_file = load_json(org_file)
def print_me():
return (new_file['text'])
def main():
print_me()
if __name__ == '__main__':
main()
Unit test:
#test_mainscript.py
import json
import mainscript
import unittest
from unittest.mock import patch
class Testmainscript(unittest.TestCase):
# def setUp(self):
# Not needed when using mock
#patch('mainscript.org_file','unitfile2.json')
def test_print_me(self):
self.assertEqual(mainscript.org_file, 'unitfile2.json') # Correctly overwrites file
self.assertEqual(mainscript.print_me(),'Bye World') #does not pass, still reading unitfile.json instead of unitfile2.json
if __name__ == '__main__':
unittest.main()
This test should fail because because I'm overwriting org_file with unitestfile2.json (which contains {'text':'Bye World'}) instead of unittestfile.json (which contains {'text':'Hello World'})
But its currently passing because the variable isn't being overwritten
What you are looking for is called "mocking", an example for your case:
import json
import mainscript
import unittest
from unittest.mock import patch
class Testmainscript(unittest.TestCase):
# def setUp(self):
# Not needed when using mock
#patch('mainscript.org_file', 'unitfile2.json')
def test_print_me(self):
self.assertEqual(mainscript.print_me(),'Hello World')
if __name__ == '__main__':
unittest.main()
I found the issue is that I am overwriting the file as expected but when I call the test I am not using the mocked file. Wrapping the function seemed to work:
#mainscript.py
import json
org_file = 'unitfile.json'
def load_json (json_file):
with open ('{0}'.format(org_file)) as f:
json_object = json.load(f)
return json_object
def print_me(df_file):
return (df_file['text'])
def main():
print_me()
if __name__ == '__main__':
main()
Here I used the wrapped patch as an input
#test_mainscript.py
import json
import mainscript
import unittest
from unittest.mock import patch
class Testmainscript(unittest.TestCase):
#patch('mainscript.org_file','unitfile2.json')
def test_print_me(self):
self.assertEqual(mainscript.org_file, 'unitfile2.json')
self.assertEqual(mainscript.print_me(mainscript.load_json(mainscript.org_file)),'Bye World')
if __name__ == '__main__':
unittest.main()
Test now work:
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Related
I am trying to figure out how to use the #patch.object to mock a __init__ and, log.write() for the Logger class, which is imported but isn't used inside the function of a module. The tuorial such as this, https://www.pythontutorial.net/python-unit-testing/python-patch/
, points to the patching needs to be at the target where it is used, not where it comes from. However, every example used shows the target to be mocked inside another function.
In the use case mentioned below, the logger is imported and used to write the log outside the scope of a function. Is there a way to mock the behavior both in main.py and routers.py?
src/apis/main.py
from utils.log import Logger
from routes import route
log = Logger(name="logger-1")
log.write("logger started")
def main():
log = Logger(name="logger-1")
log.write("inside main")
route()
if __name__ == "__main__":
import logging
logging.basicConfig(level=logging.INFO) # for demo
main()
In src/apis/routers/routes.py
from utils.log import Logger
log = Logger(name="logger-1")
log.write, message=f"Inside route")
def route():
log.write, message=f"Logging done.")
In utils/log/logging.py
import logging
Class Logger:
def __init__(self, name):
# needs to be mocked
def write(self, message):
# needs to be mocked to return None
When asking a question, it is very convenient to offer a Minimal Reproducible Example. So remove the unnecessary fastapi and starlette, and provide the code for the test you are trying to write.
Here it is :
# file: so74695297_main.py
from so74695297_log import Logger
from so74695297_routes import my_route
log = Logger(name="logger-1")
log.write("logger started")
def main(): # FAKE IMPL
log.write(message=f"in main()")
my_route()
if __name__ == "__main__":
import logging
logging.basicConfig(level=logging.INFO) # for demo
main()
# file: so74695297_routes.py
from so74695297_log import Logger
log = Logger(name="logger-1")
def my_route(): # FAKE IMPL
log.write(message=f"route")
# file: so74695297_log.py
import logging
class Logger:
def __init__(self, name):
self._logger = logging.getLogger(name) # FAKE IMPL
def write(self, message):
self._logger.info(message) # FAKE IMPL
when ran (the main.py file does something) :
INFO:logger-1:in main()
INFO:logger-1:route
Which is the expected output when the loggers don't fave any formatter.
Then adding a test :
# file: so74695297_test.py
import unittest
import unittest.mock as mock
from so74695297_routes import my_route
class TestMyRoute(unittest.TestCase):
def test__my_route_write_a_log(self):
spy_logger = mock.Mock()
with mock.patch("so74695297_log.Logger", new=spy_logger):
my_route()
assert spy_logger.assert_called()
if __name__ == "__main__":
unittest.main() # for demo
Ran 1 test in 0.010s
FAILED (failures=1)
Failure
Traceback (most recent call last):
File "/home/stack_overflow/so74695297_test.py", line 12, in test__my_route_write_a_log
assert spy_logger.assert_called()
File "/usr/lib/python3.8/unittest/mock.py", line 882, in assert_called
raise AssertionError(msg)
AssertionError: Expected 'mock' to have been called.
Now we have something to work with !
As #MrBeanBremen indicated, the fact that your logger is configured at import time (even when not being the "main" module) complicates things.
The problem is that, by the time the mock.patch line runs, the modules have already been imported and created their Logger. What we could do instead is mock the Logger.write method :
def test__my_route_writes_a_log(self):
with mock.patch("so74695297_log.Logger.write") as spy__Logger_write:
my_route()
spy__Logger_write.assert_called_once_with(message="route")
Ran 1 test in 0.001s
OK
If you prefer using the decorator form :
#mock.patch("so74695297_log.Logger.write")
def test__my_route_writes_a_log(self, spy__Logger_write):
my_route()
spy__Logger_write.assert_called_once_with(message="route")
Because we mocked the class's method, each Logger instance has a mock version of write :
# vvvv
#mock.patch("so74695297_main.Logger.write")
def test__main_writes_a_log(self, spy__Logger_write):
main()
# assert False, spy__Logger_write.mock_calls
spy__Logger_write.assert_any_call(message="in main()")
In the end, main.Logger.write is essentially the same thing as routes.Logger.write and as log.Logger.write, just a reference to the same "method" object. Mock from one way, mock for all the others too.
I want to run a single test and output the result to a txt file. I understood that I can use loadTestsFromName to specify the test. However, I'm getting an error.
test.py
import unittest
import sys
import os
def main(out=sys.stderr, verbosity=2):
loader = unittest.TestLoader()
suite = loader.loadTestsFromName(sys.modules[__name__]=='test1')
unittest.TextTestRunner(out, verbosity=verbosity).run(suite)
class TestClass(unittest.TestCase):
def test1(self):
self.assertEqual(True, True)
if __name__ == '__main__':
with open('test-results.txt', 'w') as f:
main(f)
I run the test by executing python test.py
I'm not sure how to get test1. I tried sys.modules[__name__]=='test1' but it triggered this error.
parts = name.split('.')
AttributeError: 'bool' object has no attribute 'split'
According to the python doc-unittest.TestLoader.loadTestsFromName, below code is work for me.
import unittest
import sys
import os
def main(out=sys.stderr, verbosity=2):
loader = unittest.TestLoader()
suite = loader.loadTestsFromName('__main__.TestClass.test1')
unittest.TextTestRunner(out, verbosity=verbosity).run(suite)
class TestClass(unittest.TestCase):
def test1(self):
self.assertEqual(True, True)
if __name__ == '__main__':
with open('test-results.txt', 'w') as f:
main(f)
Besides, you'd better separate the TestCase to the single module, then change the __main__ to the module name.
I'm trying to understand the assert_called_with within mock but the code I wrote throws some error.
import os
import twitter
URL = "http://test.com"
def tweet(api, message):
if len(message) > 40:
message = message.strip("?.,.,.")
status = api.PostUpdate(message)
return status
def main():
api = twitter.Api(consumer_key=''
,consumer_secret='')
msg = 'This is test message'
tweet(api, msg)
if __name__ == '__main__':
main()
unittest
import unittest
from mock import Mock
import test
class TweetTest(unittest.TestCase):
def test_example(self):
mock_twitter = Mock()
test.tweet(mock_twitter,'msg')
mock_twitter.PostUpdate.assert_called_with('message')
if __name__ == '__main__':
unittest.main()
I'm trying to understand what assert_called_with does here?
According to the python documentation https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.assert_called_with
'This method is a convenient way of asserting that calls are made in a
particular way'
so it tests whether the parameters are being used in the correct way.
About the errors that you are receiving, I think the parameter you're passing is wrong. Its have to be something like this:
mock_twitter.PostUpdate.assert_called_with(message='msg')
I am trying to code a suite test, I have one module which runs unit test correctly but I intend to add more modules and test them at once, so I have coded the following code:
#main.py
import unittest
from test.Services import TestOS
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTests( TestOS.TestOS() )
unittest.TextTestRunner().run(suite)
TestOS.py
import unittest
from app.Services.OS import OS
class TestOS(unittest.TestCase):
os = OS()
def setUp(self):
pass
def tearDown(self):
pass
def testOSName(self):
self.assertEquals(self.os.getPlatform(), 'Windows')
def testOSVersion(self):
self.assertEquals(self.os.getVersion(), '7')
if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
unittest.main()
After running it, I get this output:
Finding files... done.
Importing test modules ... done.
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
It didn't find any test, What's wrong with my code?
suite.addTest( TestOS.TestOS() ) works only if your testcase contains a runTest() function.
Otherwise you need a "TestLoader" to detect the functions of TestOS that start with "test*".
#main.py
import unittest
from test.Services import TestOS
if __name__ == '__main__':
suite = unittest.TestSuite()
tests = unittest.defaultTestLoader.loadTestsFromTestCase(TestOS)
suite.addTests(tests)
unittest.TextTestRunner().run(suite)
modify your setUp method as follows
def setUp(self):
self.os = OS()
pass
I am trying to log the output of tests to a text file. I am using the unittest module and want to log results into a text file instead of the screen. I have some script here to explain what has been tryied so far. This is the test script.
import unittest, sys
class TestOne(unittest.TestCase):
def setUp(self):
self.var = 'Tuesday'
def tearDown(self):
self.var = None
class BasicTestOne(TestOne):
def runTest(self):
TestOne.setUp(self)
self.assertEqual(self.var, 'Tuesday')
class AbsoluteMoveTestSuite(unittest.TestSuite):
# Tests to be tested by test suite
def makeAbsoluteMoveTestSuite():
suite = unittest.TestSuite()
suite.addTest(TestOne("BasicTestOne"))
return suite
def suite():
return unittest.makeSuite(TestOne)
if __name__ == '__main__':
unittest.main()
I have added this to the file but it doesn't seem to work.
log_file = 'log_file.txt'
sys.stout = sys.sterr = open(log_file, 'w')
return suite >> sys.stout
and also:
log_file = 'log_file.txt'
return suite >> open(log_file, 'w')
I have tried several different versions of this command.
if __name__ == '__main__':
unittest.main() >> open(log_file, 'w')
I have tried this. I want the log file to be stored and created inside the python script. I do not want to have to call python tests.py >> log_file.txt.
Thanks for any help
You can pass the text runner into the main method. The text runner must be set up to write to a file rather than the std.err as it wraps the stream in a decorator. The following worked for me in python 2.6
if __name__ == '__main__':
log_file = 'log_file.txt'
with open(log_file, "w") as f:
runner = unittest.TextTestRunner(f)
unittest.main(testRunner=runner)