Many developers will rely on interacting with an app to prove it works the way they want. But this is not the most efficient way to do things. Writing tests for your code is a good practice, and it can save time finding bugs down the line. In this series of blog posts, I’m going to talk about how to write tests for a Rails application.
First, I’ll talk a bit about why testing is so important. Writing tests is useful because you can use them to check that a small piece of your code works the way it was intended. This is also a good way to become more familiar with your code, because it forces you to thinks about what exactly you want each piece of your code to do. And when you find a bug in your code you can have a better idea of where it is, because you know for sure that certain things work. Also, once you’ve written a test, you can run it over and over to check that everything still works as expected, even after you have made a major change to your app.
Writing tests in Rails is relatively easy. A Rails application started with rails new
already has a testing framework set up for you, in a folder called test. It has a file structure like this:
- test
- channels/
- controllers/
- fixtures/
- helpers/
- integration/
- mailers/
- models/
- system/
- application_system_test_case.rb
- test_helper.rb
Each of the folders in test are designed to test a specific part of your application, and I’ll go into each of them in a later blog post. For this one, I’ll just focus on the basics of writing and running tests.
As an example, I’ll make references to files in a project created with these commands:
~ // ♥ > rails g scaffold Author name age:integer
~ // ♥ > rails g scaffold Book title author_id:integer
When you use a rails generator like rails g scaffold
or rails g resource
to create files for your models, rails will automatically create some test files for these models that you can fill in. In the models folder I have a file like this:
# author_test.rb
require 'test_helper'
class AuthorTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end
The class AuthorTest inherits from ActiveSupport::TestCase which gives us a number of useful methods, including the one used in the example commented out here. The test method takes a test name(here, the name is “the truth”
) and a block(everything between the do
and end
), and if you give it a descriptive name, it can be very clear what the test is designed to look for.
In the do…end
block is an assertion. Assertions are designed to evaluate something for expected results. There are a variety of available assertions, and there is a full list of them here. Each test can have as many assertions as you want, and the test will only pass if all the assertions are successful. An example assertion for my Authors/Books project test that an author will not save without a name. A test to check for this could look like this:
test "author should not save without name" do
author = Author.new
assert_not author.save
end
Now that we have a test written, we need to be able to run it and see the results. There are a variety of ways to run your tests; you could run all of them at once with rails test
, but this isn’t very efficient. Running all of your tests can take longer, so if you just want to check the results of a specific test, the best way is to drill down to run just the tests in the file, or even the test on a given line. So the command to run the test I wrote would look like this (7 being the line the test starts on):
~ // ♥ > rails test test/models/author_test.rb:7
But, because I never wrote the validation to require an author to have a name into my Author model class, the test fails, and it denotes the failure with an F:
# Running:
F
Failure:
AuthorTest#test_author_should_not_save_without_name
[/path/rails-testing/test/models/author_test.rb:17]:
Expected true to be nil or false
rails test test/models/author_test.rb:7
Finished in 1.116573s, 0.8956 runs/s, 0.8956 assertions/s.
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips
Unfortunately, this failure isn’t very descriptive about what exactly went wrong. That’s because I didn’t give the assertion a massage to output so it used the default, “Expected true to be nil or false”. To make the test clearer I can add a message, and I’ll also add another assertion to check that an author cannot be saved with an age that is not positive:
test "should not save if validations fail" do
author = Author.new
assert_not author.save, "saved the author without a name"
author = Author.new(name: "J. K. Rowling", age: -10)
assert_not author.save, "saved the author with a non-positive age"
end
Then I’ll add the validations to the model file like so:
#author.rb
class Author < ApplicationRecord
has_many :books
validates :name, presence: true
validates :age, numericality: { greater_than: 0 }
end
Running the test now gives me a passing result:
# Running:
.
Finished in 0.267856s, 3.7333 runs/s, 7.4667 assertions/s.
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips
There is one more result you can get from running a test, and that is an error. The same as in any other code, errors can occur from spelling mistakes or references to variables that don’t exist. For example, if I had misspelled assert_not
as asset_not
(which I did), the result would look like this, with an E to designate the error:
# Running:
E
Error:
AuthorTest#test_should_not_save_if_validations_fail:
NoMethodError: undefined method `asset_not' for
#<AuthorTest:0x00007fea2ffdd178>
test/models/author_test.rb:12:in `block in <class:AuthorTest>'
rails test test/models/author_test.rb:7
Finished in 0.766280s, 1.3050 runs/s, 1.3050 assertions/s.
1 runs, 1 assertions, 0 failures, 1 errors, 0 skips
The error helpfully points to the line where it occurred, on line 12 in author_test.rb. It also ran the first assertion before it found the error in the second one and stopped.
And this is most of what you need to know about the basics of testing in rails. In summary, you write tests in blocks that use assertions to verify the results you expect. Then you run the tests and, if you are clear in your test massages, the results give helpful information about any tests that didn’t pass. In my next blog post I’ll go into the types of tests to write in each of the directories that Rails gives you in the test folder.
Top comments (0)