Super Spread Sheet S³

Or little computing tricks and hacks

Testing with RSpec in Rails, Part 2, Models

(Edited 26 February 2015, added information about accessing database details.)

Following from the post Testing with RSpec in Rails, Part 1, Introduction, it is now time to expand on testing models based on my experience.

I considered three types of test relevant for my application:

  • Factory tests
  • Data fields validations
  • Associations between models

Factory test

The first test is to make sure that a valid record can be created safely and respecting all of the constraints. In other words, that is has a valid factory.

A factory is an object for creating other objects – formally a factory is simply an object that returns an object from some method call, which is assumed to be “new”.

https://en.wikipedia.org/wiki/Factory_%28object-oriented_programming%29#cite_ref-1

In my opinion, this is not a TDD type test, rather, it follows the database design. Therefore, the table might exist already. This is what I am assuming for the rest of this post.

If the table exists, we need tools to find the table structure in the development database. There are two easy ways that I know of using the rails command. The first is using the console

rails c

short for

rails console

The command

$ rails c
Loading development environment (Rails 4.0.4)
2.0.0-p451 :001 > ActiveRecord::Base.connection.tables
 => ["schema_migrations", "users", ...]

will output an array including all of the current tables. And the command User.column_names

2.0.0-p451 :008 > User.column_names
 => ["id", "name", "email", ...]

will output an array including the column names of the table User.

The second way of accessing this information is by using the command

rails db

short for

rails dbconsole

It starts a console for the database and database adapter specified in config/database.yml depending on the current Rails environment. If testing, one is most likely in the development environment.

$ rails db
Password:
psql (9.4.1, server 9.3.5)
Type "help" for help.

App_Name_development=#

To retrieve the fields of the articles table, I used the following command:

=# select column_name from information_schema.columns where table_name='articles';

The output is:

 column_name 
-------------
 id
 name
 bio
 image
 created_at
 updated_at
(6 rows)

The command

App_Name_developemnt=# \d

will display a list of all the relations.

With this information and with the information in the model.rb file, the factory can be written for the model.

I used the faker gem to create random names and descriptions (https://github.com/stympy/faker). Given that authors write articles, I also have a factory for articles. The factories look like this:

# spec/factories.rb
require 'faker'

FactoryGirl.define do

  factory :author do
    name Faker::Name.name
    bio { Faker::Lorem.sentences.to_s }
    image { Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec',
      'support', 'images', 'image_2.jpg')) }
  end

  factory :article do
    author
    title "The life of Pepito Perez"
    summary { Faker::Lorem.sentence }
  end
end

The image field has been created with the carrierwave gem and that is the code needed in the testing environment. The file image_2.jpg must exist in the spec/support/images/ directory.

I did not know what was best practice between having one file called spec/factories.rb or having a file model_name.rb in the spec/factories directory. My searches did not deliver any conclusive results. So I used trial and error. I first opted for one file, then a mix, and then multiple files in the directory, as the number of factories grew, one per model. This is in-line with Rails practice of always having small files.

The actual test to validate the factory is written in file spec/models/article_spec.rb:

require 'rails_helper'

describe Article do
  # Validation tests
  it "has a valid factory" do
    expect(FactoryGirl.create(:article)).to be_valid
  end
end

The RSpec matcher be_valid verifies that our factory does indeed return a valid object.

Given that I had already created my models, once the test was passing, I changed the code to see the test fail, making sure that specific parts of code were indeed being tested.

Data validations

These tests are straight-forward, validating any of the constraints needed in each field. The model of the article has the constraint that the title is mandatory, as shown below:

# app/models/article.rb
class Article < ActiveRecord::Base
  validates :title, presence: true
...

The following lines, create a object in the test environment, and gives it an empty title, which is not allowed:

require 'rails_helper'

describe Article do
...
  it "is invalid without a title" do
    expect(FactoryGirl.build(:article, title: nil)).not_to be_valid
  end
...
end

Notice in the article_spec.rb file there are two special methods: create and build.
create builds and saves the object, while build only does that. This allows the modification of attributes before saving the object. The not_to verifies that an empty title should not be allowed in a valid object.

Associations between models

An interesting test that I discovered in this post, is testing the associations between models using RSpec. As a beginner, I always want to double check that I made the right associations. For this, I used the gem shoulda found in github.

Add the gem to the Gemfile:

group :test do
...
  gem 'shoulda-matchers'
end

and run bundle.

Then add to the spec/models/article_spec.rb, the following test:

require 'rails_helper'

describe Article do
...

  # Associations test
  it { should belong_to(:author) }
end
Advertisements

3 responses to “Testing with RSpec in Rails, Part 2, Models

  1. Pingback: Testing with RSpec in Rails, Part 1, Introduction | Super Spread Sheet S³

  2. Pingback: Rails: multiple categories for an article | Super Spread Sheet S³

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: