Protest, the simplicity rebel test framework

require "protest"

Protest.describe("A user") do
  setup do
    @user = => "John Doe", :email => "[email protected]")

  it "has a name" do
    assert_equal "John Doe",

  it "has an email" do
    assert_equal "[email protected]",

Protest is a small, simple, and easy-to-extend testing framework for ruby. It was written as a replacement for Test::Unit, given how awful its code is, and how difficult it is to extend in order to add new features.

I believe in minimalistic software, which is easily understood, easy to test, and specially, easy to extend for third parties. That's where I'm aiming with Protest.

Get it

gem install protest


rip install git:// v0.4.0

Setup and teardown

If you need to run code before or after each test, declare a setup or teardown block (respectively.)

Protest.context("A user") do
  setup do # this runs before each test
    @user = User.create(:name => "John")

  teardown do # this runs after each test

setup and teardown blocks are evaluated in the same context as your test, which means any instance variables defined in any of them are available in the rest. Both methods are aliased for your comfort as before and after respectively.

You can also use global_setup and global_teardown to run code only once per test case. global_setup blocks will run once before the first test is run, and global_teardown will run after all the tests have been run.

These methods, however, are dangerous, and should be used with caution, as they might introduce dependencies between your tests if you don't write your tests properly. Make sure that any state modified by code run in a global_setup or global_teardown isn't changed in any of your tests.

Also, you should be aware that the code of global_setup and global_teardown blocks isn't evaluated in the same context as your tests and normal setup/teardown blocks are, so you can't share instance variables between them.

Nested contexts

Break down your test into logical chunks with nested contexts:

Protest.describe("A user") do
  setup do
    @user = User.make

  context "when validating" do
    it "validates name" do = nil
      assert !@user.valid?

    # etc, etc

  context "doing something else" do
    # you get the idea

Any setup or teardown blocks you defined in a context will run in that context and in any other context nested in it.

Pending tests

There are two ways of marking a test as pending. You can declare a test with no body:

Protest.context("Some tests") do
  test "this test will be marked as pending"

  test "this tests is also pending"

  test "this test isn't pending" do
    assert true

Or you can call the pending method from inside your test.

Protest.context("Some tests") do
  test "this test is pending" do
    pending "oops, this doesn't work"
    assert false

Custom assertions

Previous versions of Protest used to bundle all the assertions defined in Test::Unit, but that has changed and now Protest includes just three basic assertion methods:

  • assert

  • assert_equal

  • assert_raise

If you want to add assertions, just define methods that rely on assert. This method takes a boolean and an optional error message as arguments, and the assertion is considered to fail if the boolean evaluates to neither false nor nil.

For example:

module AwesomenessAssertions
  def assert_awesomeness(object)
    assert object.awesome?, "#{object.inspect} is not awesome enough"

class Protest::TestCase
  include AwesomenessAssertions

You could also define rspec-like matchers if you like that style. See matchers.rb in the examples directory for an example.


Protest can report the output of a test suite in many ways. The library ships with a :progress report, and a :documentation report, :progress being the default.

Progress report

This is the default option, but you can force this by calling Protest.report_with(:progress).

The progress report will output the “classic” Test::Unit output of periods for passing tests, “F” for failing assertions, “E” for unrescued exceptions, and “P” for pending tests, in full color.

Documentation report

Use this report by calling Protest.report_with(:documentation)

For each testcase in your suite, this will output the description of the test case (whatever you provide TestCase.context), followed by the name of each test in that context, one per line. For example:

Protest.context "A user" do
  test "has a name"
  test "has an email"

  context "validations" do
    test "ensure the email can't be blank"

Will output, when run with the :documentation report:

A user
- has a name (Not Yet Implemented)
- has an email (Not Yet Implemented)

A user validations
- ensure the email can't be blank (Not Yet Implemented)

(The 'Not Yet Implemented' messages are because the tests have no body. See “Pending tests”, above.)

This is similar to the specdoc runner in rspec.

Summary report

Use this report by calling Protest.report_with(:summary)

Will output a brief summary with the total number of tests, assertions, passed tests, pending tests, failed tests and errors.

Stories report

Use this report by calling Protest.report_with(:stories)

This report is based on Citrusbyte's Stories, by Damian Janowski and Michel Martens.

Turn report

Use this report by calling Protest.report_with(:turn)

This report displays each test on a separate line with failures being displayed immediately instead of at the end of the tests.

You might find this useful when running a large test suite, as it can be very frustrating to see a failure (.…F…) and then have to wait until all the tests finish before you can see what the exact failure was.

This report is based on the output displayed by TURN, Test::Unit Reporter (New) by Tim Pease.

Defining your own reports

This is really, really easy. All you need to do is subclass Report, and register your subclass by calling Protest.add_report. See the documentation for details, or take a look at the source code for Protest::Reports::Progress and Protest::Reports::Documentation.

Using Rails?

If you are using Rails you may want to take a look at


Matías Flores —


Nicolás Sanguinetti —


MIT (see bundled LICENSE file for more info)