The examples on this page use , the test framework installed when you generate a new Hanami app.

Actions are standalone objects with an interface that’s easy to test. You can simply instantiate an action as your object under test and exercise its functionality.

In this example, is an instance of Bookshelf::Actions::Books::Index. To make a request to the action, we can call it with an empty parameters hash. The return value is a serialized response. In this test we’re asserting that the returned response is successful (status is in 2XX range).

To test your action, run bundle exec rspec with the path to your action’s test file:

  1. $ bundle exec rspec spec/actions/books/index_spec.rb

When you run the tests for a single action, Hanami will load only the smallest set of files required to run the test. The action’s dependencies and any related app code is loaded on demand, which makes it very fast to run and re-run individual tests as part of your development flow.

When testing an action, you can simulate the parameters and headers coming from a request by passing them as a hash.

Rack expects the headers to be uppercased, underscored strings prefixed by HTTP_ (like "HTTP_ACCEPT" => "application/json"), while your other request params can be regular keyword arguments.

The following test combines both params and headers.

  1. # spec/actions/books/show_spec.rb
  2. RSpec.describe Bookshelf::Actions::Books::Show do
  3. subject(:action) do
  4. Bookshelf::Actions::Books::Index.new
  5. end
  6. it "returns a successful JSON response with book id" do
  7. response = subject.call(id: "23", "HTTP_ACCEPT" => "application/json")
  8. expect(response).to be_successful
  9. expect(response.headers["Content-Type"]).to eq("application/json; charset=utf-8")
  10. end
  11. end

Here’s the example action that would make this test pass.

You may wish to provide test doubles (also known as “mock objects”) to your actions under test to control their environment or avoid unwanted side effects.

Let’s write the test for an action that creates a book such that it does not hit the database.

  1. # spec/actions/books/create_spec.rb
  2. RSpec.describe Bookshelf::Actions::Books::Create do
  3. subject(:action) do
  4. Bookshelf::Actions::Books::Create.new(user_repo: user_repo)
  5. end
  6. let(:user_repo) do
  7. instance_double(Bookshelf::UserRepo)
  8. end
  9. let(:book_params) do
  10. {title: "Hanami Guides"}
  11. end
  12. response = action.call(book: book_params)
  13. expect(response).to be_successful
  14. expect(response.body[0]).to eq(book_params.to_json)
  15. end
  16. end

We’ve injected the user_repo dependency with an RSpec test double. This would replace the default "user_repo" component for the following action.

Use test doubles only when the side effects are difficult to handle in a test environment. Remember to mock only your own interfaces and always use verified doubles.

Action tests are helpful for setting expectations on an action’s low-level behavior. However, for many actions, testing end-to-end behavior may be more useful.

For this, you can write request specs using [rack-test][rack-test], which comes included with your Hanami app.

  1. # spec/requests/root_spec.rb
  2. RSpec.describe "Root", type: :request do
  3. it "is successful" do
  4. # Find me in `config/routes.rb`
  5. get "/"
  6. expect(last_response).to be_successful
  7. expect(last_response.body).to eq("Hello from Hanami")
  8. end

Avoid test doubles when writing request tests, since we want to verify that the whole stack is behaving as expected.