Railroad tracks at sunset

Welcome back to Riding Rails, this is Part 2. In this installment, we’ll talk more about models and introduce testing. In case you missed it, check out Part 1 as this builds on what we started.

We left off last time learning about migrations. We just scratched the surface with models and used the Rails model generator to create it and supporting files.

Testing Within Rails

Now that you know about migrations, we’ll get back to working with our model. We’re going to use test-driven development to work on our model, and because you’re a Ruby developer now, we test our code.

By default, Rails is set up for Test Driven Development (TDD), not the common alternative Behavior Driven Development (BDD). Tests in Rails inherit from ActiveSupport::TestCase, which in turn inherits from Minitest::Test. Minitest also has a Minitest::Spec option available, but to use BDD syntax like describe and it in Rails, we would have to install a gem. If we were going to do that, I would have installed RSpec (my preferred spec library) before generating the files. If we were using RSpec, then we would see *_spec.rb files instead of *_test.rb files. It’s not a big deal to install the gem, but I’m trying to keep extraneous installs to a minimum to avoid confusion. Thus we’ll use TDD. If you aren’t familiar with BDD, there are specs in place of tests. So I apologize in advance if I call something a “spec” instead of a “test!”

If we were using Minitest::Test directly, we would have to write tests with underscores for the method name like so:

def test_jobs_title_attribute

end

With ActiveSupport::TestCase running on top of Minitest::Test, we can write the same test like this:

test "jobs title attribute"

end

The key takeaway is that with ActiveSupport::TestCase we can use the test method and pass it a string, rather than being forced to provide a Ruby “test class friendly” name. This will allow us to be more descriptive with our test case names.

Testing Models

Open up test/models/job_test.rb, and let’s write our first test.

