Skip to content

Commit

Permalink
Introduce suspenders:factory_bot generator
Browse files Browse the repository at this point in the history
Maintains functionally with the [existing generator][] while adding
support for the [default Rails test suite]. With this change, the
generator can be invoked on a Rails application that use RSpec and the
default Rails test suite.

Adds generator which adds a test to lint all Factories in an effort to
improve developer experience.

[existing generator]: https://github.com/thoughtbot/suspenders/blob/main/lib/suspenders/generators/factories_generator.rb
[default Rails test suite]: https://guides.rubyonrails.org/testing.html
  • Loading branch information
stevepolitodesign committed Oct 27, 2023
1 parent 44f783e commit ed5e4a6
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 0 deletions.
70 changes: 70 additions & 0 deletions lib/generators/suspenders/factory_bot_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
module Suspenders
module Generators
class FactoryBotGenerator < Rails::Generators::Base
source_root File.expand_path("../../templates/factory_bot", __FILE__)

def add_factory_bot
gem_group :development, :test do
gem "factory_bot_rails"
end

Bundler.with_unbundled_env { run "bundle install" }
end

def set_up_factory_bot
if default_test_helper_present?
insert_into_file Rails.root.join("test/test_helper.rb"), after: "class TestCase" do
"\n include FactoryBot::Syntax::Methods"
end
elsif rspec_test_helper_present?
copy_file "factory_bot_rspec.rb", "spec/support/factory_bot.rb"
insert_into_file Rails.root.join("spec/rails_helper.rb") do
%(Dir[Rails.root.join("spec/support/**/*.rb")].sort.each { |file| require file })
end
end
end

def generate_empty_factories_file
if default_test_suite?
copy_file "factories.rb", "test/factories.rb"
elsif rspec_test_suite?
copy_file "factories.rb", "spec/factories.rb"
end
end

def provide_dev_prime_task
copy_file "dev.rake", "lib/tasks/dev.rake"
end

def remove_fixture_definitions
if default_test_helper_present?
comment_lines "test/test_helper.rb", /fixtures :all/
end
end

def create_linting_test
if default_test_suite?
copy_file "factories_test.rb", "test/factory_bots/factories_test.rb"
end
end

private

def default_test_suite?
File.exist? Rails.root.join("test")
end

def rspec_test_suite?
File.exist? Rails.root.join("spec")
end

def default_test_helper_present?
File.exist? Rails.root.join("test/test_helper.rb")
end

def rspec_test_helper_present?
File.exist? Rails.root.join("spec/rails_helper.rb")
end
end
end
end
12 changes: 12 additions & 0 deletions lib/generators/templates/factory_bot/dev.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
if Rails.env.development? || Rails.env.test?
require "factory_bot"

namespace :dev do
desc "Sample data for local development environment"
task prime: "db:setup" do
include FactoryBot::Syntax::Methods

# create(:user, email: "[email protected]", password: "password")
end
end
end
2 changes: 2 additions & 0 deletions lib/generators/templates/factory_bot/factories.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FactoryBot.define do
end
9 changes: 9 additions & 0 deletions lib/generators/templates/factory_bot/factories_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "test_helper"

class FactoryBotsTest < ActiveSupport::TestCase
class FactoryLintingTest < FactoryBotsTest
test "linting of factories" do
FactoryBot.lint traits: true
end
end
end
5 changes: 5 additions & 0 deletions lib/generators/templates/factory_bot/factory_bot_rspec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FactoryBot.use_parent_strategy = true

RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
215 changes: 215 additions & 0 deletions test/generators/suspenders/factory_bot_generator_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
require "test_helper"
require "generators/suspenders/factory_bot_generator"

module Suspenders
module Generators
class FactoryBotGenerator::DefaultTest < Rails::Generators::TestCase
include Suspenders::TestHelpers

tests Suspenders::Generators::FactoryBotGenerator
destination Rails.root
setup :prepare_destination
teardown :restore_destination

