Setting read_data after calling mock.mock_open() - python-3.x

I am testing a class that in turn uses open. I want to patch open on the test class for simplicity, but set read_data per test. So something like this:
#mock.patch('builtins.open', unittest.mock.mock_open())
class test_TheClassUnderTest:
def test_MakeSureAMethodWorks(self):
open().read_data = "custom data for the test"
I confirmed that tests work when I set read_data in the patch decorator:
#mock.patch('builtins.open', unittest.mock.mock_open(read_data="acid burn and zero cooooooool"))
class test_TheClassUnderTest(unittest.TestCase):
def test_MakeSureAMethodWorks(self):
# ...
self.assertEquals(object.a_method(), expected_value)
I've tried setting the return values of the mock returned from open():
open().read.return_value = "custom data for the test"
open().readline.return_value = "custom data for the test"
but it's not working. Is there a way to set the value of read_data after the mock_open() constructor has been called or, more generally, another way to do this?

You don't need to mock the .return_value for open().read. Use mock_open(read_data="fake data") is enough.
E.g.
main.py:
def main(file_path):
lines = []
with open(file_path) as f:
for line in f:
lines.append(line)
return lines
test_main.py:
import unittest
from unittest.mock import mock_open, patch
from main import main
class TestMain(unittest.TestCase):
def test_main(self):
m = mock_open(read_data="custom data for the test\nsecond line")
with patch('builtins.open', m) as mocked_open:
actual = main('fake/file/path')
self.assertEqual(actual, ['custom data for the test\n', 'second line'])
mocked_open.assert_called_once_with('fake/file/path')
if __name__ == '__main__':
unittest.main()
test result:
⚡ coverage run /Users/dulin/workspace/github.com/mrdulin/python-codelab/src/stackoverflow/65533628/test_main.py && coverage report -m --include='./src/**'
.
----------------------------------------------------------------------
Ran 1 test in 0.010s
OK
Name Stmts Miss Cover Missing
-----------------------------------------------------------------------
src/stackoverflow/65533628/main.py 6 0 100%
src/stackoverflow/65533628/test_main.py 12 0 100%
-----------------------------------------------------------------------
TOTAL 18 0 100%

Related

Unit testing in Python: Nesting mocked objects

I am new to unit testing in Python. I am trying to unit test a method that does something like this:
MyClass:
client : Client
def __init__(client):
self.client=client
def method_to_test(self):
images = self.client.get_stuff()
image = images[0]
result = []
for region in image.regions:
result.append(region.region_id)
return result
I have tried mocking and nesting of all the classes involved: Client, Image and ImageRegion. But at some point the mocking breaks down and my test fails. Is there a way to mock things so that one can do something like:
def test_method_to_test():
result = MyClass(mock_client).method_to_test()
assert dummy_region_id in result
... or perhaps better ways to test in such situations with nested custom objects?
My latest attempt looks like this:
with patch('package.module.ImageRegion') as MockRegion:
region = MockRegion()
region.region_id.return_value = dummy_region_id
with patch('package.module.Image') as MockImage:
image = MockImage()
image.regions.return_value = [region]
with patch('package.module.Client') as MockClient:
mock_client = MockClient()
mock_client.get_regions.return_value = [image]
result = MyClass(mock_client).method_to_test()
assert dummy_region_id in result
You could create mocked object using unittest.mock and use Dependency Injection to pass the mocked object to MyClass.
E.g.
myclass.py:
class MyClass:
def __init__(self, client):
self.client = client
def method_to_test(self):
images = self.client.get_stuff()
image = images[0]
result = []
for region in image.regions:
result.append(region.region_id)
return result
test_myclass.py:
import unittest
from unittest.mock import Mock
from collections import namedtuple
from myclass_63948475 import MyClass
Image = namedtuple('Image', ['regions'])
Region = namedtuple('Region', 'region_id')
class TestMyClass(unittest.TestCase):
def test_method_to_test(self):
regions = [Region('1'), Region('2'), Region('3')]
images = [Image(regions=regions)]
mock_client = Mock()
mock_client.get_stuff.return_value = images
myclass = MyClass(mock_client)
actual = myclass.method_to_test()
mock_client.get_stuff.assert_called_once()
self.assertEqual(actual, ['1', '2', '3'])
if __name__ == '__main__':
unittest.main()
unit test result with coverage report:
coverage run /Users/ldu020/workspace/github.com/mrdulin/python-codelab/src/stackoverflow/63948475/test_myclass_63948475.py && coverage report -m
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Name Stmts Miss Cover Missing
-----------------------------------------------------------------------------------
src/stackoverflow/63948475/myclass_63948475.py 10 0 100%
src/stackoverflow/63948475/test_myclass_63948475.py 18 0 100%
-----------------------------------------------------------------------------------
TOTAL 28 0 100%

