'How do you populate package variables when testing a class's method in Pytest?
I'm writing a test suite for a class that needs variables from a util package I have defined:
# util/__init__.py
codes = None
def load_codes():
"""Populates util.codes with the contents of codes.yml"""
with open('codes.yml', 'r') as f:
global codes
codes = yaml.safe_load(f)
The class under test, Filter, is a plugin for a larger app; normally util.load_codes() is called on startup of the app, so when Filter needs to read util.codes, it is guaranteed to be populated. Filter should not be concerned with loading the file into the package var.
For the test itself, I'm loading the class instance as a fixture:
# tests/filter.py
import pytest
from unittest.mock import AsyncMock
from plugins.filter import Filter
@pytest.fixture
def filterplugin():
bot = AsyncMock()
return Filter(bot=bot)
@pytest.mark.asyncio
class TestFilterLogic:
async def test_basic(self, filterplugin):
msg = AsyncMock()
output = await filterplugin.filter_handler(msg)
assert output == True
Filter itself is large and complicated, but a MVP that demonstrates the problem is:
# plugins/filter.py
from util import codes
class Filter():
async def filter_handler(self):
print(codes.keys())
return True
This test fails with an AttributeError when filter_handler calls .keys() on util.codes; it's None.
I attempted to fix this by calling importing util and calling util.load_codes() both in the fixture and in the test itself, to no effect. The test still fails and indicates that the codes var in util is None.
How do I correctly populate this package variable so my class can read it when being unit tested independently of the rest of the larger app?
Solution 1:[1]
when
Filterneeds to readutil.codes
The implementation shown does not read util.codes. It reads a local reference to the value of util.codes at import time, i.e. a local reference to None. That local variable will not be modified by load_codes().
There are a few ways to resolve this.
- Initialize
codesas a dictionary and modify that instance (not reassign) inload_codes().
# util/__init__.py
# codes = None # -
codes = {} # +
def load_codes():
"""Populates util.codes with the contents of codes.yml"""
with open('codes.yml', 'r') as f:
global codes
# codes = yaml.safe_load(f) # -
codes.clear() # +
codes.update(yaml.safe_load(f)) # +
- Import the module and reference the module variable as an attribute of the module.
# plugins/filter.py
# from util import codes # -
import util # +
class Filter():
async def filter_handler(self):
# print(codes.keys()) # -
print(util.codes.keys()) # +
return True
- Import the module variable as a local variable lazily.
# plugins/filter.py
# from util import codes # -
class Filter():
async def filter_handler(self):
from util import codes # +
print(codes.keys())
return True
- Import
Filterlazily, assumingload_codes()is called before the fixture.
# tests/filter.py
import pytest
from unittest.mock import AsyncMock
# from plugins.filter import Filter # -
@pytest.fixture
def filterplugin():
from plugins.filter import Filter # +
bot = AsyncMock()
return Filter(bot=bot)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | aaron |
