views:

1319

answers:

3

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]&amp;body=blah_blah_blah&amp;subject=mySubject')
    self.assertEqual('200 OK', response.status)
    self.assertTrue('success' in response)
    # somehow, assert email was sent
+2  A: 

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!

furtelwart
+2  A: 

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)
jgeewax
Check out http://gae-testbed.googlecode.com which gives base classes that do just this.
jgeewax
This project looks promising. I hope you can furnish the tutorials already, especially the one for testing the Mail API.
Randell
A: 

I used jgeewax's GAE Testbed with GAEUnit. Using GAEUnit instead of NoseGAE was easier for me since I already had GAEUnit in place. Here are the steps:

Add GAEUnit to your app

  1. Download the zipped archive of GAEUnit from its Google Code project hosting page.
  2. Extract the archive.
  3. From the folder extracted from the archive, copy gaeunit.py to your app’s root folder.
  4. Add the following 2 lines to your app.yaml, directly below the line that says handlers::

Code:

- url: /test.*
  script: gaeunit.py

(Optional) From the folder extracted from the archive, there’s a folder named sample_app and inside it is the modified version of the webtest module. Copy the webtest module (the entire folder containing debugapp.py and __init__.py) to the root of your app.

Add GAE Testbed to GAEUnit:

  1. Download the GAE Testbed tar gzipped archive from its Google Code project hosting page.
  2. Extract the archive.
  3. Inside the extracted archive is the gaetestbed module (it’s the folder named “gaetestbed”). Copy the module to the root of your app.
  4. Create a file inside the test folder of the root of your app. For the sake of this example, let’s name it test_mailer.py.
  5. Using the example from the GAE Testbed Google Code project hosting page, add the following lines to test_mailer.py:

Code:

import unittest
from gaetestbed import MailTestCase

class MyTestCase(MailTestCase, unittest.TestCase):
    def test_email_sent(self):
        send_email_to('[email protected]') # Some method that sends e-mail...
        self.assertEmailSent(to='[email protected]')
        self.assertEqual(len(self.get_sent_messages()), 1)

Start your server and go to http://localhost:8080/test. You should notice that (an additional) 1/1 test was ran from http://localhost:8080/test.

Source: Using GAE Testbed with GAEUnit: Testing that email was sent

Randell