Are you looking for a case not here?

So you like to write your tests in spec format (using describe, before, after, and it) and running them with minitest. You have a set of tests with a common setup and that setup is orders of magnitude more expensive than the body of each test, for example.

require 'minitest/autorun'

describe 'end-to-end test on main component of RSS reader' do

  # before executes before each test
  before do
    # HTTPServer takes a couple of seconds to start
    @http_server = HTTPServer.new 'localhost', 9090
  end

  # after executes after each test
  after do
    @http_server.stop
    @http_server.join
  end

  it 'main component gets RSS feed' do
    # Takes several milliseconds to complete
    main.get('http://localhost:9090/feed.xml').wont_be nil
  end

  it 'main component raises RuntimeError when given malformed RSS feed' do
    # Takes several milliseconds to complete
    ->{ main.get('http://localhost:9090/bad.xml') }.must_raise RuntimeError
  end

end

Running the example takes you 4 to 5 seconds because starting the HTTP server is expensive. You would rather setup once before all tests and teardown once after all tests. In the case where you are a practical person, you use blocks before(:all) and after(:all) provided by gem minitest-hooks like so.

require 'minitest/autorun'
require 'minitest/hooks/default'

describe 'end-to-end test on main component of RSS reader' do

  # before(:all) executes before any test is run
  before(:all) do
    # HTTPServer takes a couple of seconds to start
    @http_server = HTTPServer.new 'localhost', 9090
  end

  # after(:all) executes after all tests have been run
  after(:all) do
    @http_server.stop
    @http_server.join
  end

  it 'main component gets RSS feed' do
    main.get('http://localhost:9090/feed.xml').wont_be nil
  end

  it 'main component raises RuntimeError when given malformed RSS feed' do
    ->{ main.get('http://localhost:9090/bad.xml') }.must_raise RuntimeError
  end

end

In the case where you prefer to do without minitest-hooks, you use a class instance variable and a minitest after_run block like so.

require 'minitest/autorun'

describe 'end-to-end test on main component of RSS reader' do

  # The block assigned to @expensive executes before any test is run
  @http_server = begin
    # HTTPServer takes a couple of seconds to start
    HTTPServer.new 'localhost', 9090
  end

  # The block given to Minitest::after_run executes way after all
  # tests have been run, right before minitest stops.
  Minitest.after_run do
    @http_server.stop
    @http_server.join
  end

  it 'main component gets RSS feed' do
    main.get('http://localhost:9090/feed.xml').wont_be nil
  end

  it 'main component raises RuntimeError when given malformed RSS feed' do
    ->{ main.get('http://localhost:9090/bad.xml') }.must_raise RuntimeError
  end

end

I have nested describe blocks

Watch out. minitest-hooks executes a given before(:all) before EACH nested describe block and a corresponding after(:all) after EACH describe block. Consider the following spec file.

require 'minitest/autorun'
require 'minitest/hooks/default'

describe "spec file, eager evaluation, nested describe block, minitest-hooks" do

  before(:all) do
    puts 'executing something expensive'
    @expensive = 'woah! expensive!'
  end

  after(:all) do
    puts "cleaning expensive value: #{@expensive}"
  end

  describe 'describe1' do
    it 'test1' do
      puts "test1: #{@expensive}"
    end

    it 'test2' do
      puts "test2: #{@expensive}"
    end
  end

  describe 'describe2' do
    it 'test3' do
      puts "test3: #{@expensive}"
    end

    it 'test4' do
      puts "test4: #{@expensive}"
    end
  end

end

The spec file will give you an output like the following.

ruslan$ bundle exec rake
Run options: --seed 36717

# Running:

executing something expensive
test1: woah! expensive!
.test2: woah! expensive!
.cleaning expensive value: woah! expensive!
executing something expensive
test4: woah! expensive!
.test3: woah! expensive!
.cleaning expensive value: woah! expensive!


Finished in 0.002084s, 1919.5342 runs/s, 0.0000 assertions/s.

4 runs, 0 assertions, 0 failures, 0 errors, 0 skips

To execute your setup before all describe blocks and your teardown after all describe blocks, you could do the following.

require 'minitest/autorun'

describe "spec file, eager evaluation, nested describe block" do

  $expensive = begin
                 puts 'executing something expensive'
                 'woah! expensive!'
               end

  def self.expensive
    $expensive
  end

  Minitest.after_run do
    puts "cleaning expensive value: #{$expensive}"
    puts "cleaning expensive value: #{expensive}"
    puts "cleaning expensive value: #{self.expensive}"
  end

  describe 'describe1' do
    it 'test1' do
      puts "test1: #{self.class.expensive}"
    end

    it 'test2' do
      puts "test2: #{self.class.expensive}"
    end
  end

  describe 'describe2' do
    it 'test3' do
      puts "test3: #{self.class.expensive}"
    end

    it 'test4' do
      puts "test4: #{self.class.expensive}"
    end
  end

end

If you have aversion to global variables, you can do the following.

require 'minitest/autorun'

class ClassEagerNested < Minitest::Spec

  @@expensive = begin
                  puts 'executing something expensive'
                  'woah! expensive!'
                end

  Minitest.after_run do
    puts "cleaning expensive value: #{@@expensive}"
  end

  describe 'describe1' do
    it 'test1' do
      puts "test1: #{@@expensive}"
    end

    it 'test2' do
      puts "test2: #{@@expensive}"
    end
  end

  describe 'describe2' do
    it 'test3' do
      puts "test3: #{@@expensive}"
    end

    it 'test4' do
      puts "test4: #{@@expensive}"
    end
  end

end

I am looking for another case

You know what, maybe you like writing specs or classes, maybe you have a flat or nested describe block, maybe you want eager or lazy evaluation, maybe you like using minitest-hooks or not. Why don’t you have a look at 16 cases?

FYI, issue 61 of minitest inspired me to write this post and the 16 cases. You may want to have a look at that for an idea of why minitest does not come with before(:all)/after(:all).

Want to read more?

I regularly write solutions to programming problems that you may or may not find in technical job interviews. I also love to explain those solutions and answer questions. If you would like to get the latest problem + solution, subscribe to the newsletter or subscribe via RSS. You can also follow me on Twitter, GitHub, and LinkedIn.

Comments