Maybe you trust your db/seeds.rb
file to be idempotent when you run
bin/rails db:seed,
and maybe you don’t, but either way, you should test it so you can be sure that it does what you think
it does.
Doing so is simple! Let’s run through the process.
Here’s a sample seed file:
- We want it to create users or update them if they already exist
- We want it to create blog posts or return them unchanged if they already exist
# db/seeds.rb
require "factory_bot_rails"
class Seeder
include FactoryBot::Syntax::Methods
def create_or_update_user(name:, is_a_robot:)
if (user = User.find_by(name:))
user.update!(is_a_robot:)
user
else
create :user, is_a_robot:
end
end
def find_or_create_post(name:)
if (post = Post.find_by(name:))
post
else
create :post, name:
end
end
def call
create_or_update_user name: "Louis", is_a_robot: false
create_or_update_user name: "Ralph", is_a_robot: true
find_or_create_post name: "Seeds...of Destruction!"
end
end
Seeder.new.call
Testing this file requires including it in your test helper:
# test/test_helper.rb
require Rails.root.join("db/seeds.rb")
And then we can get into adding test coverage.
# test/seeds_test.rb
require "test_helper"
class SeedsTest < ActiveSupport::TestCase
test "#call does not create duplicate posts when run again" do
Seeder.new.call
from = Post.count
assert_predicate from, :positive?
assert_no_changes -> { Post.count }, from: do
Seeder.new.call
end
end
test "#call does not create duplicate users when run again" do
Seeder.new.call
from = User.count
assert_predicate from, :positive?
assert_no_changes -> { User.count }, from: do
Seeder.new.call
end
end
test "#create_or_update_user creates a new user" do
name = "Test User"
assert_changes -> { User.count }, from: 0, to: 1 do
user = Seeder.new.create_or_update_user(name:)
assert_equal name, user.name
end
end
test "#create_or_update_user updates an existing user" do
user = create :user, is_a_robot: false
assert_no_changes -> { User.count }, from: 1 do
Seeder.new.create_or_update_user(name: user.name, is_a_robot: true)
end
assert_predicate user.reload, :is_a_robot?
end
test "#find_or_create_post creates a new post" do
name = "The Best Post Ever"
assert_changes -> { Post.count }, from: 0, to: 1 do
post = Seeder.new.find_or_create_post(name:)
assert_equal name, post.name
end
end
test "#find_or_create_post returns an existing post" do
post = create :post
assert_no_changes -> { Post.count }, from: 1 do
found_post = Seeder.new.find_or_create_post(name: post.name)
assert_equal post.id, found_post.id
end
end
end
That’s all there is to it! With testing, you can be certain that your seeds will
grow into exactly the plants you mean to grow, and not overrun your garden database with
500 users named “Test”.