test "generator runs without errors" do
assert_nothing_raised do
run_generator
end
end

test "installs gem with Bundler" do
Bundler.stubs(:with_unbundled_env).yields
generator.expects(:run).with("bundle install").once

capture(:stdout) do
generator.add_factory_bot
end
end

test "removes fixture definitions" do
File.open(app_root("test/test_helper.rb"), "w") { _1.write test_helper }

run_generator

assert_file app_root("test/test_helper.rb") do |file|
assert_match(/# fixtures :all/, file)
end
end

test "adds gem to Gemfile" do
run_generator

assert_file app_root("Gemfile") do |file|
assert_match(/group :development, :test do\n gem "factory_bot_rails"\nend/, file)
end
end

test "includes syntax methods" do
File.open(app_root("test/test_helper.rb"), "w") { _1.write test_helper }

run_generator

assert_file app_root("test/test_helper.rb") do |file|
assert_match(/class TestCase\n include FactoryBot::Syntax::Methods/, file)
end
end

test "creates definition file" do
run_generator

assert_file app_root("test/factories.rb") do |file|
assert_match definition_file, file
end
end

test "creates linting test" do
run_generator

assert_file app_root("test/factory_bots/factories_test.rb") do |file|
assert_match factories_test, file
end
end

test "creates dev prime task" do
run_generator

assert_file app_root("lib/tasks/dev.rake") do |file|
assert_match dev_rake, file
end
end

private

def prepare_destination
mkdir "test"
touch "Gemfile"
end

def restore_destination
remove_dir_if_exists "test"
remove_dir_if_exists "spec"
remove_file_if_exists "Gemfile"
remove_dir_if_exists "lib/tasks"
end

def test_helper
<<-RUBY
ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment"
require "rails/test_help"
module ActiveSupport
class TestCase
# Run tests in parallel with specified workers
parallelize(workers: :number_of_processors)
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
# Add more helper methods to be used by all tests here...
end
end
RUBY
end

def factories_test
<<~RUBY
require "test_helper"
class FactoryBotsTest < ActiveSupport::TestCase
class FactoryLintingTest < FactoryBotsTest
test "linting of factories" do
FactoryBot.lint traits: true
end
end
end
RUBY
end

def definition_file
<<~RUBY
FactoryBot.define do
end
RUBY
end

def dev_rake
<<~RUBY
if Rails.env.development? || Rails.env.test?
require "factory_bot"
namespace :dev do
desc "Sample data for local development environment"
task prime: "db:setup" do
include FactoryBot::Syntax::Methods
# create(:user, email: "[email protected]", password: "password")
end
end
end
RUBY
end
end

class FactoryBotGenerator::RSpecTest < Rails::Generators::TestCase
include Suspenders::TestHelpers

tests Suspenders::Generators::FactoryBotGenerator
destination Rails.root
setup :prepare_destination
teardown :restore_destination

test "includes syntax methods" do
touch("spec/rails_helper.rb")

run_generator

assert_file app_root("spec/support/factory_bot.rb") do |file|
assert_match factory_bot_config, file
end
assert_file app_root("spec/rails_helper.rb") do |file|
assert_match(/Dir\[Rails\.root\.join\("spec\/support\/\*\*\/\*\.rb"\)\]\.sort\.each { \|file\| require file }/, file)
end
end

test "creates definition file" do
run_generator

assert_file app_root("spec/factories.rb") do |file|
assert_match definition_file, file
end
end

private

def prepare_destination
mkdir "spec"
touch "Gemfile"
end

def restore_destination
remove_dir_if_exists "test"
remove_dir_if_exists "spec"
remove_file_if_exists "Gemfile"
remove_dir_if_exists "lib/tasks"
end

def factory_bot_config
<<~RUBY
FactoryBot.use_parent_strategy = true
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
RUBY
end

def definition_file
<<~RUBY
FactoryBot.define do
end
RUBY
end
end
end
end

0 comments on commit ed5e4a6

Please sign in to comment.