Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce suspenders:factories generator #1136

Merged
merged 1 commit into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Unreleased
* Introduce `suspenders:accessibility` generator
* Introduce `Suspenders::Generators::APIAppUnsupported` module and concern
* Introduce `suspenders:inline_svg` generator
* Introduce `suspenders:factories` generator

20230113.0 (January, 13, 2023)

Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,27 @@ Installs [capybara_accessibility_audit] and [capybara_accessible_selectors]
[capybara_accessibility_audit]: https://github.com/thoughtbot/capybara_accessibility_audit
[capybara_accessible_selectors]: https://github.com/citizensadvice/capybara_accessible_selectors

### Factories

Build test data with clarity and ease.

This uses [FactoryBot] to help you define dummy and test data for your
test suite. The `create`, `build`, and `build_stubbed` class methods are
directly available to all tests.

We recommend putting FactoryBot definitions in one `spec/factories.rb`
(or `test/factories`) file, at least until it grows unwieldy. This helps reduce
confusion around circular dependencies and makes it easy to jump between
definitions.

Supports the [default test suite] and [RSpec].

`bin/rails g suspenders:factories`

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

### Inline SVG

Render SVG images inline using the [inline_svg] gem, as a potential performance
Expand Down
66 changes: 66 additions & 0 deletions lib/generators/suspenders/factories_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
module Suspenders
module Generators
class FactoriesGenerator < Rails::Generators::Base
include Suspenders::Generators::Helpers

source_root File.expand_path("../../templates/factories", __FILE__)
desc <<~TEXT
Build test data with clarity and ease.

This uses FactoryBot to help you define dummy and test data for your test
suite. The `create`, `build`, and `build_stubbed` class methods are directly
available to all tests.

We recommend putting FactoryBot definitions in one `spec/factories.rb` (or
`test/factories`) file, at least until it grows unwieldy. This helps reduce
confusion around circular dependencies and makes it easy to jump between
definitions.

Supports the default test suite and RSpec.
TEXT

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
stevepolitodesign marked this conversation as resolved.
Show resolved Hide resolved
%(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
end
end
end
2 changes: 2 additions & 0 deletions lib/generators/templates/factories/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/factories/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/factories/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/factories/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
18 changes: 18 additions & 0 deletions lib/suspenders/generators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

module Suspenders
module Generators
module Helpers
def default_test_suite?
File.exist? Rails.root.join("test")
end

def rspec_test_suite?
File.exist? Rails.root.join("spec/spec_helper.rb")
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

module APIAppUnsupported
class Error < StandardError
def message
Expand Down
225 changes: 225 additions & 0 deletions test/generators/suspenders/factories_generator_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
require "test_helper"
require "generators/suspenders/factories_generator"

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

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

test "generator has a description" do
description = <<~TEXT
Build test data with clarity and ease.

This uses FactoryBot to help you define dummy and test data for your test
suite. The `create`, `build`, and `build_stubbed` class methods are directly
available to all tests.

We recommend putting FactoryBot definitions in one `spec/factories.rb` (or
`test/factories`) file, at least until it grows unwieldy. This helps reduce
confusion around circular dependencies and makes it easy to jump between
definitions.

Supports the default test suite and RSpec.
TEXT

assert_equal description, FactoriesGenerator.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 FactoriesGenerator::RSpecTest < Rails::Generators::TestCase
include Suspenders::TestHelpers

tests Suspenders::Generators::FactoriesGenerator
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 "does not modify rails_helper if it's configured to include support files" do
touch("spec/rails_helper.rb")
rails_helper = <<~RUBY
Dir[Rails.root.join("spec/support/**/*.rb")].sort.each { |file| require file }
RUBY
File.open(app_root("spec/rails_helper.rb"), "w") { _1.write rails_helper }

run_generator

assert_file app_root("spec/rails_helper.rb") do |file|
assert_equal rails_helper, 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 "spec/spec_helper.rb"
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