这篇文章首先介绍 Rails5 中 controller 测试的变化,然后通过类图来分析 Rails5 中的IntegrationTest
相关的类组织结构。通过理解相关的类和模块的关系来帮助我们写出更好的测试。
在之前的文章minitest + capybara 测试基于 devise 的用户注册中,也是通过创建继承自ActionDispatch::IntegrationTest
的FeatureTest
类,然后引入 Capybara 的 DSL 模块,来方便我们创建其他 Feature Test 或者叫 User Acceptance Test。
在 Rails5 中 Controller 测试都是继承自ActionDispatch::IntegrationTest
类,而不是之前的ActionController::TestCase
。如果还想继续使用,那么可以使用这个 gem: rails-controller-testing。
在 Rails4 的 controller 测试中assigns
方法用于获得 action 中的实例变量然后进行验证,assert_template
用于验证 action 最后渲染了指定的 template。
在 Rails5 中,controller 测试强调的更多的是 action 的处理结果,比如响应的状态和响应的结果。
如果你还是想使用上面这两个方法,还是可以在rails-controller-testing这个 gem 中找到他们。
assert_select
等验证响应的 HTML 内容的方法,已经移到单独的rails-dom-testing这个 gem 中。
所以,如果是我要验证页面的内容和样式等,还是通过 capybara 来进行精确的操作和验证。
在 Rails4 中,通过 action 的名字来发送请求(说实话我一直很不习惯)
class PicturesControllerTest < ActionController::TestCase
def test_index_response
get :index
assert_response :success
end
end
而在 Rails5 中,要换成 URL(多直观),否则就会抛出异常:URI::InvalidURIError: bad URI
class PicturesControllerTest < ActionDispatch::IntegrationTest
def test_index
get pictures_url
assert_response :success
end
end
在 Rails5 中,HTTP 请求的方法参数必须明确指定关键字,比如 params,flash 等。这样会让代码更加清楚。请看例子:
class PicturesControllerTest < ActionDispatch::IntegrationTest
def test_create
post picture_url, params: { picture: { name: "sea" } }
assert_response :success
end
end
在继承了ActionDispatch::IntegrationTest
类的 Controller 测试中,我们可以使用很多方便的 helper 方法和大量用于结果验证的 assertions 方法。
比如跟响应相关的:
json = response.parsed_body # 解析json格式的响应结果
assert_response :success # 验证成功的请求
还有跟路由 routing 相关的
assert_routing({ method: 'post', path: '/pictures' }, controller: 'pictures', action: 'create')
assert_recognizes({ controller: 'pictures', action: 'index' }, '/')
如果你要测试文件上传功能,Rails 提供了非常方便的方法fixture_file_upload
。但是你会发现你无法在 controller 中直接使用,你需要引入ActionDispatch::TestProcess
模块。有点奇怪?
所以,为了搞清楚这些 helper 方法和 assertions 的来源,也方便我们日后查询相关的文档,我会通过下面的类图来理解 IntegrationTest 这个类。
首先在 Rails 中我们有ActiveSupport::TestCase
类,它继承自Minitest::Test
类,然后像ActionDispatch::IntegrationTest
, ActionView::TestCase
, ActiveJob::TestCase
等我们自己的测试需要继承的测试基类,都继承自ActiveSupport::TestCase
类。同时你会发现,我们自己的 model 的测试都是直接继承自ActiveSupport::TestCase
类。
所以在我们的测试中,可以直接使用 minitest 提供的一些 assertions,比如常见的:
assert_equal( expected, actual, [msg] )
assert_includes( collection, obj, [msg] )
assert_instance_of( class, obj, [msg] )
由于ActiveSupport::TestCase
引入了ActiveSupport::Testing::Assertions
模块,所以我们可以使用非常方便的方法
assert_difference(expression, difference = 1, message = nil, &block)
# 例如
assert_difference 'Article.count' do
post :create, params: { article: {...} }
end
assert_no_difference(expression, message = nil, &block)
另外还有两个比较有用的被引入的模块是ActiveSupport::Testing::FileFixtures
和ActiveSupport::Testing::TimeHelpers
。他们分别提供了访问 fixtures 下面的文件和修改测试时间的方法
file_fixture(fixture_name)
travel(duration, &block)
travel_back()
travel_to(date_or_time)
接下来我们再来看看IntegrationTest
这个类,它首先通过引入Integration::Runner
模块,从而一起引入了ActionDispatch::Assertions
模块,然后 Runner 中运行测试的时候,会创建Integration::Session
类的实例,Integration::Session
引入了Integration::RequestHelpers
模块,所以我们就可以使用像 get, post, put 等 HTTP 请求相关的方法。
具体的这些请求相关的方法,参考文档: ActionDispatch::Integration::RequestHelpers
了解了发送请求的方法后,我们再来看看ActionDispatch::Assertions
模块,它只是引入了另外两个重要的 module:ActionDispatch::Assertions::ResponseAssertions
和 ActionDispatch::Assertions::RoutingAssertions
ResponseAssertions
中提供了常用的 assert_redirected_to
和assert_response
方法
assert_redirected_to login_url
assert_response :redirect
RoutingAssertions
中提供了上面展示过的:assert_generates
, assert_recognizes
, assert_routing
方法,用于进行路由 Routing 相关的测试。
所以,理解了IntegrationTest
的结构后,我们就知道为什么我们还需要引入ActionDispatch::TestProcess
来测试文件上传,详细的如何测试 Carrierwave 的文件上传功能我会在下一篇文章中介绍。