[英]How to mock a SendGrid method in Python
I'm trying to mock the SendGrid method within my Flask view function, so that it does not send an email during testing. 我正在尝试在我的Flask视图函数中模拟SendGrid方法,以便它在测试期间不发送电子邮件。 When I run the below code I get an error 'ImportError: No module named sg'. 当我运行下面的代码时,我得到一个错误'ImportError:没有名为sg的模块'。 How can I properly configure the 'sg' method so it is found in testing? 如何正确配置'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)
Mocking has to be implemented with respect to where you are testing, and not where you have implemented the method. 必须针对您测试的位置实施模拟,而不是实现方法的位置。 Or, also in your case, mocking the sg
object from unittest will not work. 或者,在您的情况下,从unittest模拟sg
对象将不起作用。
So, I am not exactly sure what the structure of your project is. 所以,我不确定你的项目结构是什么。 But hopefully this example helps. 但希望这个例子有所帮助。
You need to make sure that you are also referencing the appropriate location of where that class is that you want to mock out, to properly mock out its methods. 您需要确保您还引用了要模拟的类的适当位置,以正确模拟其方法。
So, let us assume you are running your tests from test.py: 那么,让我们假设您正在从test.py运行测试:
test.py
your_app/
views.py
tests/
all_your_tests.py
Inside views.py, you are importing send like this: 在views.py中,您导入的发送方式如下:
from module_holding_your_class import SendGridClient
So, to look at your mock.patch, it should look like this: 所以,看看你的mock.patch,它应该是这样的:
@mock.patch('your_app.views.SendGridClient.send')
def test_add_user_page_loads(self, mocked_send):
As you can see, you are running from test.py, so your imports are with reference from there. 如您所见,您从test.py运行,因此您的导入是从那里引用的。 This is where I suggest running your tests with respect to where you actually run your real code, so that you don't have to mess around with your imports. 这就是我建议你在实际运行实际代码的地方运行测试的地方,这样你就不必乱用你的导入了。
Furthermore, you are mocking the send
that you are calling in views.py. 此外,您正在模拟您在views.py中调用的send
。
That should work. 这应该工作。 Let me know how that goes. 让我知道这是怎么回事。
So, based on your code, it would probably be more beneficial for you if you actually mocked out an instance of your class. 因此,根据您的代码,如果您实际模拟了类的实例,那么对您来说可能会更有益。 This way you can very easily test all your methods within that single mock of the instance of SendGridClient
, or even Mail
. 这样,您可以非常轻松地在SendGridClient
实例的单个模拟中测试所有方法,甚至是Mail
。 This way you can focus on the explicit behaviour of your method without worrying about functionality from externals. 这样,您可以专注于方法的显式行为,而无需担心外部功能。
To accomplish mocking out an instance of a Class (or in your case two), you will have to do something like this (explanation inline) 要完成模拟类的实例(或者在你的情况下是两个),你将不得不做这样的事情(内联解释)
*This specific example is untested and probably not complete. *此具体示例未经测试,可能不完整。 The goal is to get you to understand how to manipulate the mock and the data to help your testing. 目标是让您了解如何操作模拟和数据以帮助您进行测试。
Further down below I have a fully tested example to play around with.* 再往下面,我有一个经过充分测试的例子来玩。*
@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)
Based on the code that you provided, I am not sure exactly what Mail
is returning. 根据您提供的代码,我不确定Mail
究竟返回了什么。 I am assuming it is an object of Mail
. 我假设它是Mail
一个对象。 If that is the case, then the above test case would suffice. 如果是这种情况,那么上述测试用例就足够了。 However, if you are looking to test the content of message
itself and make sure the data inside each of those object properties is correct, I strongly recommend separating your unittests to handle it in the Mail
class and ensure that the data is behaving as expected. 但是,如果您要测试message
本身的内容并确保每个对象属性中的数据都是正确的,我强烈建议将您的单元测试分开以在Mail
类中处理它,并确保数据的行为符合预期。
The idea is that your add_user
method should not care about validating that data yet. 这个想法是你的add_user
方法不应该关心验证那些数据。 Just that a call was made with the object. 就是用对象调用了。
Furthermore, inside your send method itself, you can further unittest in there to make sure that the data you are inputting to the method is treated accordingly. 此外,在send方法本身内部,您可以在其中进一步进行单元测试,以确保相应地处理您输入到方法的数据。 This would make your life much easier. 这会让你的生活更轻松。
Here is a example I put together that I tested that I hope will help clarify this further. 以下是我测试的一个例子,我希望这将有助于进一步澄清这一点。 You can copy paste this in to your editor and run it. 您可以将其粘贴到编辑器中并运行它。 Pay attention to my use of __main__
, it is to indicate where I am mocking from. 注意我对__main__
使用,它是指示我在哪里嘲笑。 In this case it is __main__
. 在这种情况下,它是__main__
。
Also, I would play around with side_effect
and return_value
(look at my examples) to see the different behaviour between the two. 另外,我会使用side_effect
和return_value
(查看我的示例)来查看两者之间的不同行为。 side_effect
will return something that gets executed. side_effect
将返回被执行的内容。 In your case you are wanting to see what happens when you execute the method send. 在您的情况下,您希望看到执行方法发送时会发生什么。
Each unittest is mocking in different ways and showcasing the different use cases you can apply. 每个单元测试都以不同的方式进行模拟,并展示您可以应用的不同用例。
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()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.