During our session, we'll learn how to test model validations.
Clone this app: git clone -b model-testing git@github.com:turingschool-examples/belibery.git
.
- Generate a migration and model for donations (use
rails g model
to get the model and the migration). The migration needs to have an amount and a reference to the fans table. Migrate and look at the schema. - Now, generate a migration that adds a string status column to the donations table. Migrate and look at the schema.
- Open your schema, and rollback. Now try to rollback two steps. What do you see?
- Migrate again to apply your migrations to the database.
From the RailsGuides: "In Rails, models tests are what you write to test your models."
If you use rails g model Thing
, you'll have a model test file available to you within the test/models
folder. This is how the repo was initially set up for Fan and Location, so you already have these two test files.
However, if you need to create a model test by hand: $ touch test/models/thing_test.rb
and add this code inside of it:
require 'test_helper'
class ThingTest < ActiveSupport::TestCase
end
Remember that your generated fixtures will be loaded when you run your tests unless you remove that line in your test_helper.rb.
You can find the Rails Guides validation documentation here.
Let's write a test to check that a fan with all attributes is valid. Inside of test/models/fan_test.rb
:
require 'test_helper'
class FanTest < ActiveSupport::TestCase
def valid_attributes
{
name: "Jorge",
email: "yosoybelieber@example.com",
}
end
test "it creates a fan" do
result = Fan.new(valid_attributes)
assert result.valid?
assert_equal "Jorge", result.name
assert_equal "yosoybelieber@example.com", result.email
end
end
What happens if a name isn't entered? We shouldn't have a valid fan. Let's add a test. Inside of test/models/fan_test.rb
:
test "it cannot create a fan without an name" do
result = Fan.new(email: "yosoybelieber@example.com")
assert result.invalid?
end
This fails because we don't have any validations for presence of a name. Inside of fan.rb
, add:
validates :name, presence: true
Let's assume that a fan logs into Belibery using their email address. Email addresses will need to be unique. Let's add a test:
test "it cannot create a fan with the same email" do
2.times { Fan.create(valid_attributes) }
result = Fan.where(email: "yosoybelieber@example.com")
assert_equal 1, result.count
end
It will fail because it's creating two fans and we're asserting that there should only be one. Inside of fan.rb
we need to add a validation:
validates :email, presence: true, uniqueness: true
Names should only contain capital and lower case letters. Let's write a test:
test "it only accepts letters as a name" do
fan = Fan.create(
name: "Jorge1",
email: "yosoybelieber@example.com"
)
refute fan.valid?
end
We can use regex and a format validator to make this test pass:
validates :name, presence: true,
format: { with: /\A[a-zA-Z]+\z/, message: "only allows uppercase and lowercase letters"}
Let's limit our fans' email addresses to between 5 and 50 characters. Our test:
test "it only accepts an email between 5 to 50 characters" do
fan = Fan.create(
name: "Jorge",
email: "Jorj"
)
assert fan.invalid?
end
We'll use the length validation to make this test pass:
validates :email, presence: true,
uniqueness: true,
length: { in: 5..50 }
What happens if we want to ban all users named Richard? We will need a custom validation method. First, let's write a test:
test "it cannot create a fan named Richard" do
fan = Fan.create(
name: "Richard",
email: "richard@example.com"
)
refute fan.valid?
assert_includes fan.errors.full_messages, "Name cannot be Richard"
end
We can validate :no_richards
with a custom validation:
validate :no_richards
def no_richards
errors.add(:name, "cannot be Richard") if name == "Richard"
end
You can also use ActiveModel::Validator for custom validations.
Examples from RailsGuides:
class Coffee < ActiveRecord::Base
validates :size, inclusion: { in: %w(small medium large),
message: "%{value} is not a valid size" }
end
class Account < ActiveRecord::Base
validates :subdomain, exclusion: { in: %w(www us ca jp),
message: "%{value} is reserved." }
end