Ruby rails: testing
Rails provides testing facilities built overtop of ruby minitest.
Test cases are subclasses of ActiveSupport::TestCase
(which is a subclass of Minitest::Test
).
Documentation
official docs https://guides.rubyonrails.org/testing.html rails-minitest assertions https://guides.rubyonrails.org/testing.html#available-assertions rails assertions https://guides.rubyonrails.org/testing.html#rails-specific-assertions
Locations
Test Data {project}/test/factories/{model}.rb
factorybot pre-made models (not technically rails) {project}/test/fixtures/{table}.yml
pre-made models from provided data. Test Types {project}/test/{component}/{component}_test.rb
unittests for models, controllers, helpers, ... {project}/test/integration/
integration tests (ActionDispatch::IntegrationTest) {project}/test/system/{model}_test.rb
system tests (...ui tests with selenium?...)
Usage
Test Runners
NOTE:
If debugging flaky tests,
config.active_support.test_order = :sorted
forces tests to run in a non-random orderNOTE:
The rails test command is defined in the
railties
package.# ========= # unittests # ========= rails test tests/test_file.rb # run testfile rails test tests/test_file.rb:99 # run testfile, test at line 99 # some other options rails test \ -b `# print backtrace for each failure` \ -n /matches_regex/ \ -v `# show tests as they run` \ tests/test_file.rb # ============ # system tests # ============ rails test:system # run all system testsYou can also manually run tests, *with extra cli-options* with the following code from
railties
$LOAD_PATH << Rails.root.join("test") args = ["test/some/awesome_test.rb", "--some-cli-arg", "--some-other-arg"] Rails::TestUnit::Runner.parse_options(args) Rails::TestUnit::Runner.run(args)Generators
rails generate integration_test blog_flow # gen integration testMethods
TODO:
maybe this deserve it's own section?
test "does thing" do skip "skip this test because reasons.." end
Sample
Models
require 'test_helper' class ArticleTest < ActiveSupport::TestCase setup do # def setup @foo = "bar" User.destroy_all # destroy all instances of users before each test end test "the truth" do # def test_the_ruth assert true end endControllers
class ArticleControllerTest < ActionController::TestCase end
Basics
Assertions
By default, rails testcases support all minitest assertions, and some general rails-specific tests. (See https://guides.rubyonrails.org/testing.html#rails-specific-assertions ). See ruby minitest .
You may also include more specific tests:
TestCase Supplemental Assertions ActiveSupport::TestCase (base) ActiveSupport::Testing::Assertions ActionMailer::TestCase ActionMailer::TestHelper ActionView::TestCase none ActionController::TestCase Rails::Dom::Testing ActiveJob::TestCase ActiveJob::TestHelper ActionDispatch::IntegrationTest none? ActionDispatch::SystemTestCase Rails::Generators::TestCase Some useful, universally available assertions.
# asserts that article.count increases by +2 # after the block completes assert_difference ->{ Article.count }, 2 do post :create, params: { article: {...} } endModel Fixtures
Using a similar name/path convention as models/views/controllers, rails uses YAML files to define ready-made test-data.
sample model
# {project}/app/models/user.rb class User < ActiveRecord::Base # columns: # first_name: str # last_name: str enddefine fixtures for model
# {project}/test/fixtures/users.yml # # file is preprocessed with eruby alex: first_name: Alex last_name: Guthrie courtney: first_name: Courtney last_name: Breauuse fixtures within test
# {project}/test/controllers/user_controller.rb class UserControllerTest < ? test "user alex" do user = users(:alex) # <-- refers to YAML file alex, courtney = users(:alex, :courtney) # <-- refers to YAML file assert_equal user.first_name, "Alex" assert_equal user.last_name, "Guthrie" end enduse fixtures in rails console
# build all fixtures from '/test/fixtures/users.yml' rake db:fixtures:load FIXTURES=users # build all fixtures from 'test/fixtures/admin/users.yml' rake db:fixtures:load \ FIXTURES_DIR=admin \ FIXTURES=admin/usersrails console # open 'dev' rails console env RAILS_ENV=test rails console # open 'test' rails console RAILS_ENV=development bin/rails db:fixtures:load # load fixtures into dev db
Model Factories
Just a note, ruby factorybot is often used in place of fixtures.
Test Types
NOTE:
The best ways of learning available options to each test are:
- Read API docs for TestCase (very good overview)
binding.pry
within a test case, issuels
(cd ActiveController::TestCase
only shows class-methods!)Controller Tests
Helpers
post(:show, params: { a: 1, b: 2 }) # get, post, patch, put, ...URL helpers
You won't be able to complete these in pry, but you can use URL helpers in tests.
Unfortunately, the naming convention is slightly different than in production.assert_redirected_to ${namespace}_${controller}_path # nested resources assert_redirected_to action: :new # on same controllerNested Resource Controllers
When testing a path to a nested-resource, you must include the ID of the resource above it in params.
class HouseControllerTest < ActionController::TestCase test "..." do # POST https://domain.com/family/1/house#create post(:create, params: { family: 1 } end endDebugging Rails Magic
# break when controller is about to call action during tests # ========================================================== break AbstractController::Callbacks#process_action # actionpack-X.X.X.X/lib/abstract_controller/callbacks.rb # or break ${YOUR_CONTROLLER}#process_actionView Tests
Also applicable to emails.
Selector Assertions
Selectors target a type of HTML element, it's attributes between brackets.
For example (p, form, div, tr, ...).# assert 1x form-input w/ value assert_select(“form input[type=submit][value=‘Send Reset Instructions’]”, 1) # assert select form with values assert_select("form", id: "new_cat", action: "/cats/#{cat.id}") do assert_select("input[type=date][name*=birthday][value=\"1970-01-01\"]", 1) assert_select("input[type=checkbox][name*=healthy][value=1]", 1) endSelector Syntax
assert_select("div#my-div", 1) # <div id=my-div> assert_select("div.my-class", 1) # <div class=my-class> assert_select("div[name]", 1) # <div name=${ANYTHING}> assert_select("div[name^=foo]", 1) # div, where name starts with 'foo' assert_select("div[name$=bar]", 1) # div, where name ends with 'bar' assert_select("div[name*=oba]", 1) # div, where name contains 'oba' assert_select("div[name~=baz]", 1) # div, where name contains 'baz' (space separated word) assert_select("div[name=foobar], 1) # div, where name=foobarModel Tests
Migration Tests
TODO:
learn. also, learn how to seed only test database.
Job Tests
perform param_a: 1, param_b: 2 # run job within test (is normally queued)Email Tests
See Also: View Tests
email.body.parts # 0=text/plain 1=text/html email.html_part.decoded # raw html email_contents = ::Nokogiri::DocumentFragment.parse(email.html_part.decoded) assert_select email_contents, ":root" do assert_select 'p', /part of email/ end email.deliver_now assert_select_email do assert_select 'p', /part of email/ endLog Tests