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.

Additionally, we remove the generation of the `dev:prime` task as we
felt that should be the responsibly of another generator.

[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 30, 2023
1 parent 2a0f7ff commit e93890a
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 1 deletion.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Unreleased

* Remove `suspenders` system executable
* Introduce `suspenders:factory_bot` generator

20230113.0 (January, 13, 2023)

Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@ end

## Generators

TODO
### Factory Bot

Installs and configures [factory_bot_rails]. Supports the [default test suite] and [RSpec].

`./bin/rails g suspenders:factory_bot`

[factory_bot_rails]: https://github.com/thoughtbot/factory_bot_rails
[default test suite]: https://guides.rubyonrails.org/testing.html
[RSpec]: https://rspec.info

## Contributing

Expand Down
69 changes: 69 additions & 0 deletions lib/generators/suspenders/factory_bot_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
module Suspenders
module Generators
class FactoryBotGenerator < Rails::Generators::Base
source_root File.expand_path("../../templates/factory_bot", __FILE__)
desc "Installs and configures factory_bot_rails. Supports the default test suite and RSpec."

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 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"
elsif rspec_test_suite?
copy_file "factories_spec.rb", "spec/factory_bots/factories_spec.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
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
7 changes: 7 additions & 0 deletions lib/generators/templates/factory_bot/factories_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require "rails_helper"

RSpec.describe "Factories" do
it "has valid factoties" do
FactoryBot.lint traits: true
end
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
203 changes: 203 additions & 0 deletions test/generators/suspenders/factory_bot_generator_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
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 "generator has a description" do
description = "Installs and configures factory_bot_rails. Supports the default test suite and RSpec."

assert_equal description, FactoryBotGenerator.desc
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
definition_file = <<~RUBY
FactoryBot.define do
end
RUBY

run_generator

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

test "creates linting test" do
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

run_generator

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

private

def prepare_destination
mkdir "test"
touch "Gemfile"
end

def restore_destination
remove_dir_if_exists "test"
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
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")
factory_bot_config = <<~RUBY
FactoryBot.use_parent_strategy = true
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
RUBY

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
definition_file = <<~RUBY
FactoryBot.define do
end
RUBY

run_generator

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

test "creates linting test" do
factories_spec = <<~RUBY
require "rails_helper"
RSpec.describe "Factories" do
it "has valid factoties" do
FactoryBot.lint traits: true
end
end
RUBY

run_generator

assert_file app_root("spec/factory_bots/factories_spec.rb") do |file|
assert_match factories_spec, file
end
end

private

def prepare_destination
mkdir "spec"
touch "Gemfile"
end

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

0 comments on commit e93890a

Please sign in to comment.