diff --git a/Rakefile b/Rakefile index 7ceb0fd..0069e36 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,9 @@ #!/usr/bin/env rake require "bundler/gem_helper" +require "rspec/core/rake_task" +require "rake/testtask" +require "standard/rake" namespace "dotenv" do Bundler::GemHelper.install_tasks name: "dotenv" @@ -24,14 +27,12 @@ task build: ["dotenv:build", "dotenv-rails:build"] task install: ["dotenv:install", "dotenv-rails:install"] task release: ["dotenv:release", "dotenv-rails:release"] -require "rspec/core/rake_task" - desc "Run all specs" RSpec::Core::RakeTask.new(:spec) do |t| t.rspec_opts = %w[--color] t.verbose = false end -require "standard/rake" +Rake::TestTask.new -task default: [:spec, :standard] +task default: [:spec, :test, :standard] diff --git a/lib/dotenv.rb b/lib/dotenv.rb index c0ec36e..09f5969 100644 --- a/lib/dotenv.rb +++ b/lib/dotenv.rb @@ -63,7 +63,7 @@ def instrument(name, payload = {}, &block) if instrumenter instrumenter.instrument(name, payload, &block) else - yield + block&.call end end @@ -72,6 +72,17 @@ def require_keys(*keys) return if missing_keys.empty? raise MissingKeys, missing_keys end + + # Save a snapshot of the current `ENV` to be restored later + def save + @snapshot = ENV.to_h.freeze + instrument("dotenv.save", env: @snapshot) + end + + # Restore the previous snapshot of `ENV` + def restore + instrument("dotenv.restore", env: @snapshot) { ENV.replace(@snapshot) } + end end require "dotenv/rails" if defined?(Rails::Railtie) diff --git a/lib/dotenv/rails.rb b/lib/dotenv/rails.rb index 90b33b0..81d4aa2 100644 --- a/lib/dotenv/rails.rb +++ b/lib/dotenv/rails.rb @@ -5,7 +5,7 @@ # Watch all loaded env files with Spring begin require "spring/commands" - ActiveSupport::Notifications.subscribe(/^dotenv/) do |*args| + ActiveSupport::Notifications.subscribe('dotenv.load') do |*args| event = ActiveSupport::Notifications::Event.new(*args) Spring.watch event.payload[:env].filename if Rails.application end @@ -16,17 +16,20 @@ module Dotenv # Rails integration for using Dotenv to load ENV variables from a file class Rails < ::Rails::Railtie - attr_accessor :overwrite, :files + delegate :files, :files=, :overwrite, :overwrite=, :test_help, :test_help=, to: "config.dotenv" def initialize super() - @overwrite = false - @files = [ - root.join(".env.#{env}.local"), - (root.join(".env.local") unless env.test?), - root.join(".env.#{env}"), - root.join(".env") - ].compact + config.dotenv = ActiveSupport::OrderedOptions.new.update( + overwrite: false, + files: [ + root.join(".env.#{env}.local"), + (root.join(".env.local") unless env.test?), + root.join(".env.#{env}"), + root.join(".env") + ].compact, + test_help: env.test? + ) end # Public: Load dotenv @@ -85,6 +88,10 @@ def self.load app.deprecators[:dotenv] = deprecator if app.respond_to?(:deprecators) end + initializer "dotenv.test_help" do |app| + require "dotenv/test_help" if test_help + end + config.before_configuration { load } end diff --git a/lib/dotenv/test_help.rb b/lib/dotenv/test_help.rb new file mode 100644 index 0000000..5133d4a --- /dev/null +++ b/lib/dotenv/test_help.rb @@ -0,0 +1,21 @@ +if defined?(RSpec.configure) + RSpec.configure do |config| + # Save ENV before the suite starts + config.before(:suite) { Dotenv.save } + + # Restore ENV after each example + config.after { Dotenv.restore } + end +end + +if defined?(ActiveSupport) + ActiveSupport.on_load(:active_support_test_case) do + # Save ENV when the test suite loads + Dotenv.save + + ActiveSupport::TestCase.class_eval do + # Restore ENV after each test + setup { Dotenv.restore } + end + end +end diff --git a/spec/dotenv/rails_spec.rb b/spec/dotenv/rails_spec.rb index d2935f0..cb0ca08 100644 --- a/spec/dotenv/rails_spec.rb +++ b/spec/dotenv/rails_spec.rb @@ -3,10 +3,31 @@ require "dotenv/rails" describe Dotenv::Rails do + let(:application) do + Class.new(Rails::Application) do + config.load_defaults Rails::VERSION::STRING.to_f + config.eager_load = false + config.logger = ActiveSupport::Logger.new(StringIO.new) + config.root = fixture_path + end.instance + end + + around do |example| + # These get frozen after the app initializes + autoload_paths = ActiveSupport::Dependencies.autoload_paths.dup + autoload_once_paths = ActiveSupport::Dependencies.autoload_once_paths.dup + + # Run in fixtures directory + Dir.chdir(fixture_path) { example.run } + ensure + # Restore autoload paths to unfrozen state + ActiveSupport::Dependencies.autoload_paths = autoload_paths + ActiveSupport::Dependencies.autoload_once_paths = autoload_once_paths + end + before do Rails.env = "test" - allow(Rails).to receive(:root).and_return Pathname.new(__dir__).join("../fixtures") - Rails.application = double(:application) + Rails.application = nil Spring.watcher = Set.new # Responds to #add end @@ -15,22 +36,16 @@ Dotenv::Rails.remove_instance_variable(:@instance) end - after do - # Reset - Spring.watcher = nil - Rails.application = nil - end - describe "files" do it "loads files for development environment" do Rails.env = "development" expect(Dotenv::Rails.files).to eql( [ - Rails.root.join(".env.development.local"), - Rails.root.join(".env.local"), - Rails.root.join(".env.development"), - Rails.root.join(".env") + application.root.join(".env.development.local"), + application.root.join(".env.local"), + application.root.join(".env.development"), + application.root.join(".env") ] ) end @@ -39,15 +54,16 @@ Rails.env = "test" expect(Dotenv::Rails.files).to eql( [ - Rails.root.join(".env.test.local"), - Rails.root.join(".env.test"), - Rails.root.join(".env") + application.root.join(".env.test.local"), + application.root.join(".env.test"), + application.root.join(".env") ] ) end end - it "watches loaded files with Spring" do + it "watches other loaded files with Spring" do + application.initialize! path = fixture_path("plain.env") Dotenv.load(path) expect(Spring.watcher).to include(path.to_s) @@ -61,7 +77,7 @@ end context "load" do - subject { Dotenv::Rails.load } + subject { application.initialize! } it "watches .env with Spring" do subject @@ -109,4 +125,25 @@ end end end + + describe "test_help" do + it "is loaded if RAILS_ENV=test" do + expect(Dotenv::Rails.test_help).to eq(true) + expect(Dotenv::Rails.instance).to receive(:require).with("dotenv/test_help") + application.initialize! + end + + it "is not loaded if RAILS_ENV=development" do + Rails.env = "development" + expect(Dotenv::Rails.test_help).to eq(false) + expect(Dotenv::Rails.instance).not_to receive(:require).with("dotenv/test_help") + application.initialize! + end + + it "is not loaded if test_help set to false" do + Dotenv::Rails.test_help = false + expect(Dotenv::Rails.instance).not_to receive(:require).with("dotenv/test_help") + application.initialize! + end + end end diff --git a/spec/dotenv_spec.rb b/spec/dotenv_spec.rb index 749a245..9dca8a2 100644 --- a/spec/dotenv_spec.rb +++ b/spec/dotenv_spec.rb @@ -94,7 +94,10 @@ it "fails silently" do expect { subject }.not_to raise_error - expect(ENV.keys).to eq(@env_keys) + end + + it "does not change ENV" do + expect { subject }.not_to change { ENV.inspect } end end end @@ -149,7 +152,10 @@ it "fails silently" do expect { subject }.not_to raise_error - expect(ENV.keys).to eq(@env_keys) + end + + it "does not change ENV" do + expect { subject }.not_to change { ENV.inspect } end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8d1cdce..ee138af 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,11 +1,6 @@ require "dotenv" +require "dotenv/test_help" -RSpec.configure do |config| - # Restore the state of ENV after each spec - config.before { @env_keys = ENV.keys } - config.after { ENV.delete_if { |k, _v| !@env_keys.include?(k) } } -end - -def fixture_path(name) - Pathname.new(__dir__).join("./fixtures", name) +def fixture_path(*parts) + Pathname.new(__dir__).join("./fixtures", *parts) end diff --git a/test/test_help_test.rb b/test/test_help_test.rb new file mode 100644 index 0000000..9fad86f --- /dev/null +++ b/test/test_help_test.rb @@ -0,0 +1,18 @@ +require "active_support/deprecator" +require "active_support/test_case" +require "minitest/autorun" + +require "dotenv" +require "dotenv/test_help" + +class TestHelpTest < ActiveSupport::TestCase + test "restores ENV between tests, part 1" do + assert_nil ENV["DOTENV"], "ENV was not restored between tests" + ENV["DOTENV"] = "1" + end + + test "restores ENV between tests, part 2" do + assert_nil ENV["DOTENV"], "ENV was not restored between tests" + ENV["DOTENV"] = "2" + end +end