Résolue

Test avec pytest

# Tests unitaires

Bonjour j'ai de grosse difficulté pour écrire un test en python avec pytest.

Voila la méthode que je tente de tester :

def _set_mailbox_acl(self, dn: str, uid: str, user: str, acl: str):
        try:
            mb: Mailbox = Mailbox(name=uid, configuration=self._configuration)
            new_uid = self._build_share_uid(uid_accessing=user, uid_shared=uid)
            mb.update_acl(user=new_uid, acl=self._mce_acl_imap[acl])
            _, msg = self.log_msg.set_mailbox_acl
            self.logger.debug(
                msg,
                acl,
                dn,
                user,
                uid,
            )
        except CYRUSError as err:
            self._handle_cyrus_error(dn=dn, err=err)

Voila le test que j'ai écris :

def test_set_mailbox_acl(mock_treatments, mailbox_mock):
    # Set up test data
    dn = "test_dn"
    uid = "test_uid"
    user = "test_user"
    acl = "read"

    # Mock the necessary objects and methods
    mock_treatments.mce_acl_imap = {
        "read": "r",
        "write": "w",
    }
    mock_treatments._set_mailbox_acl.mb = MagicMock()
    mock_treatments.log_msg.set_mailbox_acl = (0, "msg")
    new_uid = "mocker_uid"
    mock_treatments._build_share_uid.return_value = new_uid

    with patch("mce_sync.core.mailbox.Mailbox", return_value=mailbox_mock):
        Treatments._set_mailbox_acl(mock_treatments, dn, uid, user, acl)

    mailbox_mock.update_acl.assert_called_once_with(
        user=new_uid,
        acl=mock_treatments.mce_acl_imap,
    )

    Treatments.logger.debug.assert_called_once_with(
        "msg", 
        acl,
        dn,
        user,
        uid,
    )

Voila l'erreur que j'ai :

mock_treatments = <mock id="140520282092304" spec="Treatments">, mailbox_mock = <mock id="140520285233552">

    def test_set_mailbox_acl(mock_treatments, mailbox_mock):
        # Set up test data
        dn = "test_dn"
        uid = "test_uid"
        user = "test_user"
        acl = "read"

        # Mock the necessary objects and methods
        mock_treatments.mce_acl_imap = {
            "read": "r",
            "write": "w",
        }
        mock_treatments._set_mailbox_acl.mb = MagicMock()
        mock_treatments.log_msg.set_mailbox_acl = (0, "msg")
        new_uid = "mocker_uid"
        mock_treatments._build_share_uid.return_value = new_uid

        # Utilisez patch pour remplacer la vraie classe Mailbox par le mock
        with patch("mce_sync.core.mailbox.Mailbox", return_value=mailbox_mock):
            # Appelez la méthode que vous voulez tester
>           Treatments._set_mailbox_acl(mock_treatments, dn, uid, user, acl)

tests/treatments/unit/test_treatments.py:517: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
mce_sync/core/treatments.py:593: in _set_mailbox_acl
    mb: Mailbox = Mailbox(name=uid, configuration=self._configuration)
