Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing and mocking email sender in Python with Google AppEngine

I'm a newbie to python and the app engine.

I have this code that sends an email based on request params after some auth logic. in my Unit tests (i'm using GAEUnit), how do I confirm an email with specific contents were sent? - i.e. how do I mock the emailer with a fake emailer to verify send was called?

class EmailHandler(webapp.RequestHandler):
 def bad_input(self):
  self.response.set_status(400)
  self.response.headers['Content-Type'] = 'text/plain'
  self.response.out.write("<html><body>bad input </body></html>")

 def get(self):
  to_addr = self.request.get("to")
  subj = self.request.get("subject")
  msg = self.request.get("body")
  if not mail.is_email_valid(to_addr):
    # Return an error message...
    #   self.bad_input()
    pass

  # authenticate here

  message = mail.EmailMessage()
  message.sender = "[email protected]"
  message.to = to_addr
  message.subject = subj
  message.body = msg
  message.send()
  self.response.headers['Content-Type'] = 'text/plain'
  self.response.out.write("<html><body>success!</body></html>")

And the unit tests,

import unittest
from webtest import TestApp
from google.appengine.ext import webapp
from email import EmailHandler

class SendingEmails(unittest.TestCase):

  def setUp(self):
    self.application = webapp.WSGIApplication([('/', EmailHandler)], debug=True)

  def test_success(self):
    app = TestApp(self.application)
    response = app.get('http://localhost:8080/[email protected]&body=blah_blah_blah&subject=mySubject')
    self.assertEqual('200 OK', response.status)
    self.assertTrue('success' in response)
    # somehow, assert email was sent 
like image 808
CVertex Avatar asked Jan 09 '09 08:01

CVertex


3 Answers

You could also override the _GenerateLog method in the mail_stub inside AppEngine.

Here is a parent TestCase class that I use as a mixin when testing that e-mails are sent:

from google.appengine.api import apiproxy_stub_map, mail_stub

__all__ = ['MailTestCase']

class MailTestCase(object):
    def setUp(self):
        super(MailTestCase, self).setUp()
        self.set_mail_stub()
        self.clear_sent_messages()

    def set_mail_stub(self):
        test_case = self
        class MailStub(mail_stub.MailServiceStub):
            def _GenerateLog(self, method, message, log, *args, **kwargs):
                test_case._sent_messages.append(message)
                return super(MailStub, self)._GenerateLog(method, message, log, *args, **kwargs)

        if 'mail' in apiproxy_stub_map.apiproxy._APIProxyStubMap__stub_map:
            del apiproxy_stub_map.apiproxy._APIProxyStubMap__stub_map['mail']

        apiproxy_stub_map.apiproxy.RegisterStub('mail', MailStub())

    def clear_sent_messages(self):
        self._sent_messages = []

    def get_sent_messages(self):
        return self._sent_messages

    def assertEmailSent(self, to=None, sender=None, subject=None, body=None):
        for message in self.get_sent_messages():
            if to and to not in message.to_list(): continue
            if sender and sender != message.sender(): continue
            if subject and subject != message.subject(): continue
            if body and body not in message.textbody(): continue
            return

        failure_message = "Expected e-mail message sent."

        args = []
        if to: args.append('To: %s' % to)
        if sender: args.append('From: %s' % sender)
        if subject: args.append('Subject: %s' % subject)
        if body: args.append('Body (contains): %s' % body)

        if args:
            failure_message += ' Arguments expected: ' + ', '.join(args)

        self.fail(failure_message)

After that, a sample test case might look like:

import unittest, MailTestCase

class MyTestCase(unittest.TestCase, MailTestCase):
    def test_email_sent(self):
        send_email_to('[email protected]') # Some method that would send an e-mail.
        self.assertEmailSent(to='[email protected]')
        self.assertEqual(len(self.get_sent_messages()), 1)
like image 158
JJ Geewax Avatar answered Nov 14 '22 23:11

JJ Geewax


A very short introduction provides PyPI: MiniMock 1.0. It's a very small library to establish mocks.

  1. Inject your mock into the module, that should be mocked
  2. Define, what your mock will return
  3. Call the method
  4. Your mock will say, which method were called.

Good luck!

like image 35
guerda Avatar answered Nov 14 '22 21:11

guerda


Just use the following to get all messages sent since activating the mail stub.

from google.appengine.api import apiproxy_stub_map
sent_messages = apiproxy_stub_map.apiproxy._APIProxyStubMap__stub_map['mail'].get_sent_messages()
like image 20
misjob Avatar answered Nov 14 '22 22:11

misjob