how to write a unittest for a class?

class Hand:
def __init__(self,cards, total, soft_ace_count):
self.cards = cards
self.total = total
self.soft_ace_count = soft_ace_count
def __str__(self):
return(str(self.cards)+','+str(self.total)+','+str(self.soft_ace_count))
def add_card(self):
self.cards.append(get_card())
self.score()
def is_blackjack(self):
return len(self.cards)==2 and self.total==21
def is_bust(self):
return self.total > 21
def score(self):
self.total=0
self.soft_ace_count=0
for x in self.cards:
if x > 10:
x=10
self.total+=x
#the following code will decide 1 =11 or 1 = 1
if self.total <= 11 and 1 in self.cards:
self.total+=10
self.soft_ace_count+=1
I am trying to write the unittest for this 'Hand' class. Do I need a init setup for this particular unittest?
Here is part of code of my unittest. Thank you so much if anyone could help me with this. I just started in python. Any suggestions would help. Thank you. Neglect the indent
class hand(unittest.TestCase):
def __init__(self):
self.cards=cards
self.total = total
self.soft_ace_count = soft_ace_count
Assuming that you have your class in a module named hand.py, you can place your unittest script in the same directory. It could look like this:
import unittest
from hand import Hand
class hand(unittest.TestCase):
def test_init(self):
cards = []
h = Hand(cards, 0, 0)
self.assertEqual(h.total, 0)
self.assertEqual(h.soft_ace_count, 0)
self.assertEqual(len(h.cards), 0)
# def test_blackjack(self):
#....
# def test_score(self):
#....
# def test_OTHERTHINGS(self):
#.....
if __name__ == '__main__':
unittest.main()
if you name your unittestscript for example unittesthand.py, you can run it via "python unittestscript.py". You will get the following result:
> .
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
The unittest component has executed all methods starting with "test" and executed. In the example the "test_init" method. This is NOT the initialization of the test, but the unittest for the initialization of class Hand.
Now change for example the code to "self.assertEqual(h.total, 1)", and you will see that the unittest component reports an error.
Now you can create additional cases for the other methods.You can also create a base test case with code that is always executed before and after the unittests and so on...

Mocking Module methods

I have a situation which i could not find on stack-overflow:
some_module.py:
class SomeModule():
def a(): pass
def b(): pass
def c(): pass
#classmethod
def bring_them_altogether(cls):
cls.a()
cls.b()
cls.c()
I am writing a unittest and would like to test the method bring_them_altogether() by determining that methods a(), b() & c() are all called once.
In my testcase i have this:
test.py:
#patch('<...>.some_module.SomeModule', autospec=True)
def test_bring_them_altogether(self, mock_class):
mock_class.bring_them_altogether()
self.assertTrue(mock_class.called) # Returns me False
I want to know how to write a proper test case for bring_them_altogether() as i seem to be having some issues getting the output i want.
On that note, I want to know how to determine that a mocked-method has been called X - number of times.
Thank you for pointing me in the right direction
You should use patch.object to patch a, b, c methods of SomeModule class. Check if they are called or not after executing the bring_them_altogether method.
E.g.
some_module.py:
class SomeModule():
def a(): pass
def b(): pass
def c(): pass
#classmethod
def bring_them_altogether(cls):
cls.a()
cls.b()
cls.c()
test_some_module.py:
import unittest
from unittest.mock import patch
from some_module import SomeModule
class TestSomeModule(unittest.TestCase):
#patch.object(SomeModule, 'c')
#patch.object(SomeModule, 'b')
#patch.object(SomeModule, 'a')
def test_bring_them_altogether(self, mock_a, mock_b, mock_c):
SomeModule.bring_them_altogether()
mock_a.assert_called_once()
mock_b.assert_called_once()
mock_c.assert_called_once()
if __name__ == '__main__':
unittest.main()
unit test result with coverage report:
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Name Stmts Miss Cover Missing
------------------------------------------------------------------------------
src/stackoverflow/58950420/some_module.py 8 0 100%
src/stackoverflow/58950420/test_some_module.py 13 0 100%
------------------------------------------------------------------------------
TOTAL 21 0 100%

How can I redirect hardcoded calls to open to custom files?

