Seeds...of Destruction! 🌱 ➑️ πŸ’₯

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”.