Skip to content

Commit

Permalink
Revamp the minitesting bundled configuration so it uses Rack::Test an…
Browse files Browse the repository at this point in the history
…d the Roda server
  • Loading branch information
jaredcwhite committed Dec 8, 2024
1 parent 7f48f7e commit 533cc77
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 92 deletions.
7 changes: 7 additions & 0 deletions bridgetown-core/lib/bridgetown-core/commands/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
87 changes: 23 additions & 64 deletions bridgetown-core/lib/bridgetown-core/configurations/minitesting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion bridgetown-website/src/_docs/bundled-configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:**

Expand Down
42 changes: 15 additions & 27 deletions bridgetown-website/src/_docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,40 @@ 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:

```sh
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,
'<img src="/images/khristi-jamil-avatar.jpg" alt="Khristi Jamil" class="avatar">'
end
end
require "minitest_helper"

class TestBlog < Bridgetown::Test
def test_authors
html get "/blog"

assert_equal '<img src="/images/khristi-jamil-avatar.jpg" alt="Khristi Jamil" class="avatar">',
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

Expand Down
47 changes: 47 additions & 0 deletions bridgetown/lib/bridgetown/test.rb
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 533cc77

Please sign in to comment.