A big part of the Ruby philosophy is testing. I think it’s hard to argue against the value of having a suite of automated tests that can run against your codebase to catch regressions.

Test Driven Development is the process of writing tests to drive the implementation. By writing the test first you incrementally get to the solution. This means you end up with better tests that fully cover the functionality you just wrote without the biases you might inadvertently introduce if you wrote the test after the implementation.

A couple of weeks ago we looked at MiniTest. I prefer to use MiniTest because it’s really straightforward.

In today’s tutorial we will be looking at doing TDD for Active Record Models with MiniTest.

Generate what we need

First we need to create a new Rails project. If you haven’t already got a Rails project to work with take a look at Getting started with Ruby on Rails.

Next we need to create the model class, migration and test file

bin/rails g model Article title:string slug:string published_at:datetime  
[/bash]  
I’m keeping this fairly simple for now, a blog article isn’t much use without a body, but we can cross that bridge another day.

You will also notice that I’m generating the model class before writing the failing test. I think it’s fine to allow Rails to generate what we need before writing the first test.

Rails will automatically generate the files we are going to need including the model class, a test class, and a database migration. I’m going to make a quick adjustment to the migration file:  
```ruby  
class CreateArticles < ActiveRecord::Migration  
def change  
create_table :articles do |t|  
t.string :title, null: false, index: true  
t.string :slug, null: false, index: true  
t.datetime :published_at

t.timestamps null: false  
end  
end  
end  

Here I’m adding a couple of options to the title and slug columns to ensure at a database level that they are required and unique. You don’t have to do this as we’ll be enforcing this at the application level.

Now you can set up the database by running the following command in terminal:

bin/rake db:setup  
[/bash]


## Writing the first test

When we used the Rails generator to create the model class, Rails also created a matching test class. You can find this class under the `test/models` directory called `article_test.rb`:  
```ruby  
require "test_helper"

class ArticleTest < ActiveSupport::TestCase

end  

To make writing our tests easier I’m going to add a setup method that will automatically create a new Article instance before each test:

def setup  
@article = Article.new  
end  

The first test I will write will be to ensure that the title is required. We have specified this is the case at the database level, but the model is currently not enforcing this rule:

def test_title_is_required  
@article.valid?  
assert_includes(@article.errors[:title], "can’t be blank")  
end  

In this test I’m checking to see if the @article is valid, and then asserting that the title errors has the specific error I’m looking for.

If you run this test now you will see it fail. With a failing test in place we can now write the code to make it work!

In the article.rb model class, add the following validation definition:

class Article < ActiveRecord::Base  
validates :title, presence: true  
end  

Now if you run that test again it should pass.

We now need to ensure that the slug field is also required. We could do this by duplicating the test method, and I’m not totally against that, but there is a better way to test this type of functionality.

Instead of writing out these boilerplate tests, we can use a gem called Shoulda to make this a lot easier.

To install Shoulda, open your Gemfile and add the following:

group :test do  
gem "shoulda"  
end  

Now run the following command from terminal to install the new gem:

bundle install  
[/bash]

Now we can simply write the presence tests like this:  
```ruby  
class ArticleTest < ActiveSupport::TestCase  
should validate_presence_of(:title)  
should validate_presence_of(:slug)  
end  

If you run your tests again you should see the second test fail. To fix this we can add the same validation rule for the slug attribute to the Article class:

class Article < ActiveRecord::Base  
validates :title, presence: true  
validates :slug, presence: true  
end  

Validating for uniqueness

Shoulda also has an assertion helper for asserting for uniqueness. However, because we have required fields on this model, it’s a bit of a pain in the arse to make it work.

Shoulda is supposed to make our lives easier, not harder, so in this case we can just write out the method:

def test_title_and_slug_should_be_unique  
@article.title = "Hello World"  
@article.slug = "hello-world"  
@article.valid?

assert_includes(@article.errors[:title], "has already been taken")  
assert_includes(@article.errors[:slug], "has already been taken")  
end  

If you run this test you should see it fail!

The first thing I’m going to do is to open up the articles.yml file under the fixtures directory. The Rails generator also generated this file when we ran the command earlier.

I’m just going to delete the contents of the file and replace it with:
[yaml]
hello-world:
title: "Hello World"
slug: "hello-world"
[/yaml]

This will setup the article and make it available for each test.

Next I can add the following definition to both the title and slug attributes of the model:

uniqueness: true  

If you run the tests again now, you should see them pass.

Validating the format of a field

Next we need to ensure that the format of the slug is correct. Here is the test:

def test_slug_should_be_correct_format  
@article.slug = "All Of Your Base"  
@article.valid?  
assert_includes(@article.errors[:slug], "is not a valid slug")

@article.slug = "All-Of-Your-Base"  
@article.valid?  
assert_includes(@article.errors[:slug], "is not a valid slug")

@article.slug = "all-of-your-base"  
@article.valid?  
assert_empty(@article.errors[:slug])  
end  

Some testing purists will say you should only have one assertion per test, but I think this is fine to combine it into one test. If you run this test, you should see it fail.

To make this test pass we can add the following validation to the slug property on the Article class:

format: { with: /\A[a-z0-9]+(?:-[a-z0-9]+)*\Z/ }  

Now if you run the tests again you should see them pass.

Ensuring that slugs are the correct format is something that I’m going to want to do across multiple models in this application. So, whilst this is a premature abstraction, I can create a custom validator to do that.

First create a new directory under app called validators. Rails will automatically pick up this new directory.

Next create a new file under the validators directory called slug_validator.rb:

class SlugValidator < ActiveModel::EachValidator  
def validate_each(record, attribute, value)  
unless value =~ /\A[a-z0-9]+(?:-[a-z0-9]+)*\Z/  
record.errors[attribute] << (options[:message] || "is not a valid slug")  
end  
end  
end  

Now you can replace the initial format validation with:

slug: true  

If you run your tests again you should see that they still pass.

Creating custom methods

Finally I want to be able to check to see if a model object has been published. Here is the test:

def test_is_published  
assert_not(@article.published?)

@article.published_at = DateTime.now

assert(@article.published?)  
end  

First I check to make sure the article is not published. Next I set the published_at attribute. Finally I assert that the article is published.

Once again, if you run this test you should see it fail. To make the test pass we can add the following method to the Article class:

def published?  
! published_at.nil?  
end  

Now if you run the tests again you should see them all pass!

Conclusion

TDD isn’t very difficult, it’s just a process you need to get your head around.

It can be tempting to just skip doing TDD when you are pushed for time.

But I often find that it’s actually a lot quicker to just do TDD because you get your code working with less problems.

In today’s tutorial we’ve done some basic TDD to flesh out the business logic of the Article model.

In the coming weeks we will be using TDD to test some more advanced scenarios and functionality.