You’ll notice that in job_test.rb there is a class defined as we saw in the migration. There is also an include and some comments in the class. Go ahead and delete the comments (the lines that start with a #). That leaves us with the class definition and the include. We’ll use this included file (test_helper.rb) in all of our tests. So if we need to write any custom logic to be commonly shared across all of our tests, it can go in here.

Now let’s write our first failing test. We’re going to assert one requirement per test. This is something I learned long ago by reading “The Art of Unit Testing” by Roy Osherove. If you are not familiar with the concepts of writing unit tests, such as what “assertions” are and the important role they play in unit testing, I highly recommend reading that book. Let’s start with validations. We want to ensure that the Job object has a title, otherwise it should not be allowed to save. You’ll see in the test below that we create a new Job object without a title, and then tell the test method, using assert_not, that we expect the job to NOT be saved.

  test "should not save job without a title" do
    job = Job.new(title: nil)
    assert_not job.save
  end

Now that we have our test, let’s run it with the command rails test. You’ll see it fails as expected:

F

Failure:
JobTest#test_should_not_save_job_without_a_title [../job_slayer/test/models/job_test.rb:6]:
Expected true to be nil or false

The F indicates a failure, and you can see the message Expected true to be nil or false. That’s not very specific. Let’s fix that by passing a message to the assert_not method:

  test "should not save job without a title" do
    job = Job.new(title: nil)
    assert_not job.save, 'Saved the job without a title'
  end

Now, when you run the test again, we have a more meaningful failure message:

Failure:
..
Saved the job without a title

With the failing test, we’ll want to fix it by adding validations to the Job class. Open up models/job.rb and fix it by matching the class below:

class Job < ApplicationRecord
  validates :title, presence: true
end

Are you surprised by the Job class? Where are our attributes we defined in the last part? The answer lies in app/models/application_record.rb, see how it inherits from ActiveRecord::Base? Well, ActiveRecord, Rails’ default ORM (object-relational mapping), does the mapping from the database for us. Seems almost magical, doesn’t it? Still curious? Read more about ActiveRecord in the docs.

Re-run the test, and now it passes. Let’s do another! We want to make sure more fields are required. Let’s target the metadata that we are storing about a job. In your same test file, underneath the previous test we just made, add the new test code as shown here:

  test "should not save job without a source" do
    job = Job.new(source: nil)
    assert_not job.save, 'Saved the job without a source'
  end

Now that you are familiar with the syntax from our first test, you should be able to see that this second test expects that the source attribute needs to have a value in order for the Job to save successfully. Since we are providing a null value to source this test should fail. Run the failing test using rails test and… wait… what? It passed?! Hmm, that’s not right. Do you see the problem? We are specifying source: nil, but the now required title isn’t set, so THAT is causing our Job to not save even though we haven’t added validations to the source attribute yet. I guess we can add the title with a value to the hash we are sending to Job.new, but that’s going to get old fast when we add more validation of attributes. “There must be a better way,” I can hear you exclaiming. You are right!

Fixtures

There are a few ways to tackle this problem that we have. Rails provides us with Fixtures, which is just a fancy word for “sample data.” We could use these Fixtures in our tests. We could also create a hash of all the attributes needed to create a model and change just the attributes we are interested in for each specification. A third option, which I personally use, is a gem called FactoryBot (previously known as FactoryGirl). That said, let’s use what we have instead of introducing another gem. Plus Factories vs. Fixtures is an another conversation entirely. Let’s go the Fixture route since Rails has already set it up for us. If you hop over into test/fixtures/jobs.yml, you’ll see some basic code that Rails created for us. Let’s use the attributes it provides to save typing. Change the file to look like this:

one:
  title: Senior Software Developer
  posted_at: 2018-02-20 13:01:47
  location: Remote
  type: Contract
  description: MyText
  how_to_apply: Apply here: https://visoftinc.com
  company: Visoft, Inc.
  company_url: https://visoftinc.com
  company_logo: https://images.visoftinc.com/logos/visoftinc.png
  source: https://jobs.github.com
  source_url: https://jobs.github.com/positions/1234567890

If you look at test/test_helper.rb, you’ll notice that it is already set up to load all of our fixtures before each test:

  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
  fixtures :all

With that in place, let’s change our two tests to the following:

class JobTest < ActiveSupport::TestCase
  test "should not save job without a title" do
    job = jobs(:one)
    job.title = nil
    assert_not job.save, 'Saved the job without a title'
  end

  test "should not save job without a source" do
    job = jobs(:one)
    job.source = nil
    assert_not job.save, 'Saved the job without a source'
  end
end

As you can see, we are initially loading up our test data from the Fixture, and then nullifying the field that we want to test. Rerun the tests using the command rails test, and uh oh!

An Error

Since we’re going along with actual development, this could happen. Let’s take a closer look at the error:

E

Error:
JobTest#test_should_not_save_job_without_a_title:
ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 
'Contract'. This error is raised because the column 'type' is reserved for storing the class in case of 
inheritance. Please rename this column if you didn't intend it to be used for storing the inheritance class 
or overwrite Job.inheritance_column to use another column for that information.
    test/models/job_test.rb:5:in `block in <class:JobTest>'

It looks like we used a reserved word in ActiveRecord, type in our job model. Now, we could go and change this, but job.type sounds a lot better than job.job_type or something. ActiveRecord reserves the word type for doing single table inheritance (STI). We can change this, telling ActiveRecord that we’re not going to be using STI with our job model. Open the model file (app/models/job.rb) and inside the class add the following line:

self.inheritance_column = :_type_disabled # disable STI

Save, and re-run the tests. The error goes away, and we’re left with what we expected back when we started this second test, a failure. Let’s fix that the same as we did with the first test we wrote; you’re job model class should now look like this:

class Job < ApplicationRecord
  self.inheritance_column = :_type_disabled # disable STI
  validates :title, presence: true
  validates :source, presence: true
end

Run the tests again (rails test) and voilà, success!

Other Validation Helpers

Now that you know about the presence validator, we can add this to other attributes. But what about other types of validation in Rails? There are a ton of other built-in helpers available. Let’s say we want to make sure that the type attribute must contain only certain values like “Full Time”, “Part Time”, and “Contract”. To add such a validation we can utilize the inclusion validator which lets us specify a set of valid values for the attribute. Let’s test that out now. Remember we are using TDD so let’s write the test first. Add the following to your test file below the second test we just finished.

  test "should not save job without a valid type" do
    job = jobs(:one)
    job.type = 'Seasonal'
    assert_not job.save, 'Saved the job without a vaild type'
  end

You know the drill by now, run the test making sure it fails. Now let’s fix it, add this to your job model:

  validates :type, inclusion: { in: ['Contract', 'Part Time', 'Full Time']}

Re-test, and it works!

Save, Test, Rinse, Repeat

We’ve repeated saving and running the tests a bunch of times now. You’re probably a bit annoyed having to repeat these steps over and over. The good news is that there is a better way. It involves installing a gem (well 2), but that’s a simple task in Rails thanks to Bundler. I thought about omitting this since it’s a separate install, but this isn’t new syntax that you need to learn or anything. It’s a merely an automated test runner. It will make your development process much better.

Open up your Gemfile and add we will add two gems in the group :development, :test, like so:

group :development, :test do
  ...
  gem 'guard'
  gem 'guard-minitest'
end

Now, from a terminal window aimed at your project root, run the command below to install the two new gems we just added:

bundle install

Once that’s completed, initialize guard-minitest from the same terminal with this command:

guard init minitest

This creates a Guardfile telling guard what to run. Open the Guardfile in the root of your project and uncomment the section that starts with # Rails 4. Finally, where it says guard :minitest do change that to read guard :minitest, spring: "bin/rails test" do. If done correctly your Guardfile should look like this:

guard :minitest, spring: "bin/rails test" do
  # with Minitest::Unit
  watch(%r{^test/(.*)\/?test_(.*)\.rb$})
  watch(%r{^lib/(.*/)?([^/]+)\.rb$})     { |m| "test/#{m[1]}test_#{m[2]}.rb" }
  watch(%r{^test/test_helper\.rb$})      { 'test' }

  ...
  # Rails 4
  watch(%r{^app/(.+)\.rb$})                               { |m| "test/#{m[1]}_test.rb" }
  watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
  watch(%r{^app/controllers/(.+)_controller\.rb$})        { |m| "test/integration/#{m[1]}_test.rb" }
  watch(%r{^app/views/(.+)_mailer/.+})                    { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
  watch(%r{^lib/(.+)\.rb$})                               { |m| "test/lib/#{m[1]}_test.rb" }
  watch(%r{^test/.+_test\.rb$})
  watch(%r{^test/test_helper\.rb$}) { 'test' }

  ...
end

Open up a new Terminal tab/window in the same application root folder. Then fire up guard by just typing guard. If it all worked correctly, you should see 3 runs, 3 assertions, 0 failures, 0 errors, 0 skips. Every time we save from now on, rails test will be run for us. Much easier!

Trying Out Guard

Since this is the first time you’re using Guard, let’s make sure it’s working as expected. Add a new test inside job_test.rb. You are familiar with the process so go ahead and make sure that the source_url is provided, and here’s something new, make sure that it’s actually a URL. There are various ways to enforce that, and there isn’t a wrong way providing you really test it out. Save the test(s), and Guard will run it. Fix the failure and Guard will jump into action and rerun the test. Continuous feedback from Guard makes TDD/BDD very productive. When you’re done with testing the new requirements, you should have at least five total tests. Remember, one assertion/unit of work per test. I’ll leave it to you to decide how many tests you add depending on the solution you choose. Don’t shy away from any option, this is how we learn.

Homework

In this post, we only validated a few of the attributes in our model. Look at what we have and where it can be improved. Even though users won’t be entering data into the database directly, remember the mantra of “garbage in, garbage out.” We don’t want just anything entered for say the company_url, do we? If you need a refresher, just have a look at Figure 2 under the heading Models. You could potentially require every attribute in our job model. However, this may limit what we’re able to store from other sources. However, if you plan to stick to just one source (GitHub Jobs), then that would probably be beneficial. Don’t forget to test out any validations that you add.

Wrap Up

In this second part of the tutorial, we saw how we could test-drive requirements with models. We learned that Rails comes with Fixtures, what they are, and how they can be used in our tests. After numerous times of saving and running the tests manually, we discovered that Guard is a fantastic tool that will give us constant, automated feedback.

Look out for part 3 of this series, in which we will discuss moving our application forward. With our job model in place, we’ll start using the model when we are querying GitHub Jobs and persisting everything to our database. I hope that you found the information here useful. If you have any questions, please leave a comment below or you can contact us.