python - 如何在 Python 中模拟 SendGrid 方法

标签 python unit-testing flask mocking

我试图在我的 Flask View 函数中模拟 SendGrid 方法,这样它就不会在测试期间发送电子邮件。当我运行以下代码时,出现错误“ImportError: No module named sg”。如何正确配置“sg”方法以便在测试中找到它?

# test_helpers.py
from unittest import TestCase
from views import app

class PhotogTestCase(TestCase):

    def setUp(self):
        app.config['WTF_CSRF_ENABLED'] = False
        app.config['TESTING'] = True
        self.app = app
        self.client = app.test_client()

# test_views.py
import mock
from test_helpers import PhotogTestCase
import sendgrid

class TestAddUser(PhotogTestCase):

    sg = sendgrid.SendGridClient(app.config['SENDGRID_API_KEY'])

    @mock.patch('sg.send')
    def test_add_user_page_loads(self, mocked_send):
        mocked_send.return_value = None  # Do nothing on send

        resp = self.client.post('/add_user', data={
                'email': 'joe@hotmail.com'
            }, follow_redirects=True)
        assert 'Wow' in resp.data

# views.py
import sendgrid
from itsdangerous import URLSafeTimedSerializer
from flask import Flask, redirect, render_template, \
    request, url_for, flash, current_app, abort
from flask.ext.stormpath import login_required
from forms import RegistrationForm, AddContactForm, \
    AddUserForm

@app.route('/add_user', methods=['GET', 'POST'])
@login_required
def add_user():
    """
    Send invite email with token to invited user
    """
    form = AddUserForm()

    if form.validate_on_submit():

        # token serializer
        ts = URLSafeTimedSerializer(app.config['SECRET_KEY'])

        email = request.form['email']
        tenant_id = user.custom_data['tenant_id']

        # create token containing email and tenant_id
        token = ts.dumps([email, tenant_id])

        # create url with token, e.g. /add_user_confirm/asdf-asd-fasdf
        confirm_url = url_for(
            'add_user_confirm',
            token=token,
            _external=True)

        try:
            # sendgrid setup
            sg = sendgrid.SendGridClient(
                app.config['SENDGRID_API_KEY'],
                raise_errors=True
            )

            # email setup
            message = sendgrid.Mail(
                to=request.form['email'],
                subject='Account Invitation',
                html='You have been invited to set up an account on PhotogApp. Click here: ' + confirm_url,
                from_email='support@photogapp.com'
            )

            # send email
            status, msg = sg.send(message)

            flash('Invite sent successfully.')
            return render_template('dashboard/add_user_complete.html')

    return render_template('dashboard/add_user.html', form=form)

最佳答案

说明

必须根据您正在测试的位置实现模拟,而不是您实现该方法的位置。或者,同样在您的情况下,从 unittest 模拟 sg 对象将不起作用。

因此,我不确定您的项目结构是什么。但希望这个例子能有所帮助。

您需要确保您还引用了您要模拟的类所在的适当位置,以正确模拟其方法。

解决方案

因此,让我们假设您正在从 test.py 运行测试:

test.py
    your_app/
        views.py
    tests/
        all_your_tests.py

在 views.py 中,您将像这样导入发送:

from module_holding_your_class import SendGridClient

因此,要查看您的 mock.patch,它应该如下所示:

@mock.patch('your_app.views.SendGridClient.send')
def test_add_user_page_loads(self, mocked_send):

如您所见,您是从 test.py 运行的,因此您的导入是从那里引用的。这是我建议根据实际运行实际代码的位置运行测试的位置,这样您就不必弄乱导入。

此外,您正在模拟您在 views.py 中调用的 send

那应该行得通。让我知道进展如何。

额外信息:类的模拟实例

因此,根据您的代码,如果您实际模拟出您的类的一个实例,可能对您更有利。通过这种方式,您可以非常轻松地在 SendGridClient 或什至 Mail 实例的单个模拟中测试所有方法。通过这种方式,您可以专注于方法的显式行为,而不必担心来自外部的功能。

要完成模拟一个类的实例(或者在你的情况下是两个),你必须做这样的事情(内联解释)

*此具体示例未经测试,可能不完整。目标是让您了解如何操纵模拟和数据来帮助您进行测试。

下面还有一个经过全面测试的示例。*

