How I write model tests

by Jason Swett,

I recently read a post on /r/ruby that asked for RSpec model testing best practices. I gave an answer, but also wanted to give some concrete examples, so here we go.

What follows is how I’d write a spec for a new model. I actually took a model from an existing application I wrote and just blew away the code so I could start over from scratch.

Once I got a ways into this post I realized that it was getting pretty long and I still hadn’t gotten very far into the “meat” of how I’d write a model spec. So I intend to write a follow-up post that goes more in-depth.

Starting point

Here’s what my Restaurant model and test look like when they’re empty.

And just so you can see what the Restaurant class’s attributes are, here’s a snippet of db/schema.rb. Most of these attributes won’t come into the picture. We’ll mostly just deal with name and phone.

The first spec

To me the most natural thing to test is the presence of the restaurant name. I’ll write a failing test for presence of name using Should Matchers.

When I run this spec, it fails, as I expect.

I add the name validator to make the spec pass.

And indeed, the spec now passes.

Spec for phone presence

Now that name presence is taken care of I turn my attention to phone. The spec in this case is the exact same.

I run the spec and it fails. I then add a presence validator:

The spec passes now.

Phone number format validity

Unlike restaurant name, which could be pretty much anything, the phone number has to have a valid format. For example, “123” of course isn’t a valid phone number. I add a failing test for this case. I’m actually not sure what I expect the error message to be, so I just put “invalid format”. After I run the spec I can update the error message in my test to match the actual error message.

As I expect, this test fails.

Now I add a format validator using a regex I found on the internet.

The spec now fails because the expected error message, “invalid format” doesn’t match the actual error message, “is invalid”.

So I update my expected error message.

And now the spec passes.

Tests for other phone cases

By adding this phone regex I’ve actually broken a rule of TDD: only write enough code to make the test pass. If you only write enough code to make the test pass, you know that all the code you’ve written is covered by tests.

In this case I “wrote some code” (copy/pasted a regex from Stack Overflow) that didn’t have tests. So I’m going to go back and add more test cases. One case will say, “when the phone number is valid, the restaurant object should be valid”. The other will say “when the phone number is all numbers, the restaurant object should not be valid”.

These tests all pass. There’s a little duplication in my test, though, so I’m going to refactor.

All the tests still pass.

To be continued

What I’ve written in the post is representative of the start of what I’d put in a model test but it’s certainly not the whole thing. What if my model contains some non-trivial methods? This post is already getting kind of long, so I plan to continue this in a Part 2.


Leave a Reply

Your email address will not be published. Required fields are marked *