Python 测试用户邀请系统

Python 测试用户邀请系统,python,unit-testing,flask,Python,Unit Testing,Flask,我在Flask应用程序中构建了一个用户邀请系统,并希望使用Python unittest对其进行测试。我正在使用SendGrid发送电子邮件。如何阻止SendGrid在测试中实际发送电子邮件?我可以接收用户邀请链接并将其拉入测试吗?我的代码如下: # views.py @app.route('/add_user', methods=['GET', 'POST']) @login_required @groups_required(['site_admin']) def add_user():

我在Flask应用程序中构建了一个用户邀请系统,并希望使用Python unittest对其进行测试。我正在使用SendGrid发送电子邮件。如何阻止SendGrid在测试中实际发送电子邮件?我可以接收用户邀请链接并将其拉入测试吗?我的代码如下:

# views.py
@app.route('/add_user', methods=['GET', 'POST'])
@login_required
@groups_required(['site_admin'])
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')

        # catch and display SendGrid errors
        except SendGridClientError as err:
            flash(err.message.get('message'))
        except SendGridServerError as err:
            flash(err.message.get('message'))
    return render_template('dashboard/add_user.html', form=form)


@app.route('/add_user_confirm/<token>', methods=['GET', 'POST'])
def add_user_confirm(token):
    """
    Decode invite token and create new user account
    """
    form = RegistrationForm()
    decoded = None
    try:
        ts = URLSafeTimedSerializer(app.config['SECRET_KEY'])
        decoded = ts.loads(token, max_age=86400)
        email = decoded[0]
    except:
        abort(404)

    if form.validate_on_submit():
        try:
            tenant_id = decoded[1]

            data = {}
            data['email'] = email
            data['password'] = request.form['password']

            # given_name and surname are required fields
            data['given_name'] = 'Anonymous'
            data['surname'] = 'Anonymous'

            # set tenant id and site_admin status
            data['custom_data'] = {
                'tenant_id': tenant_id,
                'site_admin': 'False'
            }

            # create account
            account = User.create(**data)

            # add user to tenant group
            account.add_group(tenant_id)

            # login user
            login_user(account, remember=True)

            # success redirect
            return render_template('account/add_user_complete.html')
        except StormpathError as err:
            flash(err.message.get('message'))

    elif request.method == 'POST':
        flash("Passwords don't match.")

    return render_template('account/add_user_setpassword.html',
                           form=form,
                           email=email)

# tests.py
def test_add_user(self):
    resp = self.client.post('/add_user', data={
            'email': self.test_email
        }, follow_redirects=True)
assert 'User invitation sent' in resp.data
#views.py
@app.route('/add_user',methods=['GET','POST'])
@需要登录
@需要组(['site\u admin'])
def add_user():
"""
向受邀请用户发送带有令牌的邀请电子邮件
"""
form=AddUserForm()
if form.validate_on_submit():
#令牌序列化程序
ts=URLSafeTimedSerializer(app.config['SECRET\u KEY'])
电子邮件=请求。表格['email']
租户id=user.custom\u数据['tenant\u id']
#创建包含电子邮件和租户id的令牌
令牌=ts.dumps([电子邮件,租户id])
#使用令牌创建url,例如/add\u user\u confirm/asdf asd fasdf
确认的url=url(
“添加用户确认”,
令牌=令牌,
_外部=真)
尝试:
#sendgrid设置
sg=sendgrid.SendGridClient(
app.config['SENDGRID\u API\u KEY'],
raise_errors=True
)
#电子邮件设置
message=sendgrid.Mail(
to=请求。表单['email'],
主题为“帐户邀请”,
html='您已被邀请在PhotogApp上设置帐户。单击此处:'+确认\u url,
来自support@photogapp.com'
)
#发送电子邮件
状态,msg=sg.send(消息)
flash('邀请已成功发送')
返回呈现模板('dashboard/add_user_complete.html')
#捕获并显示SendGrid错误
除SendGridClientError作为错误外:
flash(err.message.get('message'))
除SendGridServerError作为错误外:
flash(err.message.get('message'))
返回呈现模板('dashboard/add_user.html',form=form)
@app.route('/add\u user\u confirm/',方法=['GET','POST'])
def添加用户确认(令牌):
"""
解码邀请令牌并创建新用户帐户
"""
表单=注册表单()
已解码=无
尝试:
ts=URLSafeTimedSerializer(app.config['SECRET\u KEY'])
解码=ts.loads(令牌,最大年龄=86400)
电子邮件=已解码[0]
除:
中止(404)
if form.validate_on_submit():
尝试:
租户id=已解码[1]
数据={}
数据['email']=电子邮件
数据['password']=request.form['password']
#给定的名称和姓氏是必填字段
数据['given_name']=“匿名”
数据[‘姓氏’]=“匿名”
#设置租户id和站点管理员状态
数据['custom_data']={
“租户id”:租户id,
“站点管理”:“错误”
}
#创建帐户
帐户=用户。创建(**数据)
#将用户添加到租户组
帐户。添加组(租户id)
#登录用户
登录用户(帐户,记住=True)
#成功重定向
返回呈现模板('account/add\u user\u complete.html')
除StormpathError作为错误外:
flash(err.message.get('message'))
elif request.method==“POST”:
闪存(“密码不匹配”)
返回呈现模板('account/add_user_setpassword.html',
形式=形式,
电子邮件=电子邮件)
#tests.py
def测试添加用户(自身):
resp=self.client.post('/add_user',data={
“电子邮件”:self.test\u电子邮件
},follow_redirects=True)
在响应数据中断言“已发送用户邀请”

所以这是一个非常适合模仿的案例。模拟背后的基本思想是用普通的通过、失败和副作用替换当前的功能(在本例中是发送电子邮件)

否则你只能说

from unittest import mock

这样,当调用
sg.send
时,它将调用模拟函数而不是实际函数。由于mock默认为空,因此没有可怕的电子邮件发送


注意
SendGrid
需要导入,并且在
test.py
文件的范围内。

这很好。我以前从未听说过mock。我正在尝试让它工作,但遇到了“ImportTerror:这里是我设置“sg”的地方”:今晚我没有时间查看您的代码,但我将尝试明天运行它并向您报告。如果我忘了,戳我一下。我最后把问题作为一个单独的问题问了出来,得到了很好的回答。谢谢
# tests.py
from unittest import mock

@mock.patch("sg.send") 
def test_add_user(self, mocked_send):
    mocked_send.return_value = None # Do nothing on send
    resp = self.client.post('/add_user', data={
            'email': self.test_email
        }, follow_redirects=True)
assert 'User invitation sent' in resp.data