【译】使用Rails 4.2+ 测试异步邮件系统
来源:互联网 发布:嵌入式软件面试题 编辑:程序博客网 时间:2024/06/05 03:50
李哲 — MAY 26, 2015
原文链接:Testing async emails, the Rails 4.2+ way
假设想写一个需要发送邮件的应用,我们都知道在这种情况是绝对不能block控制器的,因此异步传送才是解决之道。为了达到这个目的,我们需要 将邮件发送代码从最初的request/response
循环中移到后台的异步处理进程中去。
然而,做出这样的改变之后,我们如何确保代码能够一如往常的运行呢?在这篇博文中,我们会探索一种新方法来进行测试,我们将要使用的MiniTest(Rails
已经内置了这个框架), 这里的概念同样使用Rspec
。
现在有一个好消息,那就是从Rails 4.2
开始,异步传送邮件已经比之前简单多了。我们在例子中使用Sidekiq
作为队列系统。由于ActionMailer#deliver_later
建立在ActiveJob
之上, 接口非常的简洁明了。这表示,要不是我刚才提了一下,身为开发者或用户的你也不会知情。建立队列系统是另外一个话题, 你可以在getting started with Active Job here中了解更多相关的信息。
别太依赖小组件
在例子中,我们假定Sidekiq
及其依赖组件配置正确,因此本场景特有的一段代码是声明Active Job
该使用哪一个队列调节器。
# config/application.rb module OurApp class Application < Rails::Application # ... config.active_job.queue_adapter = :sidekiq end end
Active Job
在隐藏实质性的队列配置细节方面功能非常强大,以至于若是使用Resque
,Delayed Job
或其他组件,代码也不需要太大的改动。 因此,如果我们转而使用Sucker Punch
,唯一的改变就是在引用相应的依赖包后,将queue_adapter
从:sidekiq
改为:sucker_punch
就可以了。
站在Active Job的肩膀上
如果你是Rails 4.2
或者Active Job
不太了解,https://blog.engineyard.com/2014/getting-started-with-active-job 是就是很好的入门读物。然而,这篇文章留给我的一个小期许是,找到一种简洁、地道的测试方法,从而让所有组件都能正常的运行。
根据本文的目标,我们假定你已经部署了:
- Rails 4.2或者一个更高的版本
- 已经设置好
queue_adapter
的Active Job (Sidekiq, Resque, 等)- 一封邮件
任何邮件都应该能够按照这里描述的方式正常工作,这里我们就用一封欢迎邮件来使这个例子更实用:
#app/mailers/user_mailer.rb class UserMailer < ActionMailer::Base default from: 'email@example.com' def welcome_email(user:) mail( to: user.email, subject: "Hi #{user.first_name}, and welcome!" ) end end
为了保持程序简单并有针对性,我们会在每个用户注册后发送给他们一封欢迎邮件。
这和the Rails guides mailer example是一样的:
# app/controllers/users_controller.rb class UsersController < ApplicationController def create # Yes, Ruby 2.0+ keyword arguments are preferred UserMailer.welcome_email(user: @user).deliver_later end end
The Mailer Should Do Its Job, Eventually
接下来,我们想确保控制器内的任务能如所期待的那样执行。
在测试指南中,custom assertions for testing jobs inside other components 的章节介绍了大约六种这样的自定义断言方法。
或许直觉告诉你应该单刀直入,然后使用assert_enqueued_jobs assert-enqueued-jobs
来测试每次添加新用户时,我们有否将邮件传送任务放入队列。
你可能会这么做:
# test/controllers/users_controller_test.rb require 'test_helper' class UsersControllerTest < ActionController::TestCase test 'email is enqueued to be delivered later' do assert_enqueued_jobs 1 do post :create, {} end end end
然而如果这么做,你会惊奇地发现测试失败了,系统会告诉你assert_enqueued_jobs
未经定义,且无法使用。
这是因为,我们的测试类继承自ActionController::TestCase
,而后者在编写时没有包含ActiveJob::TestHelper
。
不过我们很快就可以修正这一点:
# test/test_helper.rb class ActionController::TestCase include ActiveJob::TestHelper end
假定我们的代码如期执行,那么测试应该就能顺利通过了。
这是好消息。现在,我们可以重构我们的代码,增加新的功能,也可以增加新的测试。我们可以选择后者,看看我们的邮件有否投递成功,如果是的话,那就检查投递的内容是否正确。
ActionMailer
能为我们提供一个包含所有发出邮件的队列,前提是将delivery_method
选项设置为:test
,我们能通过ActionMailer::Base.deliveries
读取这个队列。
当同步的地投递邮件时,检测邮件是否发送成功是很容易的。我们只需检查在动作完成后,投递计数器加1。用MiniTest
来写的话,就像下面这样:
assert_difference 'ActionMailer::Base.deliveries.size', +1 do post :create, {} end
我们的测试是实时发生的,但在开篇就已经知道不能阻拦控制器,需要在后台进程中发送邮件,现在我们把所有的组件都组装起来,确定系统是没有问题的。 因此,在异步的世界里,我们必须先执行所有队列中的任务再去判定执行结果。为了执行pending
中的Active Job
任务,我们使用perform_enqueued_jobs
:
test 'email is delivered with expected content' do perform_enqueued_jobs do post :create, {} delivered_email = ActionMailer::Base.deliveries.last # assert our email has the expected content, e.g. assert_includes delivered_email.to, @user.email end end
缩短反馈流程
目前为止,我们都在进行功能性测试以确保我们的控制器如期执行。但是,代码的变化足以破坏我们发送的邮件,为什么不对我们的邮件程序进行单元测试,从而缩短反馈流程,然后更快地洞察变化呢?
Rails测试指南建议在这里使用fixtures
,但是我觉得他们太生硬了。尤其是一开始,当我们还在尝试设计邮件时,一个变化很快就会让他们变得不可用,让我们的测试无法通过。 我偏向使用assert_match
以关注那些构成邮件主体的关键元素。
为此,也因为其他原因(比如抽离处理多部分邮件的逻辑结构),我们可以建立自定义断言。这可以扩展MiniTest
标准断言或Rails专属断言。 这也是创建自己的领域专属语言(Domain Specific Language)并用于测试的好例子。
让我们在测试一文件夹内创建一个共享文件夹,用以存放SharedMailerTests
模块。我们自定义的断言可以这么来写:
# /test/shared/shared_mailer_tests.rb module SharedMailerTests def assert_email_body_matches(matcher:, email:) if email.multipart? %w(text html).each do |part| assert_match matcher,email.send("#{part}_part").body.to_s end else assert_match matcher, email.body.to_s end end end
接下来,我们需要让邮件测试系统注意到这个自定义断言,为此,我们可以将其放入ActionMailer::TestCase
类中。 然后可以借鉴之前把ActiveJob::TestHelper
类包含于ActionController::TestCase
类的方法:
# test/test_helper.rb require 'shared/shared_mailer_tests' class ActionMailer::TestCase include SharedMailerTests end
注意,我们首先需要在test_helper
中require shared_mailer_tests
。
这些办好之后,我们现在可以确信我们的邮件中包含我们期望的关键元素。假设我们想确保发送给用户的URL包含一些用于追踪的特定UTM参数。 我们现在可以将自定义断言与老朋友perform_enqueued_jobs
联合起来使用,就像这样:
# test/mailers/user_mailer_test.rb class ToolMailerTest < ActionMailer::TestCase test 'emailed URL contains expected UTM params' do UserMailer.welcome_email(user: @user).deliver_later perform_enqueued_jobs do refute ActionMailer::Base.deliveries.empty? delivered_email = ActionMailer::Base.deliveries.last %W( utm_campaign=#{@campaign} utm_content=#{@content} utm_medium=email utm_source=mandrill ).each do |utm_param| assert_email_body_matches utm_param, delivered_email end end end
结论
在Active Job
的基础上,使用ActionMailer
让从同步发送邮件到通过队列发送邮件的转化变得如此简单,就如同从deliver_now
转化到deliver_later
。
同时,由于使用Active Job
大大简化了设定工作基础环境的流程,你可以对自己所用的队列系统知之甚少。希望这篇教程能让你对此过程有更多了解。
本文作者系OneAPM工程师编译整理。OneAPM是中国基础软件领域的新兴领军企业。专注于提供下一代应用性能管理软件和服务,帮助企业用户和开发者轻松实现:缓慢的程序代码和SQL语句的实时抓取。想阅读更多技术文章,请访问OneAPM官方技术博客。
- 【译】使用Rails 4.2+ 测试异步邮件系统
- 使用curl测试rails rest
- 邮件系统使用规则
- Rails项目中使用Cucumber测试
- rails测试
- 使用Delayed job处理Rails中的异步任务
- 使用Delayed job处理Rails中的异步任务
- 使用 Awaitility 测试异步代码
- IOS XCTest使用异步测试
- 使用Rspec进行rails测试详解1-搭建测试环境
- 邮件系统
- 邮件系统
- 邮件系统
- 邮件系统
- 邮件系统
- 邮件系统
- 邮件系统
- 邮件系统
- 作为第一期iOS基础班学生,MJ广东开校之我见(原稿)
- 教你开通超级QQ,
- CoreData 多线程处理大量数据同步时的操作
- Android使用最新版本NDK开发步骤与环境配置
- VC Win32 读取 磁盘MBR 和 EBR扩展分区 数据
- 【译】使用Rails 4.2+ 测试异步邮件系统
- CFHTTP使用SSL很慢的解决方法
- 今天是我第一次开通本博客 自己爱好学习破解软件,OEM软件。易语言做软件等等,
- MAC MySQL乱码问题解决路径
- Python 4- more operations
- i春秋:通过案例学安全—再现杰奇网站漏洞环境
- i春秋:警惕您站上的MIME类型绕过漏洞
- i春秋:警惕您站上的文件扩展名绕过漏洞
- 【PagerAdapter】Fragment的陷阱