I have the below context manager inside a class method, which I would like to mock for unit testing.
def load_yaml_config(self) -> dict:
"""
Load the config based on the arguments provided.
Returns: dict
dictionary which will be used for configuring the logger, handlers, etc.
"""
with open(self.yaml_path, 'r') as config_yaml:
return yaml.safe_load(config_yaml.read())
How could I achieve it?
EDIT: this is what I've tried so far, but it throws a warning because of "Shadows built-in name 'open'"
from unittest.mock import Mock, MagicMock
open = MagicMock()
yaml = Mock()
yaml.safe_load.return_value = 'My hardcoded value'
yaml_path = 'foo'
def mocked_yaml():
with open(yaml_path, 'r') as config_yaml:
return yaml.safe_load(config_yaml.read())
print(mocked_yaml())
CodePudding user response:
The easy way
Just create the appropriate yaml file in your testing code. But you probably don't want that, since you're making this post.
A hack with mocking
You can override open
with your mock in the module scope:
# test_YourClass.py
builtin_open = open
class open:
def __init__(self, *args, **kwargs):
pass
def __enter__(self):
pass
def __exit__(self, exc_type, exc_value, exc_traceback):
pass
def read(self):
return 'hardcoded file contents for testing'
# Test here
open = builtin_open
This code is just a general idea, I haven't run it. It might require some additional work, such as parameterizing the mock file contents.
Dependency injection
The "proper" way is to unhardcode open()
call in the class and inject your context manager, I suppose. It's up to you. I personally don't like injecting everything just for the purpose of unit testing.
CodePudding user response:
If you wanted to refactor this code to be able to test the safe_load
part without having to actually open
a file or patch builtins.open
, you could do:
def load_yaml_config(self) -> dict:
"""
Load the config based on the arguments provided.
Returns: dict
dictionary which will be used for configuring the logger, handlers, etc.
"""
with open(self.yaml_path, 'r') as config_yaml:
return self._load_yaml_config(config_yaml.read())
def _load_yaml_config(self, yaml_text: str) -> dict:
return yaml.safe_load(yaml_text)
and then in your test:
TEST_YAML_DATA = """
stuff:
other_stuff
"""
def test_load_yaml_config():
assert WhateverMyClassIs()._load_yaml_config(TEST_YAML_DATA) == {
'stuff': 'other_stuff'
}
Modify to use actual appropriate YAML formatting and the correct expected dict output.
Note that all this is really testing is yaml.safe_load
(which should have its own unit tests already) and the fact that your code calls it. Practically speaking I probably wouldn't bother covering this function in a unit test at all, but would instead try to have some sort of larger integration test (using a real file) that involved loading a config as part of some larger test scenario.