Source code for bio2bel.testing

# -*- coding: utf-8 -*-

"""Testing utilities for Bio2BEL.

This module has tools for quickly writing unit tests with :mod:`unittest` that involve the usage of a mock data with
a Bio2BEL manager.
"""

import logging
import os
import tempfile
import unittest
from typing import Type
from unittest import mock

from .exc import Bio2BELManagerTypeError, Bio2BELTestMissingManagerError
from .manager.abstract_manager import AbstractManager

__all__ = [
    'TemporaryConnectionMethodMixin',
    'TemporaryConnectionMixin',
    'MockConnectionMixin',
    'AbstractTemporaryCacheMethodMixin',
    'AbstractTemporaryCacheClassMixin',
    'make_temporary_cache_class_mixin',
]

log = logging.getLogger(__name__)


[docs]class TemporaryConnectionMethodMixin(unittest.TestCase): """Creates a :class:`unittest.TestCase` that has a persistent file for use with SQLite during testing."""
[docs] def setUp(self): """Create a temporary file to use as a persistent database throughout tests in this class.""" super().setUp() self.fd, self.path = tempfile.mkstemp() self.connection = 'sqlite:///' + self.path log.info('test database at %s', self.connection)
[docs] def tearDown(self): """Close the connection to the database and removes the files created for it.""" os.close(self.fd) os.remove(self.path)
[docs]class TemporaryConnectionMixin(unittest.TestCase): """Creates a :class:`unittest.TestCase` that has a persistent file for use with SQLite during testing.""" fd, path = None, None connection = None
[docs] @classmethod def setUpClass(cls): """Create a temporary file to use as a persistent database throughout tests in this class. Subclasses of :class:`TemporaryCacheClsMixin` can extend :func:`TemporaryCacheClsMixin.setUpClass` to populate the database. """ super().setUpClass() cls.fd, cls.path = tempfile.mkstemp() cls.connection = 'sqlite:///' + cls.path log.info('test database at %s', cls.connection)
[docs] @classmethod def tearDownClass(cls): """Close the connection to the database and removes the files created for it.""" os.close(cls.fd) os.remove(cls.path)
[docs]class MockConnectionMixin(TemporaryConnectionMixin): """Allows for testing with a consistent connection without changing the configuration."""
[docs] def setUp(self): """Set up the test with a mock connection string. Add two class-level variables: ``mock_global_connection`` and ``mock_module_connection`` that can be used as context managers to mock the bio2bel connection getter functions. """ super().setUp() def mock_connection() -> str: """Get the connection enclosed by this class.""" return self.connection self.mock_global_connection = mock.patch('bio2bel.models.get_global_connection', mock_connection) self.mock_module_connection = mock.patch('bio2bel.utils.get_connection', mock_connection)
[docs]class AbstractTemporaryCacheMethodMixin(TemporaryConnectionMethodMixin): """Allows for testing with a consistent connection and creation of a manager class wrapping that connection. Requires the class variable ``Manager`` to be overriden with the class corresponding to the manager to be used that is a subclass of :class:`bio2bel.AbstractManager`. """ Manager: Type[AbstractManager] = ... manager: Manager
[docs] def setUp(self): """Set up the class with the given manager and allows an optional populate hook to be overridden.""" if self.Manager is ...: raise Bio2BELTestMissingManagerError('Must override class variable "Manager" with subclass of ' 'bio2bel.AbstractManager') if not issubclass(self.Manager, AbstractManager): raise Bio2BELManagerTypeError('Manager must be a subclass of bio2bel.AbstractManager') super().setUp() self.manager = self.Manager(connection=self.connection) self.populate()
[docs] def tearDown(self): """Close the connection in the manager and deletes the temporary database.""" self.manager.session.close() super().tearDown()
[docs] def populate(self) -> None: """Populate the database. This stub should be overridden. """
[docs]class AbstractTemporaryCacheClassMixin(TemporaryConnectionMixin): """Allows for testing with a consistent connection and creation of a manager class wrapping that connection. Requires the class variable ``Manager`` to be overriden with the class corresponding to the manager to be used that is a subclass of :class:`bio2bel.AbstractManager`. """ Manager: Type[AbstractManager] = ... manager: Manager
[docs] @classmethod def setUpClass(cls): """Set up the class with the given manager and allows an optional populate hook to be overridden.""" if cls.Manager is ...: raise Bio2BELTestMissingManagerError('Must override class variable "Manager" with subclass of ' 'bio2bel.AbstractManager') if not issubclass(cls.Manager, AbstractManager): raise Bio2BELManagerTypeError('Manager must be a subclass of bio2bel.AbstractManager') super().setUpClass() cls.manager = cls.Manager(connection=cls.connection) cls.populate()
[docs] @classmethod def tearDownClass(cls): """Close the connection in the manager and deletes the temporary database.""" cls.manager.session.close() super().tearDownClass()
[docs] @classmethod def populate(cls): """Populate the database. This stub should be overridden. """
[docs]def make_temporary_cache_class_mixin(manager_cls: Type[AbstractManager]) -> Type[ AbstractTemporaryCacheClassMixin]: # noqa: D202 """Build a testing class that has a Bio2BEL manager instance ready to go.""" class TemporaryCacheClassMixin(AbstractTemporaryCacheClassMixin): Manager = manager_cls return TemporaryCacheClassMixin