diff --git a/bridgetown-core/lib/bridgetown-core/commands/base.rb b/bridgetown-core/lib/bridgetown-core/commands/base.rb index f4b166da1..c3d4f4a7b 100644 --- a/bridgetown-core/lib/bridgetown-core/commands/base.rb +++ b/bridgetown-core/lib/bridgetown-core/commands/base.rb @@ -66,6 +66,13 @@ def handle_no_command_error(cmd, _has_namespace = $thor_runner) puts "Unknown task: #{cmd.split("[")[0]}\n\nHere's a list of tasks you can run:" display_rake_tasks(rake) end + rescue RuntimeError => e + # re-raise error unless it's an error through Minitest + raise e unless e.message.include?("ruby -Ilib:test") + + Bridgetown.logger.error "test aborted!" + Bridgetown.logger.error e.message + exit(false) end end end diff --git a/bridgetown-core/lib/bridgetown-core/configurations/minitesting.rb b/bridgetown-core/lib/bridgetown-core/configurations/minitesting.rb index 5f4960ad8..1c0890864 100644 --- a/bridgetown-core/lib/bridgetown-core/configurations/minitesting.rb +++ b/bridgetown-core/lib/bridgetown-core/configurations/minitesting.rb @@ -7,93 +7,52 @@ append_to_file "Gemfile" do <<~GEMS \n - group :test, optional: true do - gem "nokogiri" + group :test do gem "minitest" - gem "minitest-profile" gem "minitest-reporters" - gem "shoulda" - gem "rails-dom-testing" + gem "rack-test" end GEMS end -create_file "test/helper.rb" do - <<~RUBY - require "nokogiri" - require "minitest/autorun" - require "minitest/reporters" - require "minitest/profile" - require "shoulda" - require "rails-dom-testing" +gsub_file "Gemfile", '# gem "nokolexbor"', 'gem "nokolexbor"' - # Report with color. - Minitest::Reporters.use! [ - Minitest::Reporters::DefaultReporter.new( - color: true - ), - ] - - Minitest::Test.class_eval do - include Rails::Dom::Testing::Assertions - - def site - @site ||= Bridgetown::Current.site - end - - def nokogiri(input) - input.respond_to?(:output) ? Nokogiri::HTML(input.output) : Nokogiri::HTML(input) - end - - def document_root(root) - @document_root = root.is_a?(Nokogiri::XML::Document) ? root : nokogiri(root) - end +insert_into_file "Rakefile", after: %(ENV["BRIDGETOWN_ENV"] = "test"\n Bridgetown::Commands::Build.start\nend\n) do + <<~RUBY - def document_root_element - if @document_root.nil? - raise "Call `document_root' with a Nokogiri document before testing your assertions" - end - @document_root - end + require "minitest/test_task" + Minitest::TestTask.create(:test) do |t| # add on to the test task + t.warning = false end RUBY end -create_file "test/test_homepage.rb" do +create_file "test/minitest_helper.rb" do <<~RUBY - require_relative "./helper" - - class TestHomepage < Minitest::Test - context "homepage" do - setup do - page = site.collections.pages.resources.find { |doc| doc.relative_url == "/" } - document_root page - end + require "minitest/autorun" + require "minitest/reporters" + Minitest::Reporters.use! [Minitest::Reporters::ProgressReporter.new] - should "exist" do - assert_select "body" - end - end - end + require "bridgetown/test" RUBY end -create_file "plugins/test_output.rb" do +create_file "test/test_homepage.rb" do <<~RUBY - module TestOutput - unless Bridgetown.env.development? - Bridgetown::Hooks.register_one :site, :post_write do - # Load test suite to run on exit - require "nokogiri" - Dir["test/**/*.rb"].each { |file| require_relative("../\#{file}") } - rescue LoadError - Bridgetown.logger.warn "Testing:", "To run tests, you must first run \`bundle install --with test\`" - end + require "minitest_helper" + + class TestHomepage < Bridgetown::Test + def test_homepage + html get "/" + + assert document.query_selector("body") end end RUBY end +run "bundle install", env: { "BUNDLE_GEMFILE" => Bundler::SharedHelpers.in_bundle? } + say_status :minitesting, "All set! To get started, look at test/test_homepage.rb and then run \`bin/bridgetown test\`" # rubocop:enable all \ No newline at end of file diff --git a/bridgetown-website/src/_docs/bundled-configurations.md b/bridgetown-website/src/_docs/bundled-configurations.md index cf2983776..60dc7c047 100644 --- a/bridgetown-website/src/_docs/bundled-configurations.md +++ b/bridgetown-website/src/_docs/bundled-configurations.md @@ -195,7 +195,7 @@ bin/bridgetown configure gh-pages ### Automated Test Suite using Minitest -⚙️ Adds a basic test suite using [Minitest](https://rubygems.org/gems/minitest) and Rails DOM assertions for extremely fast verification of your output HTML. Check out [our automated testing guide](/docs/testing#use-ruby-and-minitest-to-test-html-directly) for more info! +⚙️ Adds a test suite using [Minitest](https://rubygems.org/gems/minitest) and [Rack::Test](https://github.com/rack/rack-test) which lets you test both static and dynamic routes. Check out [our automated testing guide](/docs/testing#use-ruby-and-minitest-to-test-html-directly) for more information. 🛠 **Configure using:** diff --git a/bridgetown-website/src/_docs/testing.md b/bridgetown-website/src/_docs/testing.md index c08374ba8..3d66b6a29 100644 --- a/bridgetown-website/src/_docs/testing.md +++ b/bridgetown-website/src/_docs/testing.md @@ -7,15 +7,11 @@ category: testing Running an automated test suite after your Bridgetown site has been built is a great way to ensure important content is available and formatted as you expect, and that some recent change hasn't broken anything critical within your build process. -Bridgetown doesn't come with an opinionated testing setup, so you're welcome to choose from a variety of approaches—and perhaps even use several at once! - {{ toc }} ## Use Ruby and Minitest to Test HTML Directly -You can run a [bundled configuration](/docs/bundled-configurations#automated-test-suite-using-minitest) on your site to add a [`post_write` hook plugin](/docs/plugins/hooks) which kicks off a Minitest-based test suite. The plugin will automatically detect if the [Bridgetown environment](/docs/configuration/environments) isn't `development` (i.e. it's `test` or `production`) and if the optional set of test gems (Minitest, Nokogiri, etc.) are available. If so, the tests will run after the site has been built. - -One of the benefits of this testing approach is it's _very_ fast, due to the fact that all the static HTML has been built and is in memory when the test suite runs. +Bridgetown provides a [bundled configuration](/docs/bundled-configurations#automated-test-suite-using-minitest) to add gems for [Minitest](https://docs.seattlerb.org/minitest/) and [Rack::Test](https://github.com/rack/rack-test) and set up the test environment in the `test` folder. To install, run the following command: @@ -23,36 +19,28 @@ To install, run the following command: bin/bridgetown configure minitesting ``` -This will set up the plugin, test gems, and an example test suite in the `test` folder. +You can write tests to verify the output of both static and dynamic routes. Right when the test suite first runs, the Bridgetown site will be built (via the `test` [environment](/docs/configuration/environments)) so that static pages are available. Then, the [Roda server application](/docs/routes) will boot up in memory and you can make direct requests much as if you were using a full HTTP server. + +The `html` and `json` helpers let you parse responses, either as a [Nokolexbor](https://github.com/serpapi/nokolexbor) document in the case of an HTML response, or `JSON.parse` in the case of a JSON response. -The tests you write will be DOM selection assertions that operate on the output HTML that's in memory after the site has been rendered, so they run extremely fast. You use the native Ruby APIs provided by Bridgetown to find pages to test, and use assertions you may be familiar with from the Ruby on Rails framework (such as `assert_select` and `assert_dom_equal`). Here's an example of such a test: +Here's an example of such a test: ```ruby -require_relative "./helper" - -class TestBlog < Minitest::Test - context "blog page" do - setup do - page = site.collections.pages.resources.find { |page| page.relative_url == "/blog/index.html" } - document_root page - end - - should "show authors" do - assert_select ".box .author img" do |imgs| - assert_dom_equal imgs.last.to_html, - 'Khristi Jamil' - end - end +require "minitest_helper" + +class TestBlog < Bridgetown::Test + def test_authors + html get "/blog" + + assert_equal 'Khristi Jamil', + document.query_selector_all(".box .author img").last.outer_html end end ``` -You can add additional contexts and "should" blocks to a test file, and you can create as many test files as you want to handle various parts of the site. - -As part of the automation setup mentioned above, you should now have new scripts in `package.json`: `test` and `deploy:test`. +There are `get`, `post`, and `delete` methods available for testing various server routes. For more information, read the [Rack::Test](https://github.com/rack/rack-test) documentation. You can also access the Bridgetown site object loaded in memory via the `site` helper. For example, `site.metadata.title` would return your site's title as defined in `_data/site_metadata.yml`. -* `test`: Builds the site using the **test** environment (requires you first to run `bundle install --with test` on your machine). -* `deploy:test`: Installs the test gems and then runs `deploy`. Note this does not specify a particular environment—it's up to you to set that to **production** or otherwise as part of your deployment context. +You can add additional tests via `test_*` methods, and you can create as many test files as you want to handle various parts of the site. Be advised that these tests are run via the `server` initialization context, so it's possible something may not have run as you would expect under a `static` initialization context. But since the static site is already built prior to your tests being executed, it's probably best for you to test static use cases via the output HTML. ## Headless Browser Testing with Cypress diff --git a/bridgetown/lib/bridgetown/test.rb b/bridgetown/lib/bridgetown/test.rb new file mode 100644 index 000000000..a9aa2c368 --- /dev/null +++ b/bridgetown/lib/bridgetown/test.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# This file can be required by project test suites to set up the Minitest environment + +require "bridgetown" + +ENV["BRIDGETOWN_NO_BUNDLER_REQUIRE"] = nil +Bridgetown.begin! + +Bridgetown::Builders::PluginBuilder.then do + Bridgetown::Builders::DSL::Inspectors.setup_nokolexbor +end + +require "bridgetown-core/rack/boot" +class Bridgetown::Rack::Logger + def add(*) + super if ENV["SERVER_LOGS"] == "true" + end +end + +Bridgetown::Current.preloaded_configuration = Bridgetown.configuration +Bridgetown::Rack.boot + +require "rack/test" + +class Bridgetown::Test < Minitest::Test + include Rack::Test::Methods + + attr_reader :document + + def roda_app_class = RodaApp + + def app = roda_app_class.app + + def site + roda_app_class.opts[:bridgetown_site] + end + + def html(request) = @document = Nokolexbor::Document.parse(request.body) + + def json(request) = @document = JSON.parse(request.body) + + def routes = JSON.parse(File.read( + File.join(Bridgetown::Current.preloaded_configuration.root_dir, + ".routes.json") + )) +end