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 love to explain and answer questions on programming problems, the kind you find in coding interviews. I publish a new programming problem and its solution every Sunday. Did I mention that I love to 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