Testing is a very important (and sometimes quite complex!) part of a software development process.
With a solid testing suite in place you can be much more confident when refactoring various parts of the application.
Even if something breaks, a failing test will notify you right away — this is much better than receiving a similar notification from an angry user who cannot log into the system anymore. Also, when writing tests, you are effectively performing a code review: if some component is hard to test it might be due to its poor code structure.
There are multiple testing solutions available, including RSpec and Cucumber, but in this series of articles we will talk about Minitest. Minitest is a default testing framework used in Rails that is both fast and powerful. In this article you will learn how to set up and configure Minitest in order to start writing tests for your Rails application.
The source code for this article can be found at GitHub.
Creating the Application
Okay, start off by creating a new Rails application:
$ rails new MaxiTest
Rails 5.1 has bring lots of nice improvements for us and enhancements of the testing suite is one of them.
For instance, you do not need to include Capybara and Selenium-webdriver into the Gemfile as they are present by default. These gems are used in BDD testing when we are simulating user’s behaviour so most developers were requiring those libraries anyways. Another new great feature is that the Database Cleaner gem is not required anymore — the testing database will be cleared for us automatically.
By the way, if you for some reason do not want to utilize Minitest, provide the -T flag when creating an application:
$ rails new SomeApp -T
In this case the default testing suite won’t be added and you are free to stick with any other solution like RSpec, for example.
All right, so now let’s open the test directory and see what’s inside.
Inside there are a bunch of folders with self-explaining names like controllers and models — these, as you’ve guessed, will store the tests for the corresponding components.
The test_helper.rb is the file that contains configuration for Minitest. It requires a file rails/test_help.rb and loads all the fixtures in alphabetical order from the corresponding directory. Of course, you are free to use FactoryGirl instead of fixtures but bear in mind that the latter solution is slower, though appears to be more flexible.
*application_system_test_case.rb* is a new file that appears only in Rails 5.1+ applications. It is contains configuration for the system tests – that is, tests that perform simulation of the user’s actions with the help of Capybara and Selenium (or some other driver). Note that the rails test command does not execute system tests as they are quite slow, so you’ll need to type rails test:system in order to run them (we’ll talk more about these tests later).
Another thing to remember is that all tests are run under the test environment, so in order to provide some special configuration, tweak the config/environments/test.rb file.
By default Minitest relies on assertions that are written in the following way:
assert user.save assert_equal User.count, 1
There are both generic and Rails-specific assertions that you can utilize — all of them are simple and self-explaining. Still, some developers may prefer specs over assertions (especially developers who are fond of RSpec). Minitest does allow you to run tests in a form of specs:
describe Meme do before do @meme = Meme.new end describe "when asked about cheeseburgers" do it "must respond positively" do @meme.i_can_has_cheezburger?.must_equal "OHAI!" end end describe "when asked about blending possibilities" do it "won't say no" do @meme.will_it_blend?.wont_match /^no/i end end end
In this series I will not use this form of writing, however.
Another possible option is to utilize a third-party solution called Minitest-Rails that supports spec DSL as well.
One thing I prefer changing about the default testing suite is the web driver used for system tests. Driver is a browser automation framework that allows us to perform various actions on web pages with a few lines of code
As you already know, Selenium Webdriver is used by default.
The problem, however, is that Selenium is somewhat slow and not headless, meaning that when the system tests are being executed, the browser literally opens and you see all the performed actions (clicking links, filling in the forms etc) in real-time which is not really convenient. Therefore, I usually stick with a headless driver called Poltergeist.
Poltergiest, in turn, relies on PhantomJS therefore you must install it on your PC. Luckily, PhatomJS works on all major OSes, so you shouldn’t have any problems with it. After PhatomJS is installed make sure it is available in your PATH.
Next, replace gem ‘selenium-webdriver’ line in the Gemfile with the following:
$ bundle install
Require Poltergeist inside the test_helper.rb:
# ... require 'rails/test_help' require 'capybara/poltergeist' # ...
Lastly, tweak the test/application_system_test_case.rb file:
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :poltergeist end
Poltergeist also has a handful of additional options that you may change as needed.
One problem with Poltergeist though is that PhantomJS project seems to be abandoned, therefore some major changes may occur in the near (or not-so-near) future.
Another headless driver that you may utilize is capybara-webkit but it is not very actively maintained and has some issues with Windows support.
One important thing about your tests is their coverage. Coverage basically shows which areas of your application are tested and which still require some tests. Gathering such data is not mandatory but can be pretty handy therefore let’s utilize a popular solution called Simplecov — a code coverage analysis tool.
So, drop in a new gem into the Gemfile:
# ... group :test do gem 'simplecov', require: false end
$ bundle install
Next add the following two lines at the very top of the test/test_helper.rb file:
require 'simplecov' SimpleCov.start 'rails' # ...
This, as you have probably guessed, will start analysis of your tests. The results will be outputted in a form of an HTML file produced by the simplecov-html gem. The report will be stored under the coverage directory by default. We don’t really want this directory to be tracked by Git, so let’s ignore it by modifying the .gitignore file:
Our First Model Test
To see that our setup is working correctly, let’s try generating a new model called Post:
$ rails g model Post body:text
You should see an output similar to this one:
It means that apart from the model and migration, a test file and a fixture were created for you which is quite convenient!
Apply the migration:
$ rails db:migrate
Note, however, that we must also prepare the testing database in order to successfully run tests. Therefore, also run:
$ rails db:test:prepare
This will, of course, create a db/test.sqlite3 for us. Later when more migrations should be applied, simply run rails db:migrate RAILS_ENV=test. If your schema drastically changes, run rails db:test:prepare again.
Now add a very simple validation checking that the post’s body is present:
# models/post.rb # ... validates :body, presence: true
Of course we would like to test this validation, so open up the test/models/post_test.rb file (note, by the way, that the filename should end with _test postfix). You’ll see the following contents:
require 'test_helper' class PostTest < ActiveSupport::TestCase # test "the truth" do # assert true # end end
That’s, basically, how your tests should look like. Let’s make sure that a post with a some contents is valid:
class PostTest < ActiveSupport::TestCase test 'post with a body should be valid' do post = Post.new body: 'test' assert post.valid? end end
The assert method checks that the provided value is truthy — that is, not false or nil. If it is not the case, the test will fail.
To run your testing suite, execute the following command:
$ rails test
To run only model tests, type:
$ rails test:models
You will see the following output now:
Our test is rocket fast (it required only 0.12 seconds to finish). This small green dot means that we have only one test example and it succeeds. If we had a failing test, you would get the following:
The error message explains what exactly has gone wrong and will differ from test to test.
Also note that the Simplecov has done its job and produced an HTML report for us. Let’s open it now:
Here we can see that only 17% of the code is covered with tests which is not particularly great. On the other hand, we have mostly some generic code and the Post model is 100% tested so that’s okay. You may add yet another test to make sure that the post is not valid without a body:
# test/models/post_test.rb # ... test 'post should not be valid without a body' do post = Post.new body: '' refute post.valid? end
refute is a method opposite to the assert — it checks that the given value is falsy.
We can refactor our tests a bit to avoid instantiating the post twice. To achieve this, employ the setup method that will be executed automatically before the tests are run:
# test/models/post_test.rb # ... class PostTest < ActiveSupport::TestCase def setup @post = Post.new end test 'post with a body should be valid' do @post.body = 'test' assert @post.valid? end test 'post should not be valid without a body' do refute @post.valid? end end
One last thing that I wanted to note here is that the tests are executed in a random order, so one test should not rely on another one — that is, they should be isolated. If for some reason you really need your tests to run one after another, you may set an option with a funny name i_suck_and_my_tests_are_order_dependent!. As this option implies, the tests should not really be order-dependent.
Our First System Test
Before wrapping up, let’s also make sure that our system tests are run properly. To do this, we’ll need a controller, a view and some routes. Create them now:
# posts_controller.rb class PostsController < ApplicationController def index @posts = Post.all end def new @post = Post.new end def create @post = Post.new post_params if @post.save redirect_to posts_path else render :new end end private def post_params params.require(:post).permit(:body) end end
The new view:
<h1>New Post</h1> <%= form_with model: @post do |f| %> <%= f.label :body %> <%= f.text_area :body %> <%= f.submit %> <% end %>
The index view:
<h1>Posts</h1> <%= render @posts %>
The _post partial:
<%= post.body %> <hr>
And, lastly, the routes:
# ... resources :posts, only: [:new, :create, :index] root 'posts#index'
If you are fond of extreme programming, then you may firstly craft a test and only after that write some code that makes this test pass. This is an interesting approach yet it may seem strange to novice developers.
Okay, now let’s add our system test:
# test/system/posts_test.rb require "application_system_test_case" class PostsTest < ApplicationSystemTestCase test "creating new post" do visit new_post_url assert_selector "h1", text: "New Post" body = 'This is my test post' fill_in "post[body]", with: body click_on "Create Post" assert_text body end end
This test is quite simple and the following actions happen:
• We visit the “New Post” page
• We check that the header contains the “New Post” content
• We then fill the form and click on the “Create Post” button. Note that because we are using the new form_with helper,
• IDs are not being added to the fields automatically, so I am providing the name of the field to fill in
• Lastly we check that the page contains the body of the post
visit, fill_in and click_on are methods provided by Capybara for us, but we won’t discuss them thoroughly in this article.
After you are done, run the following command:
$ rails test:system
It will take a bit more time to complete (a couple of seconds at least).
Note that our test coverage has significantly improved!
Watch out How to Configure Minitest In Rail 5 Application Live below: –
In this article we have seen how to configure Minitest in Rails 5 application. We have discussed some available settings, third-party solutions that you may utilize to power up testing suite, and written a couple of tests to make sure everything is working correctly.
In the upcoming articles on the series we discuss how to test various parts of your applications and see Minitest in action. If you would like to learn more about Test-Driven Development and using Minitest with Rails, I recommend referring to our course “Beginners Guide to Test Driven Development” which spans more than 4 hours and provides lots of useful information as well as practical examples.
I thank you for staying with me and see you soon!