A reader of mine recently shared with me a GitHub gist called rspec_model_testing_template.rb. He also said to me, “I would like your opinion on the value of the different tests that are specified in the gist. Which ones are necessary and which ones aren’t?”
In this post I’d like to point out which types are RSpec tests I think are pointless to write.
Testing the presence of associations
Here are some examples from the above gist of association tests:
it { expect(profile).to belong_to(:user) } it { expect(user).to have_one(:profile } it { expect(classroom).to have_many(:students) } it { expect(gallery).to accept_nested_attributes_for(:paintings) } |
Unless I’m crazy, these sorts of tests don’t actually do anything. A test like it { expect(classroom).to have_many(:students) }
verifies that classroom.rb
contains the code has_many :students
but that’s all the value the test provides.
I’ve heard these tests referred to as “tautological tests”. I had to look up that word when I first heard it, so here’s a definition for your convenience: “Tautology is useless restatement, or saying the same thing twice using different words.” That’s exactly what these tests are: a useless restatement.
What does it mean for a classroom to have many students and what sorts of capabilities does the existence of that association give us? Whatever those capabilities are is what we should be testing. For example, maybe we want to calculate the average student GPA per classroom. The ability to do classroom.average_student_gpa
would be a valuable thing to write a test for.
If we test the behaviors that has_many :students
enables, then we don’t have to directly test the :students
association at all because we’re already indirectly testing the association by testing the behaviors that depend on it. For example, our test for the classroom.average_student_gpa
method would break if the line has_many :students
were taken away.
How do you know the difference between a tautological test and a genuinely valuable test? Here’s an analogy. What would you do if you wanted to test the brakes on your bike? Would you visually verify that your bike has brake components attached to it, and therefore logically conclude that your bike has working brakes? No, because that conclusion is logically invalid. The way to test your brakes is to actually try to use your brakes to make your bike stop. In other words, you wouldn’t test for the presence of brakes, you would test for the capability or that your brakes enable.
Testing that a model responds to certain methods
it { expect(factory_instance).to respond_to(:public_method_name) } |
There’s negligible value in simply testing that a model responds to a method. Better to test that that method does the right thing.
Testing for the presence of callbacks
it { expect(user).to callback(:calculate_some_metrics).after(:save) } it { expect(user).to callback(:track_new_user_signup).after(:create) } |
Don’t verify that the callback got called. Verify that you got the result you expected the callback to produce.
Testing for database columns and indexes
it { expect(user).to have_db_column(:political_stance).of_type(:string).with_options(default: 'undecided', null: false) it { expect(user).to have_db_index(:email).unique(:true) |
I had actually never seen this before and didn’t know you could do it. I find it pointless for the same exact reasons that testing associations is pointless. Don’t test that the database has an index, test that the page loads sufficiently quickly.
Tips for writing valuable RSpec tests
Here’s how I tend to write model specs: for every method I create on a model, I try to poke at that method from every possible angle and make sure it returns the desired result.
For example, I recently added a feature in an application that made it impossible to schedule an appointment for a patient who has been inactivated. So I wrote three test cases: one where the patient is active (expect success), one where the patient is inactive (expect an error to get added to the object), and one where the patient was missing altogether (expect a different error on the object).
My methodology for writing feature specs with Capybara is a little more involved. I describe it in detail here.