I have a function that does some file operations and makes an entry for that IP to /etc/hosts file for DNS resolution
def add_hosts_entry():
ip_addr = "1.2.3.4"
HOST_FILE_PATH = "/etc/hosts"
reg_svc_name = "SVC_NAME"
try:
with open(HOST_FILE_PATH, 'r ') as fp:
lines = fp.readlines()
fp.seek(0)
fp.truncate()
for line in lines:
if not reg_svc_name in line:
fp.write(line)
fp.write(f"{ip_addr}\t{reg_svc_name}\n")
except FileNotFoundError as ex:
LOGGER.error(f"Failed to read file. Details: {repr(ex)}")
sys.exit(1)
LOGGER.info(
f"Successfully made entry in /etc/hosts file:\n{ip_addr}\t{reg_svc_name}"
)
- I want to test that there is indeed an IP entry in the file that I made.
- and that there is only 1 IP address that maps to
reg_svc_name
I found how to mock open()
.
I have this so far but not sure how to check for above two cases:
@pytest.fixture
def mocker_etc_hosts(mocker):
mocked_etc_hosts_data = mocker.mock_open(read_data=etc_hosts_sample_data)
mocker.patch("builtins.open", mocked_etc_hosts_data)
def test_add_hosts_entry(mocker_etc_hosts):
with caplog.at_level(logging.INFO):
registry.add_hosts_entry()
# how to assert??
CodePudding user response:
Solution 1
Don't mock the open
functionality because we want it to actually update a file that we can check. Instead, intercept it and open a test file instead of the actual file used in the source code. Here, we will use tmp_path to create a temporary file to be updated for the test.
src.py
def add_hosts_entry():
ip_addr = "1.2.3.4"
HOST_FILE_PATH = "/etc/hosts"
reg_svc_name = "SVC_NAME"
try:
with open(HOST_FILE_PATH, 'r ') as fp:
lines = fp.readlines()
fp.seek(0)
fp.truncate()
for line in lines:
if not reg_svc_name in line:
fp.write(line)
fp.write(f"{ip_addr}\t{reg_svc_name}\n")
except FileNotFoundError as ex:
print(f"Failed to read file. Details: {repr(ex)}")
else:
print(f"Successfully made entry in /etc/hosts file:\n{ip_addr}\t{reg_svc_name}")
test_src.py
import pytest
from src import add_hosts_entry
@pytest.fixture
def etc_hosts_content_raw():
return "some text\nhere\nSVC_NAME\nand the last!\n"
@pytest.fixture
def etc_hosts_content_updated():
return "some text\nhere\nand the last!\n1.2.3.4\tSVC_NAME\n"
@pytest.fixture
def etc_hosts_file(tmp_path, etc_hosts_content_raw):
file = tmp_path / "dummy_etc_hosts"
file.write_text(etc_hosts_content_raw)
return file
@pytest.fixture
def mocker_etc_hosts(mocker, etc_hosts_file):
real_open = open
def _mock_open(file, *args, **kwargs):
print(f"Intercepted. Would open {etc_hosts_file} instead of {file}")
return real_open(etc_hosts_file, *args, **kwargs)
mocker.patch("builtins.open", side_effect=_mock_open)
def test_add_hosts_entry(
mocker_etc_hosts, etc_hosts_file, etc_hosts_content_raw, etc_hosts_content_updated
):
assert etc_hosts_file.read_text() == etc_hosts_content_raw
add_hosts_entry()
assert etc_hosts_file.read_text() == etc_hosts_content_updated
Output
$ pytest -q -rP
. [100%]
============================================== PASSES ===============================================
_______________________________________ test_add_hosts_entry ________________________________________
--------------------------------------- Captured stdout call ----------------------------------------
Intercepted. Would open /tmp/pytest-of-nponcian/pytest-13/test_add_hosts_entry0/dummy_etc_hosts instead of /etc/hosts
Successfully made entry in /etc/hosts file:
1.2.3.4 SVC_NAME
1 passed in 0.05s
If you're interested, you can display the temporary dummy file too to see the result of the process:
$ cat /tmp/pytest-of-nponcian/pytest-13/test_add_hosts_entry0/dummy_etc_hosts
some text
here
and the last!
1.2.3.4 SVC_NAME
Solution 2
Mock open
as well as the .write
operation. Once mocked, see all the calls to the mocked .write
via call_args_list. This isn't recommended as it would feel like we are writing a change-detector test which is tightly coupled to how the source code was implemented line by line rather than checking the behavior.