mce_sync/core/mailbox/__init__.py:76: in __init__
    self._imap = Cyrus(
mce_sync/core/cyrus/__init__.py:108: in __init__
    self._session = CYRUS(url=self._url_server)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <cyruslibmce.src.cyrus.cyrus 0x7fcd6d7ca070="" at="" object="">, url = <mock id="140520281907504" name="mock._configuration.config.cyrus.uri">

    def __init__(self, url="imap://localhost:143"):
        self.VERBOSE = False
        self.AUTH = False
        self.ADMIN = None
        self.AUSER = None
        self.ADMINACL = "c"
        self.SEP = DEFAULT_SEP
        self.ENCODING = "imap"
        self.NORMALIZE = False
        self.LOGFD = stdout
>       match = re_url.match(url)
E       TypeError: expected string or bytes-like object

Je pense que le problème est que je n'arrive pas à Mock correctement la classe Mailbox. Je souhaiterai dans mon test mock la classe Mailbox pour que celle ci ne soit pas appeler par le test et ne tente pas de faire créer une classe CYRUS. Je veux juste vérifier que cette classe est appelé une fois. Je n'essaie pas pour le moment de tester si la Mailbox est bien créé.

Voila le code de la Mailbox au cas où :

class Mailbox:
    def __init__(
        self,
        name: str,
        configuration: ConfigLoader,
        as_username: Optional[str] = None,
        url_cyrus: Optional[str] = None,
    ) -> None:
        self._configuration = configuration
        self._url_cyrus = url_cyrus
        self._as_username = as_username
        self._imap = Cyrus(
            configuration=self._configuration,
            as_username=self._as_username,
            url_cyrus=self._url_cyrus,
        )
        self._name = name
        self._path = self._set_path()
        self.logger = logging.getLogger("mce_sync.Mailbox")
        self.log_msg = MailboxLogMsg()

    def _set_path(self) -> str:
        """
        Définie le chemin d'accès de la boite courante

        Returns:
            path
        """
        path_root = self._configuration.config.mailbox.path_root
        path_prefix = self._configuration.config.mailbox.path_prefix
        path_suffix = self._configuration.config.mailbox.path_suffix
        path = f"{path_root}" f"/{path_prefix}{self._name}{path_suffix}"
        return path

    def update_acl(self, user: str, acl: Optional[str]) -> None:
        """
        Modifie les droits d'accès à un utilisateur sur la boite mail courante

        Notes:
            Si 'acl' est 'None' ou une chaine vide, les droits d'accès
            sont retirés

        Args:
            user: str: utilisateur cible
            acl: Optional[str]: nouveau droits d'accès

        Returns:
            None

        Raises:
            CYRUSError: En cas d'échec de transaction
        """
        if acl is None or acl == "":
            _, msg_success = self.log_msg.delete_acl_target
            self.logger.debug(
                msg_success,
                self._path,
            )
            _, msg_success = self.log_msg.delete_acl
            self.logger.debug(msg_success, user)
            self._imap.remove_acl(mailbox=self._path, userid=user)
        else:
            self._imap.set_acl(mailbox=self._path, userid=user, acl=acl)
            inbox_folders: List[
                str
            ] = self._configuration.config.mailbox.inbox_folders
            for mailbox in inbox_folders:
                self._imap.set_acl(
                    mailbox=f"{self._path}/{mailbox}", userid=user, acl=acl
                )

Merci d'avance pour votre aide !

Thibault houdon

Mentor

Salut Flavien !

C'est toujours un peu difficile de déboguer des tests qui ne fonctionnent pas mais on va essayer ;)
Le problème est effectivement sur la génération d'un objet Cyrus car j'imagine qu'il n'a pas toutes les infos nécessaires pour le créer correctement et de toute façon tu ne veux pas créer un objet Cyrus qui semble nécessaire pour ton Mailbox si l'objectif est de faire un mock.

Du coup pour éviter ce problème il me semble qu'il faudrait que l'instance de Mailbox qui est retournée par ton mock ait déjà une propriété _imap configurée (pour éviter qu'elle ne tente d'instancier un objet CYRUS).

Tu peux rajouter un MagickMock comme ça :

mailbox_mock._imap = MagicMock()  # On s'assure que _imap est déjà mocké

Essai déjà avec ça de voir si ça règle au moins ce problème spécifique :)

Bonjour Thibault, merci pour ta réponse !

Effectivement cela à réglé ce problème.

Mais finalement du à la dtructure particulière de cette app qui fait appel à des librairies externes qui n'ont pas de test, pour faire le travail de test unitaire correctement il faudrait écrire des tests pour les libs externes également. Mais par manque on a décidé d'abandonner les tests unitaires pour des tests end to end.

Inscris-toi

(c'est gratuit !)

Inscris-toi

Tu dois créer un compte pour participer aux discussions.

Créer un compte

Rechercher sur le site

Formulaire de contact

Inscris-toi à Docstring

Pour commencer ton apprentissage.

Tu as déjà un compte ? Connecte-toi.