I've written some python code that needs to read a config file at /etc/myapp/config.conf . I want to write a unit test for what happens if that file isn't there, or contains bad values, the usual stuff. Lets say it looks like this...
""" myapp.py
"""
def readconf()
""" Returns string of values read from file
"""
s = ''
with open('/etc/myapp/config.conf', 'r') as f:
s = f.read()
return s
And then I have other code that parses s for its values.
Can I, through some magic Python functionality, make any calls that readconf makes to open redirect to custom locations that I set as part of my test environment?
Example would be:
main.py
def _open_file(path):
with open(path, 'r') as f:
return f.read()
def foo():
return _open_file("/sys/conf")
test.py
from unittest.mock import patch
from main import foo
def test_when_file_not_found():
with patch('main._open_file') as mopen_file:
# Setup mock to raise the error u want
mopen_file.side_effect = FileNotFoundError()
# Run actual function
result = foo()
# Assert if result is expected
assert result == "Sorry, missing file"
Instead of hard-coding the config file, you can externalize it or parameterize it. There are 2 ways to do it:
Environment variables: Use a $CONFIG environment variable that contains the location of the config file. You can run the test with an environment variable that can be set using os.environ['CONFIG'].
CLI params: Initialize the module with commandline params. For tests, you can set sys.argv and let the config property be set by that.
In order to mock just calls to open in your function, while not replacing the call with a helper function, as in Nf4r's answer, you can use a custom patch context manager:
from contextlib import contextmanager
from types import CodeType
#contextmanager
def patch_call(func, call, replacement):
fn_code = func.__code__
try:
func.__code__ = CodeType(
fn_code.co_argcount,
fn_code.co_kwonlyargcount,
fn_code.co_nlocals,
fn_code.co_stacksize,
fn_code.co_flags,
fn_code.co_code,
fn_code.co_consts,
tuple(
replacement if call == name else name
for name in fn_code.co_names
),
fn_code.co_varnames,
fn_code.co_filename,
fn_code.co_name,
fn_code.co_firstlineno,
fn_code.co_lnotab,
fn_code.co_freevars,
fn_code.co_cellvars,
)
yield
finally:
func.__code__ = fn_code
Now you can patch your function:
def patched_open(*args):
raise FileNotFoundError
with patch_call(readconf, "open", "patched_open"):
...
You can use mock to patch a module's instance of the 'open' built-in to redirect to a custom function.
""" myapp.py
"""
def readconf():
s = ''
with open('./config.conf', 'r') as f:
s = f.read()
return s
""" test_myapp.py
"""
import unittest
from unittest import mock
import myapp
def my_open(path, mode):
return open('asdf', mode)
class TestSystem(unittest.TestCase):
#mock.patch('myapp.open', my_open)
def test_config_not_found(self):
try:
result = myapp.readconf()
assert(False)
except FileNotFoundError as e:
assert(True)
if __name__ == '__main__':
unittest.main()
You could also do it with a lambda like this, if you wanted to avoid declaring another function.
#mock.patch('myapp.open', lambda path, mode: open('asdf', mode))
def test_config_not_found(self):
...

How to mock readlines() in Python unit tests

I am trying to write a unit test to a class init that reads from a file using readlines:
class Foo:
def __init__(self, filename):
with open(filename, "r") as fp:
self.data = fp.readlines()
with sanity checks etc. included.
Now I am trying to create a mock object that would allow me to test what happens here.
I try something like this:
TEST_DATA = "foo\nbar\nxyzzy\n"
with patch("my.data.class.open", mock_open(read_data=TEST_DATA), create=True)
f = Foo("somefilename")
self.assertEqual(.....)
The problem is, when I peek into f.data, there is only one element:
["foo\nbar\nxyzzy\n"]
Which means whatever happened, did not get split into lines but was treated as one line. How do I force linefeeds to happen in the mock data?
This will not work with a class name
with patch("mymodule.class_name.open",
But this will work by mocking the builtin directly, builtins.open for python3
#mock.patch("__builtin__.open", new_callable=mock.mock_open, read_data=TEST_DATA)
def test_open3(self, mock_open):
...
or this without class by mocking the module method
def test_open(self):
with patch("mymodule.open", mock.mock_open(read_data=TEST_DATA), create=True):
...
#Gang's answer pointed me to the right direction but it's not a complete working solution. I have added few details here which makes it a working code without any tinkering.
# file_read.py
def read_from_file():
# Do other things here
filename = "file_with_data"
with open(filename, "r") as f:
l = f.readline()
return l
# test_file_read.py
from file_read import read_from_file
from unittest import mock
import builtins
##mock.patch.object(builtins, "open", new_callable=mock.mock_open, read_data="blah")
def test_file_read(mock_file_open):
output = read_from_file()
expected_output = "blah"
assert output == expected_output

Resources