Minitest Masterclass (Part 2: Minitest with Rails)

In Part 1, we covered just the basics of Minitest:

how to install it in bare-bones Ruby code,

what the different styles of tests are,

where and how to put things,

how to use a Rakefile.

This lesson will dive into how Minitest and Rails work together. We’ll start by building a Rails app.

2-1/ Using Minitest with Rails

Example App for this section

https://github.com/jfbcodes/MTMC_2-1_MyTested_App

Since Minitest comes by default with Rails, you can get started by making a new Rails app:

rails new MyTestedApp

Next cd MyTestedApp into the new empty app, you just created above.

Let’s add the gems we’ll need in this course. If you are adding testing to an existing app, you can also run this to add the gems we’ll use in this course.

bundle add minitest-rails mocha spy factory_bot_rails ffaker vcr simplecov --group=test

You should now commit your Gemfile and Gemfile.lock files.

Remember, you’ll also need a debugger. Rails comes with ruby-debug, but your other options are pry and byebug.

Next run

bin/rails generate minitest:install .

If Minitest tells you there is a conflict on the following three files, tell it to overwrite those files by entering Y when it asks you to overwrite here:

If you run git status before committing your changes, you’ll see some changes which can be ignored and a change to this file: test/test_helper.rb

Other than the comments, all that has been added is the require "minitest/rails" line

Create a file inside of test/models called the_truth_test.rb

Here you will write your first test: TheTruthTest.

On the first line of every test, always begin each test file with

require "test_helper"

Now let’s write our very first test. It is a test called “the truth test” and it will live in the test/models folder

test/models/the_truth_test.rb

require "test_helper"

class TheTruthTest < ActiveSupport::TestCase
  test "the truth" do
    assert true
  end
end

Now run your test with

bin/rails test

Now, breathe in and breathe out. Feel the power of the green dot. This power is deep within you and promised to you under the great power of code. You will live and die by the green dot; it will be your lifeline, your mantra, your one-and-done.

Now, you are ready to be a mighty Ruby Testing Warrior.

2-2/ Kinds of Rails Tests

In the last section, we create a model test for nothing — for asserting true and only true. It was simply a demonstration of the very heart of Minitest.

Although we put our test in the “models” folder, there was never any formal Rails model (that is, ActiveRecord model with a database table) associated with this test — we just made a basic test case for which the following three things were true

1) was a class that either is or inherits from ActiveSupport::TestCase ,

2) had a method ending with _test, and

3) makes an assertion

Your test folder in your new Rails app will have these default folders:

Model, job, channel tests, and mailer tests are all unit testing.

Integration tests, dispatch tests, and request or controller tests are middleware testing.

System tests, also known as Capybara tests, are end-to-end testing.

Re: middleware tests: The general advice is to avoid heavy middleware testing and prefer unit and system testing. That doesn’t mean you shouldn’t ever use middleware test. You should know when you can’t otherwise adequately use unit or system tests to cover what you need to cover or those styles of testing don’t fit. There are plenty of appropriate times to use middleware testing.

In the first 10 chapters, we’ll work only with unit tests.

Let’s discuss two special folders you see here first. These two folders are unique and not like the others.

The Helper folder

The files in helpers are not tests to cover helpers in Rails— they are helper code abstractions for your test suite itself— that is, they are helpers for you to use and reuse in your test code that might be shared between many tests.

The Fixtures Folder

• The folders in fixtures we won’t use in this course. Instead, we prefer factories over fixtures and we’ll install FactoryBot to set up factory data.

(You can delete the fixtures folder now with rm -rf test/fixtures/)

Do all your tests need to fit into the structure pre-defined by your Rails install?

No.

You should match the folder structure in test/ with the same folder structure you have in app/ , and roughly conceptually map a test to the corresponding file in your app code. This file— the app code being tested— is known as the system under test.

Notice that I said “roughly”: You can do a 1-to-1 mapping with model and business objects, but views and UI can be tested by user flow, which is done in System tests.

APIs should be tested with request specs,

So in every place except the system tests, the name of the test file should match the name of the app file that is the test’s target — called the system under test. This term is very important, and we will return to it several times.

In the system test, we test user flows and thus name our files based on user interaction nomenclature.

Model Tests

You create model tests in the test/models/ folder. Model tests descend from the class.

Model tests are fairly easy to write, and all backend code should be tested using TDD. Remember, TDD can be summed up in these simple steps, which you should treat as gospel:

  1. Write a test asserting the smallest unit of work we can do
  2. Run it watch it fail
  3. write only the amount of code needed to make the test pass
  4. run it and watch it pass
  5. refactor

Model tests are typically easy to write, but sometimes trick to reason about. In this course we will use Factories, as previously mentioned, to be the source data for our Rails models.

