How do I mock an IMAP server in Python, despite extreme laziness?
Asked Answered
S

4

13

I'm curious to know if there is an easy way to mock an IMAP server (a la the imaplib module) in Python, without doing a lot of work.

Is there a pre-existing solution? Ideally I could connect to the existing IMAP server, do a dump, and have the mock server run off the real mailbox/email structure.

Some background into the laziness: I have a nasty feeling that this small script I'm writing will grow over time and would like to create a proper testing environment, but given that it might not grow over time, I don't want to do much work to get the mock server running.

Stereotypy answered 9/12, 2008 at 2:57 Comment(0)
E
10

I found it quite easy to write an IMAP server in twisted last time I tried. It comes with support for writing IMAP servers and you have a huge amount of flexibility.

Elanaeland answered 9/12, 2008 at 3:14 Comment(1)
Any chance you could provide an example?Aguilera
S
8

As I didn't find something convenient in python 3 for my needs (mail part of twisted is not running in python 3), I did a small mock with asyncio that you can improve if you'd like :

I defined an ImapProtocol which extends asyncio.Protocol. Then launch a server like this :

factory = loop.create_server(lambda: ImapProtocol(mailbox_map), 'localhost', 1143)
server = loop.run_until_complete(factory)

The mailbox_map is a map of map : email -> map of mailboxes -> set of messages. So all the messages/mailboxes are in memory.

Each time a client connects, a new instance of ImapProtocol is created. Then, the ImapProtocol executes and answers for each client, implementing capability/login/fetch/select/search/store :

class ImapHandler(object):
    def __init__(self, mailbox_map):
        self.mailbox_map = mailbox_map
        self.user_login = None
        # ...

    def connection_made(self, transport):
        self.transport = transport
        transport.write('* OK IMAP4rev1 MockIMAP Server ready\r\n'.encode())

    def data_received(self, data):
        command_array = data.decode().rstrip().split()
        tag = command_array[0]
        self.by_uid = False
        self.exec_command(tag, command_array[1:])

    def connection_lost(self, error):
        if error:
            log.error(error)
        else:
            log.debug('closing')
            self.transport.close()
        super().connection_lost(error)

    def exec_command(self, tag, command_array):
        command = command_array[0].lower()
        if not hasattr(self, command):
            return self.error(tag, 'Command "%s" not implemented' % command)
        getattr(self, command)(tag, *command_array[1:])

    def capability(self, tag, *args):
        # code for it...
    def login(self, tag, *args):
        # code for it...

Then in my tests, I start the server during setup with :

self.loop = asyncio.get_event_loop()
self.server = self.loop.run_until_complete(self.loop.create_server(create_imap_protocol, 'localhost', 12345))

When I want to simulate a new message :

imap_receive(Mail(to='[email protected]', mail_from='[email protected]', subject='hello'))

And stop it at teardown :

self.server.close()
asyncio.wait_for(self.server.wait_closed(), 1)

cf https://github.com/bamthomas/aioimaplib/blob/master/aioimaplib/tests/imapserver.py


EDIT: I had a buggy stop of the server, I rewrote it with asyncio.Protocol and modify the answer to reflect the changes

Seale answered 18/2, 2016 at 17:48 Comment(3)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From ReviewVivisection
But was it necessary to down vote this seriously ? You know I don't care, I'm not the kind of guy rushing after SO points (did you see my score ?) but trying to help people when I encoutered a problem that made me loose my time. I was not obliged to push my code on github and give this anwer. This kind of action is seriously pushing me stop using SO. My 2 centsSeale
Ok it was someone else, sorrySeale
C
7

How much of it do you really need for any one test? If you start to build something on the order of complexity of a real server so that you can use it on all your tests, you've already gone wrong. Just mock the bits any one tests needs.

Don't bother trying so hard to share a mock implementation. They're not supposed to be assets, but discardable bits-n-pieces.

Contrary answered 9/12, 2008 at 15:41 Comment(0)
K
1

I never tried but, if I had to, I would start with the existing SMTP server.

Koralle answered 9/12, 2008 at 9:12 Comment(1)
Link above is dead, new one is likely docs.python.org/2/library/smtpd.htmlReece

© 2022 - 2024 — McMap. All rights reserved.