I am trying to do a unit-test but don't quite get why these 2 dicts show up as not equal. I was wondering if someone could give me an explanation for this occurrence. My code is...
import unittest
class TestEmailValidator(unittest.TestCase):
def test(self):
known_dict = {
'debo#foobar.com': True,
'debo#gmail.com': False
}
result_dict = {}
for key in known_dict.keys():
result_dict[key] = is_email_valid(key)
# debugger results
# result_dict = {
# 'debo#foobar.com': True,
# 'debo#gmail.com': False
# }
if self.assertEqual(known_dict, result_dict):
print "is_email_valid passed"
else:
print "is_email_valid failed"
if __name__ == '__main__':
unittest.main()
I get the same result for assertEqual, assertEquals and assertDictEquals. I have tried assigning result_dict to known_dict before the test, but that did not pass either.
It would be great if someone could point me to why this could be happening.
You are misusing the assert. All the assertXYZ methods don't return a boolean value, they just raise an exception if the assertion fails. As these methods don't return anything, they implicitly return None. When evaluating None as a boolean it's treated as false, and hence your test prints is_email_valid failed, even though the test actually passes.
Related
I want to be able to mock a function that mutates an argument, and that it's mutation is relevant in order for the code to continue executing correctly.
Consider the following code:
def mutate_my_dict(mutable_dict):
if os.path.exists("a.txt"):
mutable_dict["new_key"] = "new_value"
return True
def function_under_test():
my_dict = {"key": "value"}
if mutate_my_dict(my_dict):
return my_dict["new_key"]
return "No Key"
def test_function_under_test():
with patch("stack_over_flow.mutate_my_dict") as mutate_my_dict_mock:
mutate_my_dict_mock.return_value = True
result = function_under_test()
assert result == "new_value"
**Please understand i know i can just mock os.path.exists in this case but this is just an example. I intentionally want to mock the function and not the external module.
**
I also read the docs here:
https://docs.python.org/3/library/unittest.mock-examples.html#coping-with-mutable-arguments
But it doesn't seem to fit in my case.
This is the test i've written so far, but it obviously doesn't work since the key changes:
def test_function_under_test():
with patch("stack_over_flow.mutate_my_dict") as mutate_my_dict_mock:
mutate_my_dict_mock.return_value = True
result = function_under_test()
assert result == "new_value"
Thanks in advance for all of your time :)
With the help of Peter i managed to come up with this final test:
def mock_mutate_my_dict(my_dict):
my_dict["new_key"] = "new_value"
return True
def test_function_under_test():
with patch("stack_over_flow.mutate_my_dict") as mutate_my_dict_mock:
mutate_my_dict_mock.side_effect = mock_mutate_my_dict
result = function_under_test()
assert result == "new_value"
How it works is that with a side effect you can run a function instead of the intended function.
In this function you need to both change all of the mutating arguments and return the value returned.
I am working with AWS Athena to get results. I have to initiate a query and then check to see if its completed.
I am now trying to write a unit test for the various states. Here is a sample code. I generate the athena connection from another function and hand it off to this function, as well as the execution ID.
def check_athena_status(athena, execution):
running = True
print('Checking Athena Execution Running State')
while running:
running_state = athena.get_query_execution(QueryExecutionId=execution)['QueryExecution']['Status']['State']
if running_state == 'SUCCEEDED':
print('Run SUCCEEDED')
running = False
elif running_state == 'RUNNING':
time.sleep(3)
print('Athena Query Still Running')
else:
raise RuntimeError('Athena Query Failed')
return True
I am basically trying to figure out is there a way where I can change the value of running_state from RUNNING to SUCCEEDED. I currently use this as the unit test for a successful run.
athena_succeed = mock.Mock()
execution_id = 'RandomID'
athena_succeed.get_query_execution.return_value = test_data.athena_succeeded
result = inventory_validator.check_athena_status(athena_succeed, execution_id)
assert result == True
where test_data.athena_succeeded is basically a dict
athena_succeed = {'QueryExecution': {
'Status': {'State': 'SUCCEEDED',
'SubmissionDateTime': '2021-08-08'}
}
}
I also have a "RUNNING" one.
athena_running = {'QueryExecution': {
'Status': {'State': 'RUNNING',
'SubmissionDateTime': '2021-08-08'}
}
}
I am trying to test branches so I want to go from running to succeed. I know I can change the while true value, but I want to change the actual "athena response" in the middle of the loop. I tried with PropertyMock but I am not sure thats the right use case.
This will fully test all branches of the code:
import pytest
from your_module import check_athena_status
def test_check_athena_status(mocker, capsys):
mock_sleep = mocker.patch("your_module.time.sleep")
mock_athena = mocker.Mock()
mock_athena.get_query_execution.side_effect = [
{"QueryExecution": {"Status": {"State": "RUNNING"}}},
{"QueryExecution": {"Status": {"State": "SUCCEEDED"}}},
]
result = check_athena_status(mock_athena, execution="RandomID")
assert result is True
mock_sleep.assert_called_once_with(3)
mock_athena.get_query_execution.assert_has_calls([
mocker.call(QueryExecutionId="RandomID"),
mocker.call(QueryExecutionId="RandomID"),
])
out, err = capsys.readouterr()
assert err == ""
assert out.splitlines() == [
"Checking Athena Execution Running State",
"Athena Query Still Running",
"Run SUCCEEDED",
]
def test_check_athena_status_error(mocker):
mock_athena = mocker.Mock()
other = {"QueryExecution": {"Status": {"State": "OTHER"}}}
mock_athena.get_query_execution.return_value = other
with pytest.raises(RuntimeError, match="^Athena Query Failed$"):
check_athena_status(mock_athena, execution="RandomID")
A couple of points to note:
time.sleep is mocked out so that the test runs immediately rather than taking 3 seconds, yet we assert that there was a delay intended between consecutive calls to the query.
the expected QueryExecutionId argument was asserted, and we check that it was called twice.
the status output text from the print statements is asserted.
The fixture mocker is provided by the plugin pytest-mock.
Use side_effect to change the return value on successive calls.
class TestCheckAthenaStatus(unittest.TestCase):
def test_check_athena_status_from_running_to_succeeded(self):
athena_running_succeeded = mock.Mock()
execution_id = 'RandomID'
athena_running_succeeded.get_query_execution.side_effect = (test_data.athena_running, test_data.athena_succeeded)
result = inventory_validator.check_athena_status(athena_running_succeeded, execution_id)
assert result == True
assert athena_running_succeeded.get_query_execution.call_count == 2
I have following python function in 'au.py' :
import os
def resolv_conf_audit():
ALT_PATH = "/etc/monitor/etc/resolv.conf.{}".format(os.uname()[1])
RES_PATH = "/data/bin/resolvconf"
if os.path.isfile(RES_PATH):
return "PASSED", "/data/bin/resolvconf is present"
elif os.path.isfile(ALT_PATH):
return "PASSED", "/etc/monitor/etc/resolv.conf. is present"
else:
return "FAILED"
I need to write a unit test with mock which can check the path exists or not
following is the unit test which I wrote
from au import resolv_conf_audit
import unittest
from unittest.mock import patch
class TestResolvConf(unittest.TestCase):
#patch('os.path.isfile.ALT_PATH')
def test_both_source_files_not(self, mock_os_is_file):
mock_os_is_file.return_value = False
assert resolv_conf_audit() == "FAILED"
but I am getting following error
AttributeError: <function isfile at 0x10bdea6a8> does not have the attribute 'ALT_PATH'
How do I mock to check the presence of ALT_PATH and RES_PATH so that I can validate the function. In future this unit test should have the capability to mock removal some files, before writing that I am testing this simple one
Thanks # Mauro Baraldi, as per your suggestion, I changed the code little bit and it works fine now
def test_both_source_files_not(self, mock_os_is_file):
mock_os_is_file.side_effect = [False , False]
assert resolv_conf_audit() == "FAILED"
Mocks by definition is a way to simulate beahvior of objects. You are trying to handle a variable (ALT_PATH) inside your function.
All you need is to mock just the os.path.isfile method.
class TestResolvConf(unittest.TestCase):
#patch('os.path.isfile')
def test_both_source_files_not(self, mock_os_is_file):
mock_os_is_file.return_value = False
assert resolv_conf_audit() == "FAILED"
#patch('os.path.isfile')
def test_both_source_files_exists(self, mock_os_is_file):
mock_os_is_file.return_value = True
assert resolv_conf_audit() == "PASSED"
Is there pytest functionality similar to pytest.raises that passes iff the block raises the specified exception, or doesn't raise at all? Something like:
def test_encode_err(ulist):
with pytest.maybe_raises_but_only(UnicodeEncodeError): # <== ?
assert encode_list(ulist, 'ascii') == map(lambda x:x.encode('ascii'), ulist)
This question came up in the following situation..
The function to test:
def encode_list(lst, enc):
"Encode all unicode values in ``lst`` using ``enc``."
return [(x.encode(enc) if isinstance(x, unicode) else x) for x in lst]
A couple of simple tests (fixtures below):
def test_encode_err(ulist):
with pytest.raises(UnicodeEncodeError):
assert encode_list(ulist, 'ascii')
def test_encode_u8(ulist, u8list):
assert encode_list(ulist, 'u8') == u8list
The fixtures:
#pytest.fixture(
scope='module',
params=[
u'blåbærsyltetøy',
u'', # <==== problem
]
)
def ustr(request):
print 'testing with:', `request.param`
return request.param
#pytest.fixture
def u8str(ustr):
return ustr.encode('u8')
#pytest.fixture
def ulist(ustr):
return [ustr, ustr]
#pytest.fixture
def u8list(u8str):
return [u8str, u8str]
the indicated <== problem is only a problem for test_encode_err() (and not test_encode_u8()), and happens since u''.encode('ascii') doesn't raise a UnicodeEncodeError (no unicode strings that doesn't contain characters above code point 127 will raise).
Is there a py.test function that covers this use case?
If you don't care when the exception is thrown just write the code as normal but put a try...except block round it to ignore the error.
def test_encode_err(ulist):
try:
assert encode_list(ulist, 'ascii') == map(lambda x:x.encode('ascii'), ulist)
except UnicodeDecodeError:
pass
Really though consider whether you should be writing a test at all if you don't know whether the code will throw an exception. Try pinning down the data a bit more and having two tests, one which raises the exception and one which doesn't.
I consider the provided response really incomplete. I like to parametrize tests for functions that could accepts different values.
Consider the following function that only accepts empty strings, in which case returns True. If you pass other type raises a TypeError and if the passed string is not empty a ValueError.
def my_func_that_only_accepts_empty_strings(value):
if isinstance(value, str):
if value:
raise ValueError(value)
return True
raise TypeError(value)
You can conveniently write parametric tests for all cases in a single test in different ways:
import contextlib
import pytest
parametrization = pytest.mark.parametrize(
('value', 'expected_result'),
(
('foo', ValueError),
('', True),
(1, TypeError),
(True, TypeError),
)
)
#parametrization
def test_branching(value, expected_result):
if hasattr(expected_result, '__traceback__'):
with pytest.raises(expected_result):
my_func_that_only_accepts_empty_strings(value)
else:
assert my_func_that_only_accepts_empty_strings(
value,
) == expected_result
#parametrization
def test_without_branching(value, expected_result):
ctx = (
pytest.raises if hasattr(expected_result, '__traceback__')
else contextlib.nullcontext
)
with ctx(expected_result):
assert my_func_that_only_accepts_empty_strings(
value,
) == expected_result
Note that when an exception raises inside pytest.raises context, the contexts exits so the later assert ... == expected_result is not executed when the exception is catch. If other exception raises, it is propagated to your test so the comparison is not executed either. This allows you to write more assertions after the execution of the function for successfull calls.
But this can be improved in a convenient maybe_raises fixture, that is what you're looking for at first:
#contextlib.contextmanager
def _maybe_raises(maybe_exception_class, *args, **kwargs):
if hasattr(maybe_exception_class, '__traceback__'):
with pytest.raises(maybe_exception_class, *args, **kwargs):
yield
else:
yield
#pytest.fixture()
def maybe_raises():
return _maybe_raises
And the test can be rewritten as:
#parametrization
def test_with_fixture(value, expected_result, maybe_raises):
with maybe_raises(expected_result):
assert my_func_that_only_accepts_empty_strings(
value,
) == expected_result
Really nice, right? Of course you need to know how the magic works to write the test properly, always knowing that the context will exits when the exception is catched.
I think that pytest does not includes this because could be a really confusing pattern that could lead to unexpected false negatives and bad tests writing. Rather than that, pytest documentation encourauges you to pass expectation contexts as parameters but for me this solution looks really ugly.
EDIT: just packaged this fixture, see pytest-maybe-raises.
I'm currently trying to run a number of tests against a JSON string there are however a few difficulties that I am encountering.
Here's what I have so far.
class PinpyTests(jsonstr, campaign):
data = json.loads(jsonstr)
test = False
def dwellTest(self):
if self.data.get('dwellTime', None) is not None:
if self.data.get('dwellTime') >= self.campaign.needed_dwellTime:
# Result matches, dwell time test passed.
self.test = True
def proximityTest(self):
if self.data.get('proximity', None) is not None:
if self.data.get('proximity') == self.campaign.needed_proximity:
# Result matches, proximity passed.
self.test = True
Basically, I need the tests to be run, only if they exist in the json string. so if proximity is present in the string, it will run the proximity test, etc etc. (there could be more tests, not just these two)
The issue seems to arise when both tests are present, and need to both return true. If they both return true then the test has passed and the class can return true, However, if dwell fails, and proximity passes I still need it to fail because not all the tests pass. (where proximity makes it pass). I'm slightly baffled as how to continue.
For starters, your class is defined incorrectly. What you probably want is an __init__ function. To achieve your desired result, I would suggest adding a testAll method that checks for each test in your json then runs that test.
class PinpyTests(Object):
test = False
def __init__(self, jsonstr, campaign):
self.data = json.loads(jsonstr)
self.campaign = campaign
def testAll(self):
passed = True
if self.data.get('dwellTime') is not None:
passed = passed and self.dwellTest()
if self.data.get('proximity') is not None:
passed = passed and self.proximityTest()
return passed
def dwellTest(self):
if self.data.get('dwellTime') >= self.campaign.needed_dwellTime:
# Result matches, dwell time test passed.
return True
return False
def proximityTest(self):
if self.data.get('proximity') == self.campaign.needed_proximity:
# Result matches, proximity passed.
return True
return False