Remember, a “model” doesn’t need to be an ActiveRecord object necessarily – it’s perfectly valid to have objects there POROs — Plain Old Ruby Object — that exist in the app/models/ folder.  You will have a corresponding test in the test/models/ folder.

Sometimes when written poorly, model tests can be brittle— or fail when unexpected changes are made.

Other times, model tests might be flaky — either fail unexpectedly between runs when nothing has changed or fail unexpectedly on the CI system but not locally.

We spend much time discussing flaky and brittle in this course in depth. We’ll discuss flaky tests as a symptom of race conditions in many lessons, but Lesson 14 is dedicated explicitly to flaky tests. As well, Lessons 17, 18, 19, 20, and 21 are a deep dive into race conditions, the root of all flaky tests. Lesson 15 covers brittle tests.

As well, Lesson 16 is devoted to failing unexpectedly on CI but not locally. For now, remember these are the kinds of problems you might deal with in model tests.

Integration Tests

Integration tests are an important part of understanding testing. Integration tests are when you test several classes or modules together— specifically, a part of the application. What’s a little tricky about integration testing is trying not to overuse it: You want to be clear about which modules or classes are part of the system under test, and which aren’t. They aren’t necessarily for beginners, so we’ll cover them only by using them for some examples when discussing other things.

Channel Tests

Chennel tests are for covering your action cable channels. These are the connections between server and client that are kept alive so that the server can communicate with the client in realtime. This enables you to create advanced, realtime multiplayer apps using Rails. This technology is built into Rails (but requires you to enable it) and is called Action Cable.

You create channel tests in the test/chanenls/ folder.

Channel tests or action cable tests. You should unit test your channels as best as possible, but channels are fundamentally middleware, so they should not be focused on as heavily as the end-to-end testing. In Rails Minitest, they are tested as-if they are units, which is why they are grouped in with unit testings.

ActionCable::Channel::TestCase

ActionCable::Connection::TestCase

ActionCable::TestCase

Mailer Tests

Mailer tests are in test/mailers/. These tests test your mailer objects and output and make sure that your outgoing mail is tested and has no parse errors.

Mailer tests for testing your outgoing mail. You absolutely should test your outgoing mail, mostly because it’s very easy to introduce a parse error in your mail templates and you want a safeguard against it.

System Tests

System tests — or Capybara tests — are in the test/system/ folder. Capybara is used for loading a ‘simulation’ of your UI in a controller browser. The test suite automatically loads your website in this browser (“headless” if it is unseen), clicks around, and interacts with the page. Assertions are made against the state in the UI to confirm that the user’s experience works as expected. These are known as system tests or Capybara tests. They like gold — the most valuable kind of testing but also the most laborious. They require attention to detail while writing and can be difficult to work on. They are prone to race conditions, where one thing is racing against another, and as a result, something fails in your app’s test suite. We’ll cover Capybara system tests in Chapter 11.

Job Tests

obs tests are for testing jobs. (Jobs are found in the app/jobs folder and are designed to be background processes that your app will run asynchronously.)

Dispatch Testing

Dispatch testing is used for testing routes and paths in your app. Generally, this is “middleware testing.” overuse of middleware testing is not recommended. It will not be covered in this course.

Request tests and Controller tests

Request tests and controller tests are legacy. They are only recommended if you can’t otherwise test the UI. If you can test the UI using system specs, do that instead, and do not test your controllers directly. We will not cover them in this course.

2-4/ Examples for Model, Mailer, Job, and Channel Tests

Although we just gave you an overview of many tests, we will finish this chapter by showing concrete examples of Model, Mailer, Job, and Channel tests.

Integrating testing will be employed in more advanced lessons when we show how to test several components of an app together.

Remember, integration testing is a form of middleware testing, so it is only preferred whenever it makes more sense to use middleware testing than unit testing or system testing. For example, when unit testing or system testing is prohibitively difficult. It isn’t wrong or right to use middleware testing— it’s a matter of what is most effective, but your orientation should start with unit testing and see how far that gets you.

I, personally, sometimes start with integration testing (even though it is middleware), but at other times I start with unit testing. It depends on the problem, and this course will hopefully give you some good tools to learn when one is appropriate and the other is appropriate. Suffice it to say, it’s ok not to know up-front, and it’s ok to try out either unit testing or integration testing as a first testing strategy based on your gut, then adjust if you change your mind when you have more information.

System tests will be introduced in Lesson 11. Finally, dispatch testing and request/controller tests will not be covered in this course. (Although there are perfectly valid reasons to use dispatch and request/controller testing, it is not recommended as a place to start, so we’ll skip it to focus only on the important stuff.)

All four of the below kinds of testing can be considered different forms of “unit testing.” (Except for channel testing, which blurs the line as discussed above.)

Model Test Example

Mailer Test Example

Job Test Example

Channel Test Example