Let's say I have the following python files:
# source.py
def get_one():
return 1
# file1.py
from source import get_one
def func1():
return get_one()
# file2.py
from source import get_one
def func2():
return get_one()
# script.py
from file1 import func1
from file2 import func2
def main(a, b):
count = 0
for _ in range(a):
count += func1()
for _ in range(b):
count += func2()
return count
I know I can mock out get_one() in main.py using the following setup:
def test_mock():
with (
patch("file1.get_one") as mock1,
patch("file2.get_one") as mock2,
):
main(2, 3)
assert mock1.call_count + mock2.call_count == 5
However, this gets increasingly verbose and hard to read if get_one() needs to be mocked out in many files. I would love to be able to mock out all of its locations in a single mock variable. Something like:
# this test fails, I'm just showing what this ideally would look like
def test_mock():
with patch("file1.get_one", "file2.get_one") as mock:
main(2, 3)
assert mock.call_count == 5
Is there anyway to do this or do I need to use multiple mocks?
Note, I know I can't mock where the function is defined, e.g. patch("source.get_one").
patch accepts new as the object to patch the target with:
def test_mock():
with (
patch("file1.get_one") as mock,
patch("file2.get_one", new=mock),
):
main(2, 3)
assert mock.call_count == 5, mock.call_count
You can write a helper context manager:
import contextlib
from unittest.mock import DEFAULT, patch
#contextlib.contextmanager
def patch_same(target, *targets, new=DEFAULT):
with patch(target, new=new) as mock:
if targets:
with patch_same(*targets, new=mock):
yield mock
else:
yield mock
Usage:
def test_mock():
with patch_same("file1.get_one", "file2.get_one") as mock:
main(2, 3)
assert mock.call_count == 5
A possible workaround: add a level of indirection in source.py so that get_one has a dependency that you can patch:
def get_one():
return _get_one()
def _get_one():
return 1
and now you can do this in your test:
from script import main
from unittest.mock import patch
def test_mock():
with patch("source._get_one") as mock1:
main(2, 3)
assert mock1.call_count == 5
Related
I'm using python unittest for functions that write data to JSON. I use tearDownClass to delete the output test files so they don't clutter the local repo. Ground truths are also stored as JSON files.
I do want to store the output test files when tests fail, so its easier for troubleshooting.
My current implementation is to use a global boolean keep_file = False. When the unittest fails the assertion, it modifies keep_file = True. tearDownClass only deletes the files when keep_file == False. I don't like the idea of modifying global variables and the try exception blocks for each assert.
import json
import os
import unittest
from src.mymodule import foo1, foo2
# These are defined outside the class on purpose so the classmethods can access them
FILE_1 = "unittest.file1.json"
EXPECTED_FILE_1 = "expected.file1.json"
FILE_2 = "unittest.file2.json"
EXPECTED_FILE_2 = "expected.file2.json"
keep_files = False
class TestRhaPostPayload(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.get_file1()
cls.get_file2()
#classmethod
def get_file1(cls):
output1 = foo1()
with open(FILE_1, "w") as f:
f.write(output1)
#classmethod
def get_file2(cls):
output2 = foo1()
with open(FILE_2, "w") as f:
f.write(output2)
#classmethod
def tearDownClass(cls):
if not keep_files:
os.remove(FILE_1)
os.remove(FILE_2)
def test_foo1(self):
# code that reads in file1 and expected_file_1
try:
self.assert(expected_output1, output1)
except AssertionError:
global keep_files
keep_files = True
raise
def test_foo2(self):
# code that reads in file2 and expected_file_2
try:
self.assert(expected_output2, output2)
except AssertionError:
global keep_files
keep_files = True
raise
You could simply check, if there were any errors/failures in your test case during tear-down and only delete the files, if there were none.
How to perform this check was explained in this post.
This check is done on a TestCase instance so tearDownClass won't work. But you are using different files in different tests anyway, so you might as well use normal setUp/tearDown to remove the current file.
Here is a working example:
from pathlib import Path
from typing import Optional
from unittest import TestCase
class Test(TestCase):
def all_tests_passed(self) -> bool:
"""Returns `True` if no errors/failures occurred at the time of calling."""
outcome = getattr(self, "_outcome")
if hasattr(outcome, "errors"): # Python <=3.10
result = self.defaultTestResult()
getattr(self, "_feedErrorsToResult")(result, outcome.errors)
else: # Python >=3.11
result = outcome.result
return all(test != self for test, _ in result.errors + result.failures)
def setUp(self) -> None:
super().setUp()
self.test_file: Optional[Path] = None
def tearDown(self) -> None:
super().tearDown()
if self.test_file and self.all_tests_passed():
self.test_file.unlink()
def test_foo(self) -> None:
self.test_file = Path("foo.txt")
self.test_file.touch()
self.assertTrue(True)
def test_bar(self) -> None:
self.test_file = Path("bar.txt")
self.test_file.touch()
self.assertTrue(False)
Running this test case leaves bar.txt in the current working directory, whereas foo.txt is gone.
I will start off by admitting that I have just got into python mock or any mock for that matter for a day or two.
So I have a python file that has a class in it:
MyFileA.py
class A:
def Afunc():
#do smthn
Now I want to mock a different script that uses this MyFileA.py
MyFileB.py
from MyFileA import A
def Bfunc():
Aobj = A()
ReturnVal = Aobj.Afunc()
How do I mock the statement Aobj.Afunc() to return a specific value?
Also, I am exclusively using decorators for mock method so I am expecting solutions in that format only.
You can use mock.patch to mock class A and Afunc and its returned value.
E.g.
MyFileA.py:
class A:
def Afunc():
print('do smthn')
MyFileB.py:
from MyFileA import A
def Bfunc():
Aobj = A()
ReturnVal = Aobj.Afunc()
return ReturnVal
test_MyFileB.py:
import unittest
from MyFileB import Bfunc
from unittest import mock
class TestMyFileB(unittest.TestCase):
#mock.patch('MyFileB.A')
def test_Bfunc(self, mock_A):
a_instance = mock_A.return_value
a_instance.Afunc.return_value = "mocked value"
actual = Bfunc()
self.assertEqual(actual, 'mocked value')
mock_A.assert_called_once()
a_instance.Afunc.assert_called_once()
if __name__ == '__main__':
unittest.main()
unit test result with coverage report:
.
----------------------------------------------------------------------
Ran 1 test in 0.002s
OK
Name Stmts Miss Cover Missing
--------------------------------------------------------------------------
src/stackoverflow/63429593/MyFileA.py 3 1 67% 3
src/stackoverflow/63429593/MyFileB.py 5 0 100%
src/stackoverflow/63429593/test_MyFileB.py 13 0 100%
--------------------------------------------------------------------------
TOTAL 21 1 95%
I have the following code:
my_module.py
def my_func(number):
def nested_func(value):
'''
Doing some calculation
'''
return result
output = []
for i in range(number):
res = nested_func(i)
output.append(res)
return output
I'm using pytest with pytest-mock and mocker as a fixture in test_my_module.py
test_my_module.py
def test_my_module(mocker):
expected_res = [1, 1, 1]
mocker.patch('nested_func', return_value=1)
from my_module import my_func
assert my_func(3) == expected_res
But when I run in py.test I got an error:
TypeError: Need a valid target to patch. You supplied: 'nested_func'
Is there any way to mocker.patch functions\methods, which are not visible in testing module and located as nested in that functions, I want to test?
As a follow up to #MrBean Bremen, I think a work around is to define nested_func outside of my_func and call it within my_func
def nested_func(value):
result = value + 2
return result
def my_func(number):
output = []
for i in range(number):
res = nested_func(i)
output.append(res)
return output
Your test_my_module
from my_module import my_func
def test_my_module(mocker):
expected_res = [1, 1, 1]
mocker.patch('my_module.nested_func', return_value=1)
assert my_func(3) == expected_res
I'm new to mock and really strugling with it. Mostly in documentation and most of SO pages it shows how to get the mock result_value, but I want to check if the values I'm getting from methods are correct, not the result_value's. Here's the example:
#!/usr/bin/env python
class Example:
def one(self):
return 1
def two(self, one):
print(one + 1) # basically any void method, ex: result = one + 1 and check result value if correct
def main(self):
self.two(self.one())
if __name__ == '__main__':
e = Example()
e.main()
Test:
#!/usr/bin/env python
import unittest
import example
from mock import patch
class Example(unittest.TestCase):
def test_one(self):
self.assertEqual(1, et.one())
def test_two(self):
with patch('example.Example.two'):
self.assertEqual(2, et.two(et.one())) # ..the part I'm stuck
# whick ofc throws AssertionError: 2 != <MagicMock name='two()' id='blablabla'>
def test_main(self):
# unknown part..
if __name__ == '__main__':
et = example.Example()
unittest.main()
How to achieve void method check with unittest?
UPDATE:
so the print I figured out with chepner's help:
def test_twoi3(self):
mock_print = MagicMock()
with patch('sys.stdout', mock_print):
print(2)
expected = call.write('2')
self.assertEqual(mock_print.mock_calls[0], expected)
and for main I'm not quite sure if it is a good solution...:
def test_main(self):
with patch ('example.Example.main') as m:
et.main(et.two(1))
m.assert_called_with(et.two(1))
but I want to check not by passing the methods and values, but if main calls other two methods. How to achieve this?
You don't need to mock anything (directly, anyway); you want to capture standard output and verify that it is what you expect.
from contextlib import redirect_stdout
from io import StringIO
def test_two(self):
stdout = StringIO()
with redirect_stdout(stdout):
et.two(et.one())
self.assertEqual(stdout.getvalue(), "2\n")
Or, you can mock the print and check that it is called with the expected arguments.
def test_two(self):
with patch('__builtin__.print') as p:
et.two(et.one())
p.assert_called_with(2)
I have figured out how to test if main methods have been called. Test looks like this:
#!/usr/bin/env python
import unittest
import example
from mock import patch, MagicMock, call
class Example(unittest.TestCase):
def setUp(self):
self.et = example.Example()
def test_one(self):
self.assertEqual(1, self.et.one())
def test_two(self):
mock_print = MagicMock()
with patch('sys.stdout', mock_print):
print(2)
expected = call.write('2')
self.assertEqual(mock_print.mock_calls[0], expected)
def test_main(self):
self.et.two = MagicMock(side_effect=self.et.two)
self.et.one = MagicMock(side_effect=self.et.one)
self.et.main()
self.et.one.assert_called()
self.et.two.assert_called()
self.et.one.__str__ = self.et.one
self.assertEqual(1, int(self.et.one))
if __name__ == '__main__':
unittest.main()
Upon mocking methods that are in main (all of them) and calling main methods one and two have been successfully called. For one you can return value by using __str__
py.test documentations says that I should add capsys parameter to my test methods but in my case this doesn't seem to be possible.
class testAll(unittest.TestCase):
def setUp(self):
self.cwd = os.path.abspath(os.path.split(inspect.getfile(inspect.currentframe()))[0])
os.chdir(self.cwd)
def execute(self, cmd, result=0):
"""
Helper method used by many other tests, that would prevent replicating too much code.
"""
# cmd = "%s > /dev/null 2>&1" % cmd
ret = os.system(cmd) >> 8
self.assertEqual(ret, result, "`%s` returned %s instead of %s (cws=%s)\n\t%s" % (cmd, ret, result, os.getcwd(), OUTPUT)) ### << how to access the output from here
def test_1(self):
self.execute("do someting", 0)
You could define a helper function in the class that inherits the capsys fixture:
#pytest.fixture(autouse=True)
def capsys(self, capsys):
self.capsys = capsys
Then call this function inside the test:
out,err = self.capsys.readouterr()
assert out == 'foobar'
Kudos to MichaĆ Krassowski for his workaround which helped me work through a similar problem.
https://github.com/pytest-dev/pytest/issues/2504#issuecomment-309475790
Thomas Wright's answer is perfect. I'm just sticking this code block here for my own reference as my search led me here and I'll likely forget this in future! [doing a few things in this so useful reference for me]. If anyone is looking and sees where it can be improved - suggest away!
import os
import pytest
from _pytest.monkeypatch import MonkeyPatch
from unittest import TestCase
# -----------------------------------------------------------------------------
def foo_under_test(inp1):
"""Example of a Method under test"""
do_some_calcs_here = inp1*2
get_a_return = ClassCalled.foo_called(do_some_calcs_here)
return get_a_return
# -----------------------------------------------------------------------------
class ClassUnderTest():
"""Example of a Class contained Method under test"""
def __init__(self):
"""Instantiate the class"""
self.var1 = "TestVar"
def foo_under_test2(self, inp11):
"""The class method under test"""
return self.var1 + self.foo_called2(inp11)
def foo_called2(self, inp12):
"""Nominal sub-foo to foo_under_test2"""
return str(inp12*5)
# -----------------------------------------------------------------------------
class ClassCalled:
"""Example of a class that could be called by foo_under_test"""
def foo_called(inp2):
"""Sub-foo to foo_under_test"""
return inp2 * 2
# -----------------------------------------------------------------------------
class MockResponses:
"""Class for holding the mock responses"""
def foo_called(inp2):
"""**Mock of foo_called**"""
return inp2*3
def foo_called2(inp12):
"""**Mock of foo_called**"""
return str(inp12*4)
# -----------------------------------------------------------------------------
class Test_foo_under_test(TestCase):
"""Test class - means of grouping up tests for a target function
This one is addressing the individual function (not within a class)
"""
# ---------------------------------------------------------------
#pytest.fixture(autouse=True)
def capsys(self, capsys):
"""Capsys hook into this class"""
self.capsys = capsys
def print_to_console(self, strOut):
"""Print strOut to console (even within a pyTest execution)"""
with self.capsys.disabled():
print(strOut)
def setUp(self):
"""Ran by pyTest before running any test_*() functions"""
self.monkeypatch = MonkeyPatch()
# ---------------------------------------------------------------
def test_1(self):
"""**Test case**"""
def mock_foo_called(inp2):
return MockResponses.foo_called(inp2)
mockedFoo = ClassCalled.foo_called # Need to get this handle here
self.monkeypatch.setattr(ClassCalled, "foo_called", mock_foo_called)
x = foo_under_test(1)
self.print_to_console("\n")
strOut = "Rtn from foo: " + str(x)
self.print_to_console(strOut)
assert x == 6
# Manually clear the monkey patch
self.monkeypatch.setattr(
ClassCalled, "foo_called", mockedFoo)
"""I've noticed with me having monkeypatch inside the
class, the damn thing persists across functions.
This is the only workaround I've found so far"""
# -----------------------------------------------------------------------------
class Test_ClassUnderTest_foo_under_test(TestCase):
"""Test class - means of grouping up tests for a target function
This one is addressing the function within a class
"""
# ---------------------------------------------------------------
#pytest.fixture(autouse=True)
def capsys(self, capsys):
"""Capsys hook into this class"""
self.capsys = capsys
def print_to_console(self, strOut):
"""Print strOut to console (even within a pyTest execution)"""
with self.capsys.disabled():
print(strOut)
def setUp(self):
"""Ran by pyTest before running any test_*() functions"""
self.monkeypatch = MonkeyPatch()
# ---------------------------------------------------------------
def test_1(self):
"""**Test case**"""
def mock_foo_called2(self, inp2):
"""
Mock function
Defining a mock function, note this can be dealt with directly
here, or if its more comprehensible, put it in a separate class
(i.e. MockResponses)
"""
# return MockResponses.foo_called2(inp2) # Delegated approach
return str(inp2*4) # Direct approach
"""Note that the existence of self within this test class forces
a wrapper around calling a MockClass - so we have to go through
both the line below and the function mock_foo_called2() above to
properly invoke MockResponses.foo_called2()
"""
mockedFoo = ClassUnderTest.foo_called2
self.monkeypatch.setattr(
ClassUnderTest, "foo_called2", mock_foo_called2)
x = ClassUnderTest().foo_under_test2(1)
strOut = "Rtn from foo: " + str(x)
self.print_to_console("\n")
self.print_to_console(strOut)
assert x == "TestVar" + str(4)
self.monkeypatch.setattr(
ClassUnderTest, "foo_called2", mockedFoo)
# -----------------------------------------------------------------------------
# ---- Main
if __name__ == "__main__":
#
# Setup for pytest
outFileName = os.path.basename(__file__)[:-3] # Remove the .py from end
currScript = os.path.basename(__file__)
# -------------------------------------------------------------------------
# PyTest execution
pytest.main([currScript, "--html", outFileName + "_report.html"])
rtnA = foo_under_test(1)
print(rtnA == 4)
# This should output 4, demonstrating effect of stub (which produced 6)
rtnB = ClassUnderTest().foo_under_test2(1)
print(rtnB == "TestVar"+str(5))
# This should output "TestVar5", demonstrating effect of stub
# conftest.py
class TTY:
def communicate(self):
with self.trace():
print('wow!')
#pytest.fixture(autouse=True)
def set_capsys(capsys):
TTY.trace = capsys.disabled
#pytest.fixture
def get_tty():
_get_tty():
return TTY()
return _get_tty
# test_wow.py
def test_wow(get_tty):
get_tty().communicate()