An integration test spans multiple controllers and actions, tying them all together to ensure they work together as expected. It tests more completely than either unit or functional tests do, exercising the entire stack, from the dispatcher to the database.
At its simplest, you simply extend IntegrationTest
and write your tests using the get/post methods:
require "test_helper" class ExampleTest < ActionDispatch::IntegrationTest fixtures :people def test_login # get the login page get "/login" assert_equal 200, status # post the login and follow through to the home page post "/login", params: { username: people(:jamis).username, password: people(:jamis).password } follow_redirect! assert_equal 200, status assert_equal "/home", path end end
However, you can also have multiple session instances open per test, and even extend those instances with assertions and methods to create a very powerful testing DSL that is specific for your application. You can even reference any named routes you happen to have defined.
require "test_helper" class AdvancedTest < ActionDispatch::IntegrationTest fixtures :people, :rooms def test_login_and_speak jamis, david = login(:jamis), login(:david) room = rooms(:office) jamis.enter(room) jamis.speak(room, "anybody home?") david.enter(room) david.speak(room, "hello!") end private module CustomAssertions def enter(room) # reference a named route, for maximum internal consistency! get(room_url(id: room.id)) assert(...) ... end def speak(room, message) post "/say/#{room.id}", xhr: true, params: { message: message } assert(...) ... end end def login(who) open_session do |sess| sess.extend(CustomAssertions) who = people(who) sess.post "/login", params: { username: who.username, password: who.password } assert(...) end end end
Another longer example would be:
A simple integration test that exercises multiple controllers:
require "test_helper" class UserFlowsTest < ActionDispatch::IntegrationTest test "login and browse site" do # login via https https! get "/login" assert_response :success post "/login", params: { username: users(:david).username, password: users(:david).password } follow_redirect! assert_equal '/welcome', path assert_equal 'Welcome david!', flash[:notice] https!(false) get "/articles/all" assert_response :success assert_select 'h1', 'Articles' end end
As you can see the integration test involves multiple controllers and exercises the entire stack from database to dispatcher. In addition you can have multiple session instances open simultaneously in a test and extend those instances with assertion methods to create a very powerful testing DSL (domain-specific language) just for your application.
Here's an example of multiple sessions and custom DSL in an integration test
require "test_helper" class UserFlowsTest < ActionDispatch::IntegrationTest test "login and browse site" do # User david logs in david = login(:david) # User guest logs in guest = login(:guest) # Both are now available in different sessions assert_equal 'Welcome david!', david.flash[:notice] assert_equal 'Welcome guest!', guest.flash[:notice] # User david can browse site david.browses_site # User guest can browse site as well guest.browses_site # Continue with other assertions end private module CustomDsl def browses_site get "/products/all" assert_response :success assert_select 'h1', 'Products' end end def login(user) open_session do |sess| sess.extend(CustomDsl) u = users(user) sess.https! sess.post "/login", params: { username: u.username, password: u.password } assert_equal '/welcome', sess.path sess.https!(false) end end end
See the request helpers documentation for help on how to use get
, etc.
You can also test your JSON API easily by setting what the request should be encoded as:
require "test_helper" class ApiTest < ActionDispatch::IntegrationTest test "creates articles" do assert_difference -> { Article.count } do post articles_path, params: { article: { title: "Ahoy!" } }, as: :json end assert_response :success assert_equal({ id: Article.last.id, title: "Ahoy!" }, response.parsed_body) end end
The as
option passes an “application/json” Accept header (thereby setting the request format to JSON unless overridden), sets the content type to “application/json” and encodes the parameters as JSON.
Calling parsed_body
on the response parses the response body based on the last response MIME type.
Out of the box, only :json
is supported. But for any custom MIME types you've registered, you can add your own encoders with:
ActionDispatch::IntegrationTest.register_encoder :wibble, param_encoder: -> params { params.to_wibble }, response_parser: -> body { body }
Where param_encoder
defines how the params should be encoded and response_parser
defines how the response body should be parsed through parsed_body
.
Consult the Rails Testing Guide for more.
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.