Gem Version Build Status Code Climate

Protest is a tiny, simple, and easy-to-extend testing framework for ruby.

Get it

gem install protest


require "protest"

Protest.describe "A user" do
  setup do
    @user = "John Doe", email: "")

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

  it "has an email" do
    assert_equal "",

Use Protest.context or Protest.describe at the top level to define a new test case. Inside the block, you can use test, it or should for defining each individual test.

Setup and teardown

If you need to run code before or after each test, declare a setup or teardown block:

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.

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


Protest includes just three basic assertion methods:

  • assert(condition, message)

    Ensure that a condition is met, otherwise it raises an AssertionFailed exception. The second argument is optional and it is used to override the default failure message.

  • assert_equal(expected, actual, message)

    Syntax sugar for assert(expected == actual, message).

  • assert_raise(exception, message) do ... end

    Passes if the code block raises the specified exception. If no exception is specified, this assertion passes if any exception is raised inside the block. The second argument is optional and it is used to override the default failure message.

Custom assertions

If you want to add more assertion methods, just define new methods that rely on assert.

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 few reports defined by default.

You can select which report to use using the report_with method:


By default, Protest will use the report defined in the PROTEST_REPORT environment variable. If this variable is not defined, the Documentation report will be used.

Progress report

Use this report 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(:progress).

For each testcase in your suite, this will output the description of the test case (whatever you passed to 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).

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

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.

Failing Early

If needed, you can configure Protest to stop the execution when the first error or failed assertion occurs.

This can be configured with the fail_early method:

Protest.fail_early = true

This feature can be configured by passing a PROTEST_FAIL_EARLY environment variable, to activate it you must set it to "true".


Protest comes with a command-line interface for running tests:

$ protest --help
  protest --help             # Show this help text
  protest                    # Run all tests in test/**/*.rb
  protest DIR                # Run all tests in DIR/**/*.rb
  protest FILE1.rb FILE2.rb  # Run all tests in FILE1.rb and FILE2.rb
  protest FILE.rb:15         # Run tests in FILE.rb on line 15

Using Rails?

If you are using Rails you may want to take a look at protest-rails.


Protest was created by Nicolás Sanguinetti and is currently maintained by Matías Flores.


Distributed under the terms of the MIT license. See bundled LICENSE file for more info.