HolyGrailHarness
A curated Rails application prototype that focuses on simple test patterns for Ruby & JavaScript!
Unlike normal Rails Application Templates or more modern Rails application generators like Rails Composer, the HolyGrailHarness is a basic Rails application that can be considered a prototype and customized via a simple setup script. It is also somewhat opinionated in that it promotes simple and powerful testing choices and focuses on using Ruby 1.9 and up, MiniTest::Spec, Capybara, Poltergeist/PhantomJS, and Konacha. More details on each component and what HolyGrailHarness provides are below.
The HolyGrailHarness is perfect for any of the following:
- Bootstrapping your next Rails application.
- Learning and promoting MiniTest::Spec
- Modern JavaScript testing setups.
- Teaching Rails and/or JavaScript at your next meetup.
Usage
- Download the project.
- Now from the root of "holy_grail_harness" directory.
$ bundle install
$ bundle exec thor setup my_app_name
Make sure to replace my_app_name
above with the name of your new Rails application. The setup script has a few options, but the end result will be a new Rails application all ready to go. So why not a normal Rails application template? Although, Rails application templates provide a really nice feature set. It was much easier to bootstrap a new Rails application using this prototype method. The end result is a cleaner Gemfile and application setup that can be vetted and tested from within HolyGrailHarness itself.
The script will rename your directory and prompt you to cd
to that directory. Once you do that, run rake test:all
to see that everything is working.
$ cd ../my_app_name
$ bundle exec rake test:all
Rails 3
This application prototype will focus on the latest Rails version. At this time, the bundle is locked down to v3.2.11. As Rails updates and is compatible with each component, so will this prototype application be updated. The bundle includes:
- QuietAssets gem for silent pipeline logging.
- Thin webserver. Primarily to be automatically used by Konacha but also good for development if you are not using something like Pow.
Testing
MiniTest::Spec All The Way Across The Sky!
Don't wait for Rails 4 to use MiniTest::Spec! This application is using the minitest-spec-rails gem which forces ActiveSupport::TestCase
to subclass MiniTest::Spec
. This means that you can start using the MiniTest's Spec or Unit structure and assertions directly within the familiar Rails unit, functional, or integration directories. For full details, check out the minitest-spec-rails documentation or some of the test shims within HolyGrailHarness. For example, a test/unit/user_test.rb
might look like this.
require 'test_helper'
class UserTest < ActiveSupport::TestCase
let(:bob) { users(:bob) }
let(:admin) { users(:admin) }
it 'must respond true to #admin? for administrators only' do
admin.must_be :admin?
bob.wont_be :admin?
end
end
Capybara Integration Tests With Poltergeist Using PhantomJS
You don't need Cucumber to write good integration tests. Instead use the basic Capybara DSL directly within a Rails integration tests with the most bad ass driver available, Poltergeist, which is built on top of PhantomJS. Never again worry about installing Qt so you can compile capybara-webkit, just go download a pre-compiled PhantomJS binary for your specific platform and enjoy 20% faster integration test runs vs capybara-webkit.
Integration tests are still within the ActionDispatch::IntegrationTest
class and as promised, MiniTest::Spec is available here too. Each test file needs to require the test_helper_integration which provides the following base features.
- Sets page size to that of a 13" MacBook Air.
- Resets Capybara sessions after each test.
- Provides a
#save_and_open_page
, or#page!
for short, screen shot method. - Ensures a single ActiveRecord DB connection for transactional test runs.
- An
#execjs
helper for bridging Ruby and the JavaScript under test.
HolyGrailHarness comes with a integration test example in the test/integration/application_test.rb file. An integration test might look something like this.
require 'test_helper_integration'
class ApplicationTest < ActionDispatch::IntegrationTest
before { visit root_path }
let(:h1) { find 'h1' }
it 'renders' do
h1.must_be :present?
end
end
Konacha JavaScript Tests Using PhantomJS
Move over Jasmine(rice), Konacha is the way to test your JavaScript now. Konacha is a Rails engine that allows you to test your JavaScript with the Mocha test framework and Chai assertion library. Konacha's killer feature is a sandboxed <iframe>
for each test spec to run within as well as full Rails asset pipeline integration. The HolyGrailHarness does all the work to get your Konacha spec/javascripts
directory all setup and ready to go. Highlights include:
- An initializer that sets up Poltergeist as the Capybara driver.
- A directory structure for model, view, and controller specs.
- A
spec_helper.js.coffee
for your specs to require. Provides global setup, configurations and vendor requires.
HolyGrailHarness also has a spec/javascripts/spec_helper
directory meant for helpers and extensions that should be available to all specs. We have included a fixtures.js.coffee
file that demonstrates how to setup JSON data fixtures for use from anything to stubbing requests to instantiating new model objects. We also have a helpers.js.coffee
file that exposes a few top level functions that make debugging your JavaScript easy. Below are the vendored JavaScript libraries that are required by the spec_helper
.
- Sinon.JS - For spies, stubs, faking time, etc.
- jQuery Mockjax - Best way to mock jQuery's AJAX functions.
- Chai jQuery - Chai assertions for jQuery.
- jsDump - Used by the
myLog()
helper.
Because your CI system should run all your tests, the HolyGrailHarness has added a Rake task to the test namespace that runs the default rails test task (units, functional, integrations) then your Konacha tests.
$ rake test:all # Runs all Rails tests, then Konacha tests.
Guard
TDD in style and run your tests when you hit save! Both guard-minitest and guard-konacha are bundled and ready to go. A basic Guardfile
is already setup too. Unlike most, this one is split into two groups :ruby
or :js
. This lets you focus on either everything or a specific language for your tests.
$ guard # Monitor both Ruby and JavaScript tests.
$ guard -g ruby # Monitor Ruby tests.
$ guard -g js # Monitor JavaScript tests.
The Guardfile assumes you are running OS X and wish to use the Ruby GNTP (Growl Notification Transport Protocol). If this is not the case, consult the Guard documentation on different system notification alternatives.
Factories And Fixtures
ActiveRecord YAML fixtures suck, but so do slow tests that rely on an empty database with excessive setups based on factories. The answer? Take advantage of the best each has to offer. Use factories to populate fixtures into the test database while leveraging database transactions during your test runs. The end result is a known factory story with the ability to create more test data as needed using the same factories. Allowing factories to properly hook into model logic means no more decomposing business logic into YAML text files. How?
The HolyGrailHarness bundles the named_seeds gem along with the factory_girl gem. The NamedSeeds library checks for the existence of a db/test/seeds.rb
file and if present, loads that file. Just like Rails' own db/seeds.rb
anything in this file goes. The only difference is that this seed file is populated right before you tests are run so they persist between transactions. You also get the benefit of using this same seed data in development as part of the normal Rails db:setup
process. Read the full documentationn on their site on how to use it. Below is a brief example.
Create factories in the test/factories
directory. Note, factories are best when they make valid garbage™
, so the HolyGrailHarness also requires the forgery gem to help with that.
# In test/factories/user_factory.rb
FactoryGirl.define do
factory :user do
email { Forgery::Email.address }
first_name { Forgery::Name.first_name }
last_name { Forgery::Name.first_name }
password 'test'
end
end
When making seed data, be explicit with your attributes that may be forged in the factory, database seeds should be consistent and have meaningful attributes. In this example we are creating an admin user. Note too how we are using NamedSeeds.identify
which mimics AcctiveRecord's fixture identity. This gives us a handle to the fixture within our tests. We also create the @admin
instance variable because we might want to use that user later on in the fixture story.
# In db/test/seeds.rb
require 'factory_girl'
FactoryGirl.find_definitions rescue true
include FactoryGirl::Syntax::Methods
@admin = create :user, id: NamedSeeds.identify(:admin),
first_name: 'Admin', last_name: 'User', email: '[email protected]'
Lastly, in your test/test_helper.rb
file, declare that you have a named seed to the users model. This will allow your tests to act just like those with ActiveRecord fixtures and use the users(:admin)
helper to get to that seeded fixture.
# In test/test_helper.rb
class ActiveSupport::TestCase
named_seeds :users
end
MVC JavaScript
The HolyGrailHarness wants you to use some type MV* structure for your JavaScript. The setup script supports Spine.js as an option, however you can decline and all traces of Spine.js will be removed. If so, the following features will still remain.
A single JavaScript namespace on the window object. This namespace creates a model, view, controller object structure that direly matches to the app/assets/javascripts/#{my_app_name}/(model|view|controller)
directory structure within the Rails asset pipeline. This JavaScript namespace and matching directories will be changed to your new application name as part of the setup task. Here is an example of a User model whose corresponding file would be found in the app/assets/javascripts/my_app_name/models/user.js.coffee
file.
class @MyAppName.App.Models.User extends View
@configure 'User', 'id', 'email'
The main application.js
file requires all vendor frameworks, then the index.js.coffee
within your application name directory. Use this file to boot your JavaScript application and/or setup your root view controller.
Also included is the SpacePen view framework. SpacePen is a powerful and minimalist client-side view framework authored in CoffeeScript. It is actually a jQuery subclass which makes your views really easy to traverse and respond to controller events. Read my View Controller Patterns With Spine.js & SpacePen article to learn why views should not be dumb and how you can take advantage of SpacePen no matter what JavaScript MV* framework you use.
With Spine.js
If you choose to use Spine.js as your JavaScript MVC structure, the setup script will create a git submodule to the Spine repository to the vendor/assets/javascripts/spine
directory. This allows your project to use the the source CoffeeScript files, which makes for a wonderful learning experience to both Spine.js and idomatic CoffeeScript.
By default the index.js.coffee
will require all Spine components. This includes manager (stacks), ajax, route, and relation. Remove anything that you do not need. This file also defines the root view controller along with a MyAppName.App.Index.init()
class level initialization function. This is called in the main application.html.erb
layout file for you too. Likewise, the application init is done in the Mocha before filters mentioned above in both the spec_helper.js.coffee
and fixtures.js.coffee
files. If you examine these files closely, you will see how they make use of Mocha's done()
callback so that you can cleanly abstract AJAX mocks and anything else related to your JavaScript application's boot process. Here is an example of how you might setup your initApplication()
.
@initApplication = (callback) =>
bob = MyAppName.Test.Seeds.users.bob
$.mockjax url: "/users/#{bob.id}", responseText: MyAppName.Test.Response.bobInitial.responseText
MyAppName.App.Models.User.fetch id: bob.id
MyAppName.App.Models.User.one 'refresh', callback
No JavaScript project should be without a local notification system to help keep disparate components up to date. Thankfully, Spine's event module makes a local PubSub system a breeze. The HolyGrailHarness has a notifications.js.coffee
that exposes a class level bind()
and trigger()
to any event string/namespace you want. To make more simple, we recommend creating class level functions that expose the event name as the function name and pass the args to the handle()
function. We have done this for the MyAppName.Notifications.appReady()
to demonstrate. Calling this function will trigger the app.ready
event and passing a function to this function will bind that function to the same event name.
Sass & Compass
Sass is the only way to write CSS for today's modern web applications. Compass is the CSS framework that no Sass user should go without. Together they provide a foundation for writing beautiful CSS using pre-built time saving functions. The HolyGrailHarness includes both the sass-rails and compass-rails gems.
To get you started on the right path, we have also created a basic structure within the app/assets/stylesheets
asset pipeline directory to help you organize your Sass files. Here is the directory structure below.
├── application.css
├── application
│ ├── _layout.scss
│ ├── index.scss
│ ├── components
│ │ └── _foo.scss
└── shared
├── _animations.scss
├── _fonts.scss
├── _mixins.scss
├── _placeholders.scss
├── _variables.scss
└── base.scss
The application.css file.
Never write CSS in application.css
. Say what? I know right, but trust me. Just consider this file a top level bundle dependency that only requires other top level bundle assets. Here is the contents of that file. Notice how it requires a bundle called twitter and an index. One is for twitter bootstrap, see section below, and the other is the index to your own Sass framework.
/*
*= require application/twitter
*= require application/index
*/
The shared directory.
Think of this as your own Compass framework. The base.scss
is your single file to @import
to get everything loaded and ready to go. Nothing in any of the shared files should generate CSS! Importing shared/base
should act just like importing compass
. Use these files for setting your own variables and creating misc helper functions & mixins. There is a variables file for... variables! Another for animations, fonts and mixins too.
Pay special attention to the _placeholders.scss
file. If you do not know about Sass 3.2's placeholder selectors (silent classes) and how they make presentational classes efficiently extended by semantic ones, then I highly suggest you read Dale Sande's presentation titled Sass 3.2 Silent Classes on Speaker Deck.
Below is the contents of the base.scss
file, take note of the order. See too how we import the entire Compass framework. This means that all of your Sass code in any of the shared files can take full advantage of both Bootstrap and Compass' variables and mixins. Epic win!
// Think of this file as your own compass. Importing the base, never generates CSS.
@import "shared/variables";
@import "bootstrap/mixins";
@import "compass";
@import "compass/layout";
@import "compass/css3/user-interface";
@import "shared/fonts";
@import "shared/mixins";
@import "shared/animations";
@import "shared/placeholders";
The application directory.
Organize this as you see fit. We have started you off by creating a _layout.scss
file for your general layout/structure styles. There is also a components
directory which all sub files are imported via a glob. The idea is that components are not dependent upon another. Files that might go in here are things like datepicker, navigation, and general files named after components or widgets. Below is what the application/index.scss
looks like.
@import "shared/base";
@import "./layout";
@import "components/*";
If you are more advanced with your CSS and like the idea of style guides, take a looks a the Toadstool style guide framework.
Twitter Bootstrap
Twitter Bootstrap is awesome, but LESS is not. That is why the HolyGrailHarness uses the bootstrap-sass gem that converts all the Bootstrap LESS files to Sass. Making them ready to import via the Rails asset pipeline.
As shown above in the Sass section, we require the application/twitter.scss
bundle asset from the top level application.css
bundle file. This twitter bundle file, contents below, take advantage of your shared variables before importing bootstrap from the gem. In this way you can define variables that tweak bootstrap. A good example would be button colors, column widths, etc. Later on in the file you can extend bootstrap styles to your liking. For instance, add more padding to buttons.
@import "shared/variables";
@import "bootstrap";
@import "font-awesome";
// Tweak or redefine Twitter classes below.
Font Awesome
The glyph icons included in Twitter Bootstrap are horrible for hi-resolution "retina" displays typically found on mobile devices. Thankfully the Font Awesome project provides a drop in replacement that instead uses icon fonts vs raster images.
The HolyGrailHarness vendors these font files and the needed font-awesome.scss
file and requires them as part of the Twitter Bootstrap bundle shown above. More advanced users may prefer to only include the icon fonts needed in their application or a few custom icons. If that is the case, check out Font Custom, webfonts from the comfort of the command line.