I have a class:
class AccountBusiness:
def save(self, account) -> Account:
if not account.account_number_is_valid():
return False
return True
and a test as:
#mock.patch.object(AccountBusiness, 'save')
def test_can_save_valid_account(self, mock_save):
mock_account = mock.create_autospec(Account)
mock_account.account_number_is_valid.return_value = False
account_business = AccountBusiness()
result = account_business.save(mock_account)
self.assertEqual(result.return_value, True)
but it shows an exception like:
AssertionError: <MagicMock name='save()()' id='48830448'> != True
I want to set the return value of account.account_number_is_valid() to False and run the test.
You are using a patch object on the instance method you are looking to test. However, you are looking to test the logic inside the save method. So mocking that out will not test any of the logic inside that method. So, the output you are actually getting here:
AssertionError: <MagicMock name='save()()' id='48830448'> != True
Should be the first hint that something is not right. Your save method is coming back as a MagicMock. You don't want this. What you actually want to do is only mock the Account class, and go accordingly from there. So, your patching here:
#mock.patch.object(AccountBusiness, 'save')
should actually only be:
#mock.patch('path.to.AccountBusiness.Account', return_value=Mock(), autospec=True)
The path.to.AccountBusiness.Account is the location of the Account class with respect to the AccountBusiness class.
So, with that patching, then the return_value of calling Account will now be your mock object that you can use for your account_number_is_valid. So, the code will actually look like this:
class MyTest(unittest.TestCase):
def setUp(self):
self.account_business = AccountBusiness()
#mock.patch('path.to.AccountBusiness.Account', return_value=Mock(), autospec=True)
def test_can_save_valid_account(self, mock_account):
mock_account_obj = mock_account.return_value
mock_account_obj.account_number_is_valid.return_value = False
self.assertFalse(self.account_business.save(mock_account_obj))
Also, pay close attention to the assertion at the end. It was changed to make use of the available assertFalse. Also, look over your own logic, as returning False for account_number_is_valid will actually return False in your save method.
Related
I had created a simple example to illustrate my issue. First is the setup say mydummy.py:
class TstObj:
def __init__(self, name):
self.name = name
def search(self):
return self.name
MyData = {}
MyData["object1"] = TstObj("object1")
MyData["object2"] = TstObj("object2")
MyData["object3"] = TstObj("object3")
def getObject1Data():
return MyData["object1"].search()
def getObject2Data():
return MyData["object2"].search()
def getObject3Data():
return MyData["object3"].search()
def getExample():
res = f"{getObject1Data()}{getObject2Data()}{getObject3Data()}"
return res
Here is the test that failed.
def test_get_dummy1():
dummy.MyData = MagicMock()
mydummy.MyData["object1"].search.side_effect = ["obj1"]
mydummy.MyData["object2"].search.side_effect = ["obj2"]
mydummy.MyData["object3"].search.side_effect = ["obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
The above failed with run time error:
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/mock.py:1078: StopIteration
Here is the test that passed:
def test_get_dummy2():
dummy.MyData = MagicMock()
mydummy.MyData["object1"].search.side_effect = ["obj1", "obj2", "obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
Am I missing something? I would have expected test_get_dummy1() to work and test_get_dummy2() to fail and not vice versa. Where and how can I find/learn more information about mocking to explain what is going on...
MyData["object1"] is converted to this function call: MyData.__getitem__("object1"). When you call your getExample method, the __getitem__ method is called 3 times with 3 parameters ("object1", "object2", "object3").
To mock the behavior you could have written your test like so:
def test_get_dummy_alternative():
mydummy.MyData = MagicMock()
mydummy.MyData.__getitem__.return_value.search.side_effect = ["obj1", "obj2", "obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
Note the small change from your version: mydummy.MyData["object1"]... became: mydummy.MyData.__getitem__.return_value.... This is the regular MagicMock syntax - we want to to change the return value of the __getitem__ method.
BONUS:
I often struggle with mock syntax and understanding what's happening under the hood. This is why I wrote a helper library: the pytest-mock-generator. It can show you the actual calls made to the mock object.
To use it in your case you could have added this "exploration test":
def test_get_dummy_explore(mg):
mydummy.MyData = MagicMock()
mydummy.getExample()
mg.generate_asserts(mydummy.MyData, name='mydummy.MyData')
When you execute this test, the following output is printed to the console, which contains all the asserts to the actual calls to the mock:
from mock import call
mydummy.MyData.__getitem__.assert_has_calls(calls=[call('object1'),call('object2'),call('object3'),])
mydummy.MyData.__getitem__.return_value.search.assert_has_calls(calls=[call(),call(),call(),])
mydummy.MyData.__getitem__.return_value.search.return_value.__str__.assert_has_calls(calls=[call(),call(),call(),])
You can easily derive from here what has to be mocked.
I'm having some issue while creating unittest for internal parameter.
My structure is:
[1] my_animal.py contains Myclass and method: do_bite()
my_animal.py
class Myclass():
def do_bite(self):
return 1
[2] my_module.py contains jobMain("") which is using the method from my_animal.py
my_module.py
import sys
from someclass import Myclass
def jobMain(directoryPath):
flag = -1
result = Myclass()
if result.do_bite() is None:
flag = 0
if result.do_bite() is 1:
flag = 1
if result.do_bite() is 2:
flag = 2
[3] my_test.py contains the unittest to test jobMain in my_module.py
my_test.py
# Mock Myclass.dobite to None
#pytest.fixture
def mock_dobite0():
with mock.patch('my_module.Myclass') as mocked_animal:
mocked_animal.return_value.do_bite.return_value = None
yield
# Mock Myclass.dobite to 1
#pytest.fixture
def mock_dobite1():
with mock.patch('my_module.Myclass') as mocked_animal:
mocked_animal.return_value.do_bite.return_value = 1
yield
# Mock Myclass.dobite to 2
#pytest.fixture
def mock_dobite2():
with mock.patch('my_module.Myclass') as mocked_animal:
mocked_animal.return_value.do_bite.return_value = 2
yield
# My unittest to test dobite() method
def test_dobite0(mock_Myclass, mock_dobite0):
jobMain("")
def test_dobite1(mock_Myclass, mock_dobite1):
jobMain("")
def test_dobite2(mock_Myclass, mock_dobite2):
jobMain("")
My question is: How to test 'flag' parameter inside JobMain?
'flag' para must be assigned the correct value.( eg: dobite = 1 => flag = 1)
The variable para only exists in the scope of jobMain. If you want to use the variable outside jobMain the most common ways are
1) return the value
This is quite obvious. Since jobMain is a function, it returns a value. Without an explicit return statement you return None. You could just
def jobmain(pth):
# do stuff and assign flag
return flag
# and inside tests
assert jobmain("") == 1
2) Use a class instead
If you want the jobMain to remember some state, then it is common practice to use objects. Then flag would be attribute of the object and could be accessed from outside, after you call any method (function) of JobMain. For example
class JobMain:
def __init__(self):
self.flag = -1
def run(self, pth):
result = Myclass()
if result.do_bite() is None:
self.flag = 0
if result.do_bite() is 1:
self.flag = 1
if result.do_bite() is 2:
self.flag = 2
# and inside test
job = JobMain()
job.run()
assert job.flag == 1
Note
I just copy-pasted your code for setting the flag. Note that you call do_bite() many times, if the resulting value is None or 1. Also, when testing against a number, one should use == instead of is.
How to test 'flag' parameter inside JobMain?
You don't. It's an internal variable. Testing it would be glass-box testing; the test will break if the implementation changes.
Instead, test the effect of flag. This is black-box testing. Only the interface is tested. If the implementation changes the test still works allowing the code to be aggressively refactored.
Note: If you don't hard code result = Myclass() you don't need to mock. Pass it in as an argument with the default being Myclass().
def jobMain(directoryPath, result=Myclass()):
Then you don't need to patch Myclass(). Instead, pass in a mock object.
# I don't know unittest.mock very well, but something like this.
mock = Mock(Myclass)
mock.do_bite.return_value = 2
jobMain('', result=mock)
This also makes the code more flexible outside of testing.
I have code that looks like the following:
#patch.object(Path, "__init__", return_value=None)
#patch.object(Path, "exists", return_value=True)
#patch.object(Path, "read_text", return_value="FAKEFIELD: 'FAKEVALUE'")
def test_load_param_from_file_exists(self, *mock_path):
expected_dict = YAML.safe_load("FAKEFIELD: 'FAKEVALUE'")
return_dict = load_parameters("input", None)
self.assertTrue(return_dict["FAKEFIELD"] == expected_dict["FAKEFIELD"])
and deep in the code of load_parameters, the code looks like this:
file_path = Path(parameters_file_path)
if file_path.exists():
file_contents = file_path.read_text()
return YAML.safe_load(file_contents)
Right now, I have to break it up into two tests, because I cannot seem to get a single mock object that allows me to switch between "file exists" and "file doesn't". Ideally, I'd be able to do a single test like this:
#patch.object(Path, "__init__", return_value=None)
#patch.object(Path, "exists", return_value=False)
#patch.object(Path, "read_text", return_value="FAKEFIELD: 'FAKEVALUE'")
def test_load_param_from_file(self, mock_path, *mock_path_other):
with self.assertRaises(ValueError):
load_parameters("input", False)
mock_path.read_text.return_value = "FAKEFIELD: 'FAKEVALUE'"
expected_dict = YAML.safe_load("FAKEFIELD: 'FAKEVALUE'")
return_dict = load_parameters("input", None)
self.assertTrue(return_dict["FAKEFIELD"] == expected_dict["FAKEFIELD"])
To be clear, the above doesn't work because each of those patched objects get instantiated differently, and when the Path object in the load_parameters method gets called, exists is mocked correctly, but read_text returns no value.
What am I doing wrong? Is there a way to patch multiple methods on a single object or class?
I think you are making this more complicated than it needs to be:
def test_load_param_from_file_exists(self):
# Adjust the name as necessary
mock_path = Mock()
mock_path.exists.return_value = True
mock_path.read_text.return_value = '{"FAKEFIELD": "FAKEVALUE"}'
with patch("Path", return_value=mock_path):
return_dict = load_parameters("input", None)
self.assertTrue(return_dict["FAKEFIELD"] == 'FAKEVALUE')
Configure a Mock to behave like you want file_path to behave, then patch Path to return that object when it is called.
(I removed the code involving the environment variable, since it wasn't obvious the value matters when you patch Path.)
I need to check to see if a specific function "oscp.deactivate_user()" is being called in an instance of a method call delete()
Here's what I have so far, the assert fails, when it shouldn't be.
#mock.patch.object(AuthUser, 'delete')
#mock.patch('oscp.deactivate_user')
def test_delete(self, deactivate_user_mock, delete_mock):
"""Test the delete() method in AuthUser"""
authUserObject = mock.Mock()
authUserObject.oscp_id = 4
"""If delete_from_oscp = True && oscp_id isset"""
delete_mock_instance = delete_mock(self, True, authUserObject, oscp_id=4)
delete_mock_instance.delete_mock.assert_called_once_with(self)
i have a new property in my model however I'd like to assign a test value in it for my test script.
this is my code:
models.py
mycode = models.UUIDField(null=True)
#property
def haveCode(self):
if self.mycode == uuid.UUID('{00000000-0000-0000-0000-000000000000}'):
return False
else
return True
and this is the test script that i am working on. I wanted to have a test value for haveCode:
test = Test()
test.mycode = uuid.UUID('{00000000-0000-0000-0000-000000000000}')
test.save()
checkTest = Test()
#this is only to pass the test
#delete this when start coding
checkTest.haveCode = True
assertEqual(test.haveCode, True)
however I got an error in checkTest.haveCode = True since this is just a property and not an attribute.
how to assign True to it? I appreciate your help
You can 'mock' that property using the mock library
from mock import patch, PropertyMock
#patch.object(Test, 'haveCode', new_callable=PropertyMock)
def myTest(test_haveCode_mock):
test_haveCode_mock.return_value = True
checkTest = Test()
assertEqual(checkTest.haveCode, True)
patch.stopall() # when you want to release all mocks