@mock.patch('your_app.views.Mail')
@mock.patch('your_app.views.SendGridClient')
def test_add_user_page_loads(self, m_sendgridclient, m_mail):
    # get an instance of Mock()
    mock_sgc_obj = mock.Mock()
    mock_mail_obj = mock.Mock()

    # the return of your mocked SendGridClient will now be a Mock()
    m_sendgridclient.return_value = mock_sgc_obj
    # the return of your mocked Mail will now be a Mock()
    m_mail.return_value = mock_mail_obj

    # Make your actual call
    resp = self.client.post('/add_user', data={
            'email': 'joe@hotmail.com'
        }, follow_redirects=True)

    # perform all your tests
    # example
    self.assertEqual(mock_sgc_obj.send.call_count, 1)
    # make sure that send was also called with an instance of Mail.
    mock_sgc_obj.assert_called_once_with(mock_mail_obj)

根据您提供的代码,我不确定 Mail 返回的到底是什么。我假设它是 Mail 的一个对象。如果是这样的话,那么上面的测试用例就足够了。但是,如果您希望测试 message 本身的内容并确保每个对象属性中的数据都是正确的,我强烈建议您将单元测试分开以在 Mail< 中处理它 类并确保数据按预期运行。

想法是您的 add_user 方法不应该关心验证该数据。只是对对象进行了调用。

此外,在您的发送方法本身内部,您可以在那里进一步进行单元测试,以确保您输入到该方法的数据得到相应处理。这会让您的生活更轻松。

例子

这是我整理并测试过的示例,希望有助于进一步阐明这一点。您可以将其复制粘贴到您的编辑器中并运行它。注意我使用的__main__,它表示我从哪里开始模拟。在这种情况下,它是 __main__

另外,我会尝试使用 side_effectreturn_value(查看我的示例)来了解两者之间的不同行为。 side_effect 将返回一些被执行的东西。在你的情况下,你想看看当你执行发送方法时会发生什么。

每个单元测试都以不同的方式进行模拟,并展示您可以应用的不同用例。

import unittest
from unittest import mock


class Doo(object):
    def __init__(self, stuff="", other_stuff=""):
        pass


class Boo(object):
    def d(self):
        return 'the d'

    def e(self):
        return 'the e'


class Foo(object):

    data = "some data"
    other_data = "other data"

    def t(self):
        b = Boo()
        res = b.d()
        b.e()
        return res

    def do_it(self):
        s = Stuff('winner')
        s.did_it(s)

    def make_a_doo(self):
        Doo(stuff=self.data, other_stuff=self.other_data)


class Stuff(object):
    def __init__(self, winner):
        self.winner = winner

    def did_it(self, a_var):
        return 'a_var'


class TestIt(unittest.TestCase):

    def setUp(self):
        self.f = Foo()

    @mock.patch('__main__.Boo.d')
    def test_it(self, m_d):
        '''
            note in this test, one of the methods is not mocked.
        '''
        #m_d.return_value = "bob"
        m_d.side_effect = lambda: "bob"

        res = self.f.t()

        self.assertEqual(res, "bob")

    @mock.patch('__main__.Boo')
    def test_them(self, m_boo):
        mock_boo_obj = mock.Mock()
        m_boo.return_value = mock_boo_obj

        self.f.t()

        self.assertEqual(mock_boo_obj.d.call_count, 1)
        self.assertEqual(mock_boo_obj.e.call_count, 1)

    @mock.patch('__main__.Stuff')
    def test_them_again(self, m_stuff):
        mock_stuff_obj = mock.Mock()
        m_stuff.return_value = mock_stuff_obj

        self.f.do_it()

        mock_stuff_obj.did_it.assert_called_once_with(mock_stuff_obj)
        self.assertEqual(mock_stuff_obj.did_it.call_count, 1)

    @mock.patch('__main__.Doo')
    def test_them(self, m_doo):

        self.f.data = "fake_data"
        self.f.other_data = "some_other_fake_data"

        self.f.make_a_doo()

        m_doo.assert_called_once_with(
            stuff="fake_data", other_stuff="some_other_fake_data"
        )

if __name__ == '__main__':
    unittest.main()

关于python - 如何在 Python 中模拟 SendGrid 方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33311001/

相关文章:

python - 化学表示 - SNL 到 SMILES

python - StaleElementReferenceException selenium webdriver python

Python Flask 应用程序无法解析 XML 请求数据

python - 循环遍历蓝图中的所有规则并根据文件、flask 检查 json

c++ - 如何在 boost.python 中转换 std::string* ?

python - 在 python 中评估后缀?

javascript - 如何在 Angular 8 中使用 jasmine 模拟回调函数来测试 promise

javascript - Jest 单元测试函数抛出错误

python - 如何在 Python 中模拟用户输入

python - 获取按钮名称作为输入