How To Test Ruby CLI: Console


One of the most interesting challenges I met during Hanami development is to write isolated integration tests. The framework ships with an extensive Command Line Interface (CLI) to generate (or destroy) code, start the server, open a Ruby (or a database) console, create/drop the database, run the migrations, precompile assets, and print informations.

Console

Hanami console is an interactive REPL based on IRB, which is useful to interact with the objects defined in a project.

 hanami console
irb(main):001:0> repository = AuthorRepository.new
=> #<AuthorRepository:0x007fdc917373a8 ...>
irb(main):002:0> repository.all
=> []
irb(main):003:0> author = repository.create(name: "Luca")
=> #<Author:0x007fdc9170dd78 @attributes={:id=>1, :name=>"Luca", :created_at=>2017-02-07 09:39:46 UTC, :updated_at=>2017-02-07 09:39:46 UTC}>
irb(main):004:0> author.id
=> 1
irb(main):005:0> repository.all
=> [#<Author:0x007fdc916f70c8 @attributes={:id=>1, :name=>"Luca", :created_at=>2017-02-07 09:39:46 UTC, :updated_at=>2017-02-07 09:39:46 UTC}>]
irb(main):006:0>

How To Test Console

In order to test the console the test generates a fresh project (see with_project), it installs the dependencies, setup the database. Now I want to be able to programmatically interact with the console.

RSpec.describe "hanami console", type: :cli do
  context "irb" do
    it "starts console" do
      with_project("bookshelf_console_irb", console: :irb) do
        setup_model

        console do |input, _, _|
          input.puts("Hanami::VERSION")
          input.puts("Web::Application")
          input.puts("Web.routes")
          input.puts("BookRepository.new.all.to_a")
          input.puts("exit")
        end

        expect(out).to include(Hanami::VERSION)
        expect(out).to include("Web::Application")
        expect(out).to include("#<Hanami::Routes")
        expect(out).to include("[]")
        expect(out).to include("[#{project_name}] [INFO]")
        expect(out).to include("SELECT `id`, `title` FROM `books` ORDER BY `books`.`id`")
      end
    end
  end # irb

  # ...
end

The console spec helper uses Open3 from Ruby Standard Library to start a child process for the console. Specifically, I use Open3.popen3 with a proc to create a blocking execution of the console commands. That means the first part of the test simulates the inputs, while the second part asserts the output.

The console block yields input, which represents the stream for the console standard input. Just by using IO#puts, I’m able to simulate a human that types the commands.

When IRB receives "exit", it terminates the child process. At this point the thread for the child process is freed so I can capture the output and make it available as out variable.

The last part of the test asserts:

  • The Hanami version
  • That the Hanami applications and their routes are accessible from the console
  • The user is able to query the database via a repository
  • The database queries are logged in the console

Conclusion

I oversimplified the explanation for this article. If you want to level up your Ruby skills, it may worth to have a closer look at the code behind it.

To test Hanami CLI is complex: it requires a reliable CI build (with full isolation of each spec), and a deep knowledge of the Ruby toolchain. Next time we’ll talk about testing the Hanami server.

Luca Guidi

Family man, software architect, Open Source indie developer, speaker.

Rome, Italy https://lucaguidi.com