Loading...
New webinar: "The Remote Job Search: My Microverse Journey" with graduate Paul Rail
Watch Now

As developers, tackling complex problems in bits and modules is common practice. Tests performed in the different parts of our application, in isolation, are called unit testing. These are usually done on the smallest part of the application.

By the end of this article, you should be comfortable working with Model and Helper Specs.

Working With Rails Model Specs

In my opinion, model specs are the most important test you can run in your application. They are easy to work with, mostly because, unlike controllers there is no request/response cycle to worry about - there is nothing like rendering a template.

As a beginner, model specs are the easiest to take on, and add a lot of value. They are fairly straight forward, but I’m going to share a few pointers that will help you get past the most common stumbling blocks:

  • There may be times you need to #reload after making changes in the database to ensure you’re working with a fresh copy of the database. This won’t happen all the time, so you certainly shouldn’t overuse it.
  • Get comfortable in understanding the difference between the lazy-executing #let versus eagerly-executing #let!. The first doesn’t actually get executed until it’s called, while #let! forces it to happen right away.
  • Another thing people often get confused about is how to write tests for Scopes. Scopes:match_array works with ActiveRecord::Relations.

describe 'Users' do 
let!(:users) do   
	[User.create, User.create, User.create] 
 end 
 it 'uses match_array to match a scope' do   
 		expect(User.all).to match_array(users) 
  end
 end

How to Test Associations & Validations

Some complex code can be written to allow you to poke around in Rails internals, in order to figure out your test expectations. But a far easier way to do this is to use a popular third-party library by thoughtbot called shoulda-matchers. This library gives you a lot of useful matchers to play with. For example; have_many, validate_presence_of.

Did you notice a slight difference there? It says “have_many” not “has_many” and that's because it’s a matcher meant to be used with an expectation. So we would expect users to “have_many” posts. In the definition we say, has_many :posts but in our expectations, we say — we expect users to have_many orders. The same applies to validation.

Configuring shoulda-matchers is not so challenging as they have very good documentation. To start, add the shoulda-matcher gem to your gem file.


group :test do
	gem 'rails-controller-testing' # If you are using Rails 5.x 
  gem 'rspec' 
  gem 'shoulda-matchers', '4.0.0.rc1'
end

Place the code below at the bottom of spec/rails_helper.rb 


Shoulda::Matchers.configure do |config| 
config.integrate do |with|   
	with.test_framework :rspec   
  with.library :rails 
 end
end

I encourage you to take a look at the shoulda-matchers documentation, there are great examples there that will guide you in writing yours. 

The examples below will give you better insights on using shoulda-matchers for both your validations and association testing.

Validations


validates_uniqueness_of :username, case_sensitive: false, message: ‘Username already taken.’
validates_presence_of :username, message: ‘Username cannot be blank’
validates_presence_of :fullname, message: ‘FullName cannot be blank’
validates :username, length: { minimum: 3, maximum: 10,
  			 too_long: ‘Maximum allowed username is 10 characters.’,
         too_short: ‘Minimum allowed characters for username is 3’ }
validates :fullname, length: { minimum: 6, maximum: 20,
         too_long: ‘Maximum allowed fullname is 20 characters.’,
         too_short: ‘Minimum allowed characters for fullname is 6’ }
Tests for Validations Above

describe ‘Validations’ do
it do
   should validate_presence_of(:username).with_message(‘Username cannot be blank’)
end
it do
  should validate_length_of(:username).is_at_most(10)
       .with_message(‘Maximum allowed username is 10 characters.’)
end
it { should_not validate_length_of(:username).is_at_least(2) }
it { should validate_uniqueness_of(:username).case_insensitive.with_message(‘Username already taken.’) }
end


Association Tests


describe ‘Associations’ do
it { should have_many(:opinions).with_foreign_key(:author_id) }
it { should have_many(:follows).through(:followings) }
end

Working with ActiveRecord Test Doubles

Another useful tool for working with ActiveRecord test doubles  is  rspec-activemodel-mocks. You can find the documentation here.

It used to be part of the official RSpec but moved out to be a separate gem. This tool gives us two additional methods to use: mock-model and stub-model.

The mock-model is used in order to create a double of an ActiveRecord object. What's unique about it is that it already has stubbed most of the methods that are included in the ActiveRecord object, like save, create, and build, for you.

You can use ‘stub-model’ to stub certain behaviors. ActiveRecord test doubles become most useful when you are trying to create something that acts like an ActiveRecord object but doesn’t hit the database. You can check out more in the documentation.

Using these tips and techniques you should be able to write specs for all of your Rails app models.

Working With Rails Helper Specs

Working with Rails helper specs is almost as easy as working with models. We can work with them in isolation, and there is no request/response cycle for us to worry about. 

However, there is one important feature to note, and an object called helper that can be used in our examples.

Let's start by creating a spec helper file:


$ rails g rspec:helper application

We will define a simple method inside our helpers/application_helper.rb file for demonstration.


module ApplicationHelper
 def interest_val(capital)
   capital * 0.05
 end
end
----------------------------------------------------------
# inside our spec/helpers/application_helper_spec.rb file
...
RSpec.describe ApplicationHelper, type: :helper do
describe "#interest_val" do
  it "returns the interest on a capital" do
    expect(helper.interest_val(1000)).to eq(50)
  end
end
end

In the above example, you’ll notice how we called the method using the helper. Remember helpers are ruby modules, not ruby classes. We could have simply included the module, then had access to all the methods in our spec files.

However, using the helper has two advantages;

  • The helper method also includes all of the rails built-in helpers, which automatically gives us access to any of those.
  • Secondly, and more importantly, we can assign an instance variable that will normally be present whenever any of our helper methods are used inside the view. The example below will give you more insight into what I mean here.

module ApplicationHelper
 def next_page
   (@page || 0) + 1
 end
end
----------------------------------------------------------
# inside our spec/helpers/application_helper_spec.rb file
...
RSpec.describe ApplicationHelper, type: :helper do
describe "#next_page" do
  it "returns @page plus 1" do
    assign(:page, 5)
    expect(helper.next_page).to eq(6)  
  end
end
end

If #next_page method doesn't recognize it, it will initialize 0 and add 1 to it. Accessing the method via the helper object is like using the technique in its proper context, instead of trying to use it abstractly out of context.

For more examples of this, see the helper spec docs.

By now you should feel more comfortable running tests on your model and helper specs. Model specs are like the sine qua non of unit testings, which is an important part of Test Driven Development. Unit testing ensures the smallest testable parts of your application work properly.

In the next article, we’ll dive into understanding controller specs and feature testing using Capybara.

To learn more about Microverse, and joining our supportive community of remote software developers, get started below!

Subscribe to our Newsletter

Get Our Insights in Your Inbox

Career advice, the latest coding trends and languages, and insights on how to land a remote job in tech, straight to